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
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
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
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
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
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
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
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
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
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
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)