butterfly_viewer.aux_mdi

QMdiArea with drag-and-drop functions, vertical/horizontal window tiling, and keyboard shortcuts.

Not intended as a script.

Creates the multi document interface (MDI) widget for the Butterfly Viewer.

  1#!/usr/bin/env python3
  2
  3"""QMdiArea with drag-and-drop functions, vertical/horizontal window tiling, and keyboard shortcuts.
  4
  5Not intended as a script.
  6
  7Creates the multi document interface (MDI) widget for the Butterfly Viewer.
  8"""
  9# SPDX-License-Identifier: GPL-3.0-or-later
 10
 11
 12
 13from PyQt5 import QtWidgets, QtCore, QtGui
 14
 15
 16
 17class QMdiAreaWithCustomSignals(QtWidgets.QMdiArea):
 18    """Extend QMdiArea with drag-and-drop functions, vertical/horizontal window tiling, and keyboard shortcuts.
 19
 20    Instantiate without input.
 21    
 22    Features:
 23        Signals for drag-and-drop, subwindow events, shortcut keys.
 24        Methods for arranging the subwindows vertically and horizontally, and to track the history of the arrangement.
 25    """
 26
 27    file_path_dragged_and_dropped = QtCore.pyqtSignal(str)
 28    file_path_dragged = QtCore.pyqtSignal(bool)
 29    shortcut_escape_was_activated = QtCore.pyqtSignal()
 30    shortcut_f_was_activated = QtCore.pyqtSignal()
 31    shortcut_h_was_activated = QtCore.pyqtSignal()
 32    shortcut_ctrl_c_was_activated = QtCore.pyqtSignal()
 33
 34    first_subwindow_was_opened = QtCore.pyqtSignal()
 35    last_remaining_subwindow_was_closed = QtCore.pyqtSignal()
 36
 37    def __init__(self):
 38        super().__init__()
 39        
 40        self.setAcceptDrops(True)
 41        self.subWindowActivated.connect(self.subwindow_was_activated)
 42        self.last_tile_method = None
 43        self.are_there_any_subwindows_open = False
 44        self.most_recently_activated_subwindow = None
 45
 46        self.escape_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Escape"), self)
 47        self.escape_shortcut.activated.connect(self.shortcut_escape_was_activated)
 48
 49        self.f_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("f"), self)
 50        self.f_shortcut.activated.connect(self.shortcut_f_was_activated)
 51
 52        self.h_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("h"), self)
 53        self.h_shortcut.activated.connect(self.shortcut_h_was_activated)
 54
 55        self.ctrl_c_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+c"), self)
 56        self.ctrl_c_shortcut.activated.connect(self.shortcut_ctrl_c_was_activated)
 57
 58    def tile_subwindows_vertically(self, button_input=None):
 59        """Arrange subwindows vertically as a single column.
 60
 61        Arranges subwindows top to bottom in order of when they were added (oldest to newest).
 62        
 63        Args:
 64            button_input: Always set as None (kept for legacy purposes).
 65        """
 66        windows = self.subWindowList()
 67        position = QtCore.QPoint()
 68        for window in windows:
 69            rect = QtCore.QRect(0, 0, self.width(), self.height()/len(windows))
 70            window.setGeometry(rect)
 71            window.move(position)
 72            position.setY(position.y() + window.height())
 73        self.last_tile_method = "vertically"
 74
 75    def tile_subwindows_horizontally(self, button_input=None):
 76        """Arrange subwindows horizontally as a single row.
 77
 78        Arranges subwindows left to right in order of when they were added (oldest to newest).
 79        
 80        Args:
 81            button_input: Always set as None (kept for legacy purposes).
 82        """
 83        windows = self.subWindowList()
 84        position = QtCore.QPoint()
 85        for window in windows:
 86            rect = QtCore.QRect(0, 0, self.width()/len(windows), self.height())
 87            window.setGeometry(rect)
 88            window.move(position)
 89            position.setX(position.x() + window.width())
 90        self.last_tile_method = "horizontally"
 91    
 92    def tileSubWindows(self, button_input=None):
 93        """Arrange subwindows as tiles (override).
 94        
 95        Args:
 96            button_input: Always set as None (kept for legacy purposes).
 97        """
 98        super().tileSubWindows()
 99        self.last_tile_method = "grid"
