Skip to content

missingfiles

sleap.gui.dialogs.missingfiles

Gui for prompting the user to locate one or more missing files.

Classes:

Name Description
MissingFileTable

Qt table view for missing files.

MissingFileTableModel

Qt table model for missing files.

MissingFilesDialog

MissingFileTable

Bases: QTableView

Qt table view for missing files.

Arguments are passed through to the table view object.

Source code in sleap/gui/dialogs/missingfiles.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
class MissingFileTable(QtWidgets.QTableView):
    """
    Qt table view for missing files.

    Arguments are passed through to the table view object.
    """

    def __init__(self, *args, **kwargs):
        super(MissingFileTable, self).__init__()
        self.setModel(MissingFileTableModel(*args, **kwargs))
        self.resizeColumnsToContents()

    def reset(self):
        super(MissingFileTable, self).reset()
        self.resizeColumnsToContents()

MissingFileTableModel

Bases: QAbstractTableModel

Qt table model for missing files.

Parameters:

Name Type Description Default
filenames List[str]

Filenames to show, needn't all be missing.

required
missing List[bool]

Corresponding list, whether each file is missing.

required

Methods:

Name Description
columnCount

Required by Qt.

data

Required by Qt.

headerData

Required by Qt.

rowCount

Required by Qt.

Source code in sleap/gui/dialogs/missingfiles.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
class MissingFileTableModel(QtCore.QAbstractTableModel):
    """Qt table model for missing files.

    Args:
        filenames: Filenames to show, needn't all be missing.
        missing: Corresponding list, whether each file is missing.
    """

    _props = ["filename"]

    def __init__(self, filenames: List[str], missing: List[bool]):
        super(MissingFileTableModel, self).__init__()
        self.filenames = filenames
        self.missing = missing

    def data(self, index: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole):
        """Required by Qt."""
        if not index.isValid():
            return None

        idx = index.row()
        prop = self._props[index.column()]

        if idx >= self.rowCount():
            return None

        if role == QtCore.Qt.DisplayRole:
            if prop == "filename":
                return self.filenames[idx]

        elif role == QtCore.Qt.ForegroundRole:
            return QtGui.QColor("red") if self.missing[idx] else None

        return None

    def rowCount(self, *args):
        """Required by Qt."""
        return len(self.filenames)

    def columnCount(self, *args):
        """Required by Qt."""
        return len(self._props)

    def headerData(
        self, section, orientation: QtCore.Qt.Orientation, role=QtCore.Qt.DisplayRole
    ):
        """Required by Qt."""
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self._props[section]
            elif orientation == QtCore.Qt.Vertical:
                return section
        return None

columnCount(*args)

Required by Qt.

Source code in sleap/gui/dialogs/missingfiles.py
225
226
227
def columnCount(self, *args):
    """Required by Qt."""
    return len(self._props)

data(index, role=QtCore.Qt.DisplayRole)

Required by Qt.

Source code in sleap/gui/dialogs/missingfiles.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def data(self, index: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole):
    """Required by Qt."""
    if not index.isValid():
        return None

    idx = index.row()
    prop = self._props[index.column()]

    if idx >= self.rowCount():
        return None

    if role == QtCore.Qt.DisplayRole:
        if prop == "filename":
            return self.filenames[idx]

    elif role == QtCore.Qt.ForegroundRole:
        return QtGui.QColor("red") if self.missing[idx] else None

    return None

headerData(section, orientation, role=QtCore.Qt.DisplayRole)

Required by Qt.

Source code in sleap/gui/dialogs/missingfiles.py
229
230
231
232
233
234
235
236
237
238
def headerData(
    self, section, orientation: QtCore.Qt.Orientation, role=QtCore.Qt.DisplayRole
):
    """Required by Qt."""
    if role == QtCore.Qt.DisplayRole:
        if orientation == QtCore.Qt.Horizontal:
            return self._props[section]
        elif orientation == QtCore.Qt.Vertical:
            return section
    return None

rowCount(*args)

Required by Qt.

Source code in sleap/gui/dialogs/missingfiles.py
221
222
223
def rowCount(self, *args):
    """Required by Qt."""
    return len(self.filenames)

MissingFilesDialog

Bases: QDialog

Methods:

Name Description
__init__

Creates dialog window for finding missing files.

locateFile

Shows dialog for user to locate a specific missing file.

setFilename

Applies change after user finds missing file.