100
101    def tile_what_was_done_last_time(self):
102        """Arrange subwindows based on previous arrangement.
103        
104        Needed to arrange windows in the last arranged method during events like resizing.
105        """
106        if self.last_tile_method == "horizontally":
107            self.tile_subwindows_horizontally()
108        elif self.last_tile_method == "vertically":
109            self.tile_subwindows_vertically()
110        else:
111            self.tileSubWindows()
112
113    def dragEnterEvent(self, event):
114        """event: Signal that one or more files have been dragged into the area."""
115        self.file_path_dragged.emit(True)
116        event.accept()
117
118    def dragMoveEvent(self, event):
119        """event: Signal that one or more files are being dragged in the area."""
120        event.accept()
121
122    def dragLeaveEvent(self, event):
123        """event: Signal that one or more files have been dragged out of the area."""
124        self.file_path_dragged.emit(False)
125        event.accept()
126
127    def dropEvent(self, event):
128        """event: Signal that one or more files have been dropped into the area."""
129        event.setDropAction(QtCore.Qt.CopyAction)
130
131        self.file_path_dragged.emit(False)
132
133        urls = event.mimeData().urls()
134
135        if urls:
136            for url in urls:
137                file_path = url.toLocalFile()
138                self.file_path_dragged_and_dropped.emit(file_path)
139            event.accept()
140        else:
141            event.ignore()
142
143    def subwindow_was_activated(self, window): 
144        """Signal if first subwindow has been activated or if last remaining subwindow has been closed.
145
146        Triggered when subwindow activated signal of area is emitted.
147        Fixes issues with improper subwindow activation behavior.
148
149        Args:
150            window (QMdiSubWindow)
151        """
152        
153        if not window: #  When the last remaining subwindow is closed, subWindowActivated throws Null window
154            self.are_there_any_subwindows_open = False
155            self.last_remaining_subwindow_was_closed.emit()
156        elif not self.are_there_any_subwindows_open: # If there is indeed a window but the boolean still shows there are none open, then change the boolean
157            self.are_there_any_subwindows_open = True
158            self.first_subwindow_was_opened.emit()
159            self.most_recently_activated_subwindow = window
160        return
161
162    def resizeEvent(self, event):
163        """Override resizeEvent() to maintain horizontal and vertical arrangement of subwindows during resizing.
164        
165        Fixes shuffling of subwindows when area is resized in vertical and horizontal arrangements.
166        """
167        super().resizeEvent(event)
168
169        if self.last_tile_method == "horizontally":
170            self.tile_subwindows_horizontally()
171        elif self.last_tile_method == "vertically":
172            self.tile_subwindows_vertically()
173        else:
174            return
class QMdiAreaWithCustomSignals(PyQt5.QtWidgets.QMdiArea):
 18class QMdiAreaWithCustomSignals(QtWidgets.QMdiArea):
 19    """Extend QMdiArea with drag-and-drop functions, vertical/horizontal window tiling, and keyboard shortcuts.
 20
 21    Instantiate without input.
 22    
 23    Features:
 24        Signals for drag-and-drop, subwindow events, shortcut keys.
 25        Methods for arranging the subwindows vertically and horizontally, and to track the history of the arrangement.
 26    """
 27
 28    file_path_dragged_and_dropped = QtCore.pyqtSignal(str)
 29    file_path_dragged = QtCore.pyqtSignal(bool)
 30    shortcut_escape_was_activated = QtCore.pyqtSignal()
 31    shortcut_f_was_activated = QtCore.pyqtSignal()
 32    shortcut_h_was_activated = QtCore.pyqtSignal()
 33    shortcut_ctrl_c_was_activated = QtCore.pyqtSignal()
 34
 35    first_subwindow_was_opened = QtCore.pyqtSignal()
 36    last_remaining_subwindow_was_closed = QtCore.pyqtSignal()
 37
 38    def __init__(self):
 39        super().__init__()
 40        
 41        self.setAcceptDrops(True)
 42        self.subWindowActivated.connect(self.subwindow_was_activated)
 43        self.last_tile_method = None
 44        self.are_there_any_subwindows_open = False
 45        self.most_recently_activated_subwindow = None
 46
 47        self.escape_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Escape"), self)
 48        self.escape_shortcut.activated.connect(self.shortcut_escape_was_activated)
 49
 50        self.f_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("f"), self)
 51        self.f_shortcut.activated.connect(self.shortcut_f_was_activated)
 52
 53        self.h_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("h"), self)
 54        self.h_shortcut.activated.connect(self.shortcut_h_was_activated)
 55
 56        self.ctrl_c_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+c"), self)
 57        self.ctrl_c_shortcut.activated.connect(self.shortcut_ctrl_c_was_activated)
 58
 59    def tile_subwindows_vertically(self, button_input=None):
 60        """Arrange subwindows vertically as a single column.
 61
 62        Arranges subwindows top to bottom in order of when they were added (oldest to newest).
 63        
 64        Args:
 65            button_input: Always set as None (kept for legacy purposes).
 66        """
 67        windows = self.subWindowList()
 68        position = QtCore.QPoint()
 69        for window in windows:
 70            rect = QtCore.QRect(0, 0, self.width(), self.height()/len(windows))
 71            window.setGeometry(rect)
 72            window.move(position)
 73            position.setY(position.y() + window.height())
 74        self.last_tile_method = "vertically"
 75
 76    def tile_subwindows_horizontally(self, button_input=None):
 77        """Arrange subwindows horizontally as a single row.
 78
 79        Arranges subwindows left to right in order of when they were added (oldest to newest).
 80        
 81        Args:
 82            button_input: Always set as None (kept for legacy purposes).
 83        """
 84        windows = self.subWindowList()
 85        position = QtCore.QPoint()
 86        for window in windows:
 87            rect = QtCore.QRect(0, 0, self.width()/len(windows), self.height())
 88            window.setGeometry(rect)
 89            window.move(position)
 90            position.setX(position.x() + window.width())
 91        self.last_tile_method = "horizontally"
 92    
 93    def tileSubWindows(self, button_input=None):
 94        """Arrange subwindows as tiles (override).
 95        
 96        Args:
 97            button_input: Always set as None (kept for legacy purposes).
 98        """
 99        super().tileSubWindows()
100        self.last_tile_method = "grid"
101
102    def tile_what_was_done_last_time(self):
103        """Arrange subwindows based on previous arrangement.
104        
105        Needed to arrange windows in the last arranged method during events like resizing.
106        """
107        if self.last_tile_method == "horizontally":
108            self.tile_subwindows_horizontally()
109        elif self.last_tile_method == "vertically":
110            self.tile_subwindows_vertically()
111        else:
112            self.tileSubWindows()
113
114    def dragEnterEvent(self, event):
115        """event: Signal that one or more files have been dragged into the area."""
116        self.file_path_dragged.emit(True)
117        event.accept()
118
119    def dragMoveEvent(self, event):
120        """event: Signal that one or more files are being dragged in the area."""
121        event.accept()
122
123    def dragLeaveEvent(self, event):
124        """event: Signal that one or more files have been dragged out of the area."""
125        self.file_path_dragged.emit(False)
126        event.accept()
127
128    def dropEvent(self, event):
129        """event: Signal that one or more files have been dropped into the area."""
130        event.setDropAction(QtCore.Qt.CopyAction)
131
132        self.file_path_dragged.emit(False)
133
134        urls = event.mimeData().urls()
135
136        if urls:
137            for url in urls:
138                file_path = url.toLocalFile()
139                self.file_path_dragged_and_dropped.emit(file_path)
140            event.accept()
141        else:
142            event.ignore()
143
144    def subwindow_was_activated(self, window): 
145        """Signal if first subwindow has been activated or if last remaining subwindow has been closed.
146
147        Triggered when subwindow activated signal of area is emitted.
148        Fixes issues with improper subwindow activation behavior.
149
150        Args:
151            window (QMdiSubWindow)
152        """
153        
154        if not window: #  When the last remaining subwindow is closed, subWindowActivated throws Null window
155            self.are_there_any_subwindows_open = False
156            self.last_remaining_subwindow_was_closed.emit()
157        elif not self.are_there_any_subwindows_open: # If there is indeed a window but the boolean still shows there are none open, then change the boolean
158            self.are_there_any_subwindows_open = True
159            self.first_subwindow_was_opened.emit()
160            self.most_recently_activated_subwindow = window
161        return
162
163    def resizeEvent(self, event):
164        """Override resizeEvent() to maintain horizontal and vertical arrangement of subwindows during resizing.
165        
166        Fixes shuffling of subwindows when area is resized in vertical and horizontal arrangements.
167        """
168        super().resizeEvent(event)
169
170        if self.last_tile_method == "horizontally":
171            self.tile_subwindows_horizontally()
172        elif self.last_tile_method == "vertically":
173            self.tile_subwindows_vertically()
174        else:
175            return