Source code in sleap/gui/dialogs/missingfiles.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class MissingFilesDialog(QtWidgets.QDialog):
    def __init__(
        self,
        filenames: List[str],
        missing: List[bool] = None,
        replace: bool = False,
        allow_incomplete: bool = False,
        *args,
        **kwargs,
    ):
        """
        Creates dialog window for finding missing files.

        Any changes made by user will be reflected in filenames list.

        Args:
            filenames: List of filenames to find, needn't all be missing.
            missing: Corresponding list, whether each file is missing. If
                not given, then we'll check whether each file exists.
            replace: Whether we are replacing files (already found) or
                locating files (not already found). Affects text in dialog.
            allow_incomplete: Whether to enable "accept" button when there
                are still missing files.

        Returns:
            None.
        """

        super(MissingFilesDialog, self).__init__(*args, **kwargs)

        if not missing:
            missing = pathutils.list_file_missing(filenames)

        self.filenames = filenames
        self.missing = missing
        self.replace = replace

        missing_count = sum(missing)

        layout = QtWidgets.QVBoxLayout()

        if replace:
            info_text = "Double-click on a file to replace it..."
        else:
            info_text = (
                f"{missing_count} file(s) which could not be found. "
                "Please double-click on a file to locate it..."
            )
        info_label = QtWidgets.QLabel(info_text)
        layout.addWidget(info_label)

        self.file_table = MissingFileTable(filenames, missing)
        self.file_table.doubleClicked.connect(_qt_row_index_call(self.locateFile))
        layout.addWidget(self.file_table)

        buttons = QtWidgets.QDialogButtonBox()
        buttons.addButton("Abort", QtWidgets.QDialogButtonBox.RejectRole)
        self.accept_button = buttons.addButton(
            "Continue", QtWidgets.QDialogButtonBox.AcceptRole
        )
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)

        if not allow_incomplete:
            self.accept_button.setEnabled(False)

        layout.addWidget(buttons)

        self.setLayout(layout)

    def locateFile(self, idx: int):
        """Shows dialog for user to locate a specific missing file."""
        old_filename = self.filenames[idx]
        _, old_ext = os.path.splitext(old_filename)

        caption = f"Please locate {old_filename}..."
        filters = [f"Missing file type (*{old_ext})", "Any File (*.*)"]
        filters = [filters[0]] if self.replace else filters
        new_filename, _ = FileDialog.open(
            None, dir=None, caption=caption, filter=";;".join(filters)
        )

        path_new_filename = Path(new_filename)
        paths = [str(PurePath(fn)) for fn in self.filenames]
        if str(path_new_filename) in paths:
            # Do not allow same video to be imported more than once.
            QtWidgets.QMessageBox(
                text=(
                    f"The file <b>{path_new_filename.name}</b> cannot be added to "
                    "the project multiple times."
                )
            ).exec_()
        elif new_filename:
            # Try using this change to find other missing files
            self.setFilename(idx, new_filename)

            # Redraw the table
            self.file_table.reset()

    def setFilename(self, idx: int, filename: str, confirm: bool = True):
        """Applies change after user finds missing file."""
        old_filename = self.filenames[idx]

        self.filenames[idx] = filename
        self.missing[idx] = False

        old_prefix, new_prefix = pathutils.find_changed_subpath(old_filename, filename)

        # See if we can apply same change to find other missing files.
        # We'll ask for confirmation for making these changes.
        confirm_callback = None
        if confirm:

            def confirm_callback():
                return self.confirmAutoReplace(old_prefix, new_prefix)

        pathutils.filenames_prefix_change(
            self.filenames, old_prefix, new_prefix, self.missing, confirm_callback
        )

        # If there are no missing files still, enable the "accept" button
        if sum(self.missing) == 0:
            self.accept_button.setEnabled(True)

    def confirmAutoReplace(self, old, new):
        message = (
            f"Other missing files can be found by replacing\n\n"
            f"{old}\n\nwith\n\n{new}\n\nWould you like to apply this "
            f"change?"
        )

        response = QtWidgets.QMessageBox.question(
            self,
            "Apply change to other paths",
            message,
            QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.Yes,
        )
        return response == QtWidgets.QMessageBox.Yes

    def finish(self):
        self.accept()

__init__(filenames, missing=None, replace=False, allow_incomplete=False, *args, **kwargs)

Creates dialog window for finding missing files.

Any changes made by user will be reflected in filenames list.

Parameters:

Name Type Description Default
filenames List[str]