Extend QMdiArea with drag-and-drop functions, vertical/horizontal window tiling, and keyboard shortcuts.

Instantiate without input.

Features:

Signals for drag-and-drop, subwindow events, shortcut keys. Methods for arranging the subwindows vertically and horizontally, and to track the history of the arrangement.

def tile_subwindows_vertically(self, button_input=None):
59    def tile_subwindows_vertically(self, button_input=None):
60        """Arrange subwindows vertically as a single column.
61
62        Arranges subwindows top to bottom in order of when they were added (oldest to newest).
63        
64        Args:
65            button_input: Always set as None (kept for legacy purposes).
66        """
67        windows = self.subWindowList()
68        position = QtCore.QPoint()
69        for window in windows:
70            rect = QtCore.QRect(0, 0, self.width(), self.height()/len(windows))
71            window.setGeometry(rect)
72            window.move(position)
73            position.setY(position.y() + window.height())
74        self.last_tile_method = "vertically"

Arrange subwindows vertically as a single column.

Arranges subwindows top to bottom in order of when they were added (oldest to newest).

Arguments:
  • button_input: Always set as None (kept for legacy purposes).
def tile_subwindows_horizontally(self, button_input=None):
76    def tile_subwindows_horizontally(self, button_input=None):
77        """Arrange subwindows horizontally as a single row.
78
79        Arranges subwindows left to right in order of when they were added (oldest to newest).
80        
81        Args:
82            button_input: Always set as None (kept for legacy purposes).
83        """
84        windows = self.subWindowList()
85        position = QtCore.QPoint()
86        for window in windows:
87            rect = QtCore.QRect(0, 0, self.width()/len(windows), self.height())
88            window.setGeometry(rect)
89            window.move(position)
90            position.setX(position.x() + window.width())
91        self.last_tile_method = "horizontally"

Arrange subwindows horizontally as a single row.

Arranges subwindows left to right in order of when they were added (oldest to newest).

Arguments:
  • button_input: Always set as None (kept for legacy purposes).
def tileSubWindows(self, button_input=None):
 93    def tileSubWindows(self, button_input=None):
 94        """Arrange subwindows as tiles (override).
 95        
 96        Args:
 97            button_input: Always set as None (kept for legacy purposes).
 98        """
 99        super().tileSubWindows()