List of filenames to find, needn't all be missing.

required
missing List[bool]

Corresponding list, whether each file is missing. If not given, then we'll check whether each file exists.

None
replace bool

Whether we are replacing files (already found) or locating files (not already found). Affects text in dialog.

False
allow_incomplete bool

Whether to enable "accept" button when there are still missing files.

False

Returns:

Type Description

None.

Source code in sleap/gui/dialogs/missingfiles.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def __init__(
    self,
    filenames: List[str],
    missing: List[bool] = None,
    replace: bool = False,
    allow_incomplete: bool = False,
    *args,
    **kwargs,
):
    """
    Creates dialog window for finding missing files.

    Any changes made by user will be reflected in filenames list.

    Args:
        filenames: List of filenames to find, needn't all be missing.
        missing: Corresponding list, whether each file is missing. If
            not given, then we'll check whether each file exists.
        replace: Whether we are replacing files (already found) or
            locating files (not already found). Affects text in dialog.
        allow_incomplete: Whether to enable "accept" button when there
            are still missing files.

    Returns:
        None.
    """

    super(MissingFilesDialog, self).__init__(*args, **kwargs)

    if not missing:
        missing = pathutils.list_file_missing(filenames)

    self.filenames = filenames
    self.missing = missing
    self.replace = replace

    missing_count = sum(missing)

    layout = QtWidgets.QVBoxLayout()

    if replace:
        info_text = "Double-click on a file to replace it..."
    else:
        info_text = (
            f"{missing_count} file(s) which could not be found. "
            "Please double-click on a file to locate it..."
        )
    info_label = QtWidgets.QLabel(info_text)
    layout.addWidget(info_label)

    self.file_table = MissingFileTable(filenames, missing)
    self.file_table.doubleClicked.connect(_qt_row_index_call(self.locateFile))
    layout.addWidget(self.file_table)

    buttons = QtWidgets.QDialogButtonBox()
    buttons.addButton("Abort", QtWidgets.QDialogButtonBox.RejectRole)
    self.accept_button = buttons.addButton(
        "Continue", QtWidgets.QDialogButtonBox.AcceptRole
    )
    buttons.accepted.connect(self.accept)
    buttons.rejected.connect(self.reject)

    if not allow_incomplete:
        self.accept_button.setEnabled(False)

    layout.addWidget(buttons)

    self.setLayout(layout)

locateFile(idx)

Shows dialog for user to locate a specific missing file.

Source code in sleap/gui/dialogs/missingfiles.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def locateFile(self, idx: int):
    """Shows dialog for user to locate a specific missing file."""
    old_filename = self.filenames[idx]
    _, old_ext = os.path.splitext(old_filename)

    caption = f"Please locate {old_filename}..."
    filters = [f"Missing file type (*{old_ext})", "Any File (*.*)"]
    filters = [filters[0]] if self.replace else filters
    new_filename, _ = FileDialog.open(
        None, dir=None, caption=caption, filter=";;".join(filters)
    )

    path_new_filename = Path(new_filename)
    paths = [str(PurePath(fn)) for fn in self.filenames]
    if str(path_new_filename) in paths:
        # Do not allow same video to be imported more than once.
        QtWidgets.QMessageBox(
            text=(
                f"The file <b>{path_new_filename.name}</b> cannot be added to "
                "the project multiple times."
            )
        ).exec_()
    elif new_filename:
        # Try using this change to find other missing files
        self.setFilename(idx, new_filename)

        # Redraw the table
        self.file_table.reset()

setFilename(idx, filename, confirm=True)

Applies change after user finds missing file.

Source code in sleap/gui/dialogs/missingfiles.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def setFilename(self, idx: int, filename: str, confirm: bool = True):
    """Applies change after user finds missing file."""
    old_filename = self.filenames[idx]

    self.filenames[idx] = filename
    self.missing[idx] = False

    old_prefix, new_prefix = pathutils.find_changed_subpath(old_filename, filename)

    # See if we can apply same change to find other missing files.
    # We'll ask for confirmation for making these changes.
    confirm_callback = None
    if confirm:

        def confirm_callback():
            return self.confirmAutoReplace(old_prefix, new_prefix)

    pathutils.filenames_prefix_change(
        self.filenames, old_prefix, new_prefix, self.missing, confirm_callback
    )

    # If there are no missing files still, enable the "accept" button
    if sum(self.missing) == 0:
        self.accept_button.setEnabled(True)