100        self.last_tile_method = "grid"

Arrange subwindows as tiles (override).

Arguments:
  • button_input: Always set as None (kept for legacy purposes).
def tile_what_was_done_last_time(self):
102    def tile_what_was_done_last_time(self):
103        """Arrange subwindows based on previous arrangement.
104        
105        Needed to arrange windows in the last arranged method during events like resizing.
106        """
107        if self.last_tile_method == "horizontally":
108            self.tile_subwindows_horizontally()
109        elif self.last_tile_method == "vertically":
110            self.tile_subwindows_vertically()
111        else:
112            self.tileSubWindows()

Arrange subwindows based on previous arrangement.

Needed to arrange windows in the last arranged method during events like resizing.

def dragEnterEvent(self, event):
114    def dragEnterEvent(self, event):
115        """event: Signal that one or more files have been dragged into the area."""
116        self.file_path_dragged.emit(True)
117        event.accept()

event: Signal that one or more files have been dragged into the area.

def dragMoveEvent(self, event):
119    def dragMoveEvent(self, event):
120        """event: Signal that one or more files are being dragged in the area."""
121        event.accept()

event: Signal that one or more files are being dragged in the area.

def dragLeaveEvent(self, event):
123    def dragLeaveEvent(self, event):
124        """event: Signal that one or more files have been dragged out of the area."""
125        self.file_path_dragged.emit(False)
126        event.accept()

event: Signal that one or more files have been dragged out of the area.

def dropEvent(self, event):
128    def dropEvent(self, event):
129        """event: Signal that one or more files have been dropped into the area."""
130        event.setDropAction(QtCore.Qt.CopyAction)
131
132        self.file_path_dragged.emit(False)
133
134        urls = event.mimeData().urls()
135
136        if urls:
137            for url in urls:
138                file_path = url.toLocalFile()
139                self.file_path_dragged_and_dropped.emit(file_path)
140            event.accept()
141        else:
142            event.ignore()

event: Signal that one or more files have been dropped into the area.

def subwindow_was_activated(self, window):
144    def subwindow_was_activated(self, window): 
145        """Signal if first subwindow has been activated or if last remaining subwindow has been closed.
146
147        Triggered when subwindow activated signal of area is emitted.
148        Fixes issues with improper subwindow activation behavior.
149
150        Args:
151            window (QMdiSubWindow)
152        """
153        
154        if not window: #  When the last remaining subwindow is closed, subWindowActivated throws Null window
155            self.are_there_any_subwindows_open = False
156            self.last_remaining_subwindow_was_closed.emit()
157        elif not self.are_there_any_subwindows_open: # If there is indeed a window but the boolean still shows there are none open, then change the boolean
158            self.are_there_any_subwindows_open = True
159            self.first_subwindow_was_opened.emit()
160            self.most_recently_activated_subwindow = window
161        return

Signal if first subwindow has been activated or if last remaining subwindow has been closed.

Triggered when subwindow activated signal of area is emitted. Fixes issues with improper subwindow activation behavior.

Arguments:
  • window (QMdiSubWindow)
def resizeEvent(self, event):
163    def resizeEvent(self, event):
164        """Override resizeEvent() to maintain horizontal and vertical arrangement of subwindows during resizing.
165        
166        Fixes shuffling of subwindows when area is resized in vertical and horizontal arrangements.
167        """
168        super().resizeEvent(event)
169
170        if self.last_tile_method == "horizontally":
171            self.tile_subwindows_horizontally()
172        elif self.last_tile_method == "vertically":
173            self.tile_subwindows_vertically()
174        else:
175            return

Override resizeEvent() to maintain horizontal and vertical arrangement of subwindows during resizing.

Fixes shuffling of subwindows when area is resized in vertical and horizontal arrangements.