123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- /******************************************************************************
- Copyright (C) 2019 by Dillon Pentz <[email protected]>
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- ******************************************************************************/
- #include "moc_window-missing-files.cpp"
- #include "window-basic-main.hpp"
- #include "obs-app.hpp"
- #include <QLineEdit>
- #include <QToolButton>
- #include <QFileDialog>
- #include <qt-wrappers.hpp>
- enum MissingFilesColumn {
- Source,
- OriginalPath,
- NewPath,
- State,
- Count
- };
- enum MissingFilesRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole };
- /**********************************************************
- Delegate - Presents cells in the grid.
- **********************************************************/
- MissingFilesPathItemDelegate::MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath)
- : QStyledItemDelegate(),
- isOutput(isOutput),
- defaultPath(defaultPath)
- {
- }
- QWidget *MissingFilesPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
- const QModelIndex &) const
- {
- QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding,
- QSizePolicy::ControlType::PushButton);
- QWidget *container = new QWidget(parent);
- auto browseCallback = [this, container]() {
- const_cast<MissingFilesPathItemDelegate *>(this)->handleBrowse(container);
- };
- auto clearCallback = [this, container]() {
- const_cast<MissingFilesPathItemDelegate *>(this)->handleClear(container);
- };
- QHBoxLayout *layout = new QHBoxLayout();
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(0);
- QLineEdit *text = new QLineEdit();
- text->setObjectName(QStringLiteral("text"));
- text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding,
- QSizePolicy::ControlType::LineEdit));
- layout->addWidget(text);
- QToolButton *browseButton = new QToolButton();
- browseButton->setText("...");
- browseButton->setSizePolicy(buttonSizePolicy);
- layout->addWidget(browseButton);
- container->connect(browseButton, &QToolButton::clicked, browseCallback);
- // The "clear" button is not shown in input cells
- if (isOutput) {
- QToolButton *clearButton = new QToolButton();
- clearButton->setText("X");
- clearButton->setSizePolicy(buttonSizePolicy);
- layout->addWidget(clearButton);
- container->connect(clearButton, &QToolButton::clicked, clearCallback);
- }
- container->setLayout(layout);
- container->setFocusProxy(text);
- return container;
- }
- void MissingFilesPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
- {
- QLineEdit *text = editor->findChild<QLineEdit *>();
- text->setText(index.data().toString());
- editor->setProperty(PATH_LIST_PROP, QVariant());
- }
- void MissingFilesPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- // We use the PATH_LIST_PROP property to pass a list of
- // path strings from the editor widget into the model's
- // NewPathsToProcessRole. This is only used when paths
- // are selected through the "browse" or "delete" buttons
- // in the editor. If the user enters new text in the
- // text box, we simply pass that text on to the model
- // as normal text data in the default role.
- QVariant pathListProp = editor->property(PATH_LIST_PROP);
- if (pathListProp.isValid()) {
- QStringList list = editor->property(PATH_LIST_PROP).toStringList();
- if (isOutput) {
- model->setData(index, list);
- } else
- model->setData(index, list, MissingFilesRole::NewPathsToProcessRole);
- } else {
- QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
- model->setData(index, lineEdit->text(), 0);
- }
- }
- void MissingFilesPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
- const QModelIndex &index) const
- {
- QStyleOptionViewItem localOption = option;
- initStyleOption(&localOption, index);
- QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter);
- }
- void MissingFilesPathItemDelegate::handleBrowse(QWidget *container)
- {
- QLineEdit *text = container->findChild<QLineEdit *>();
- QString currentPath = text->text();
- if (currentPath.isEmpty() || currentPath.compare(QTStr("MissingFiles.Clear")) == 0)
- currentPath = defaultPath;
- bool isSet = false;
- if (isOutput) {
- QString newPath =
- QFileDialog::getOpenFileName(container, QTStr("MissingFiles.SelectFile"), currentPath, nullptr);
- #ifdef __APPLE__
- // TODO: Revisit when QTBUG-42661 is fixed
- container->window()->raise();
- #endif
- if (!newPath.isEmpty()) {
- container->setProperty(PATH_LIST_PROP, QStringList() << newPath);
- isSet = true;
- }
- }
- if (isSet)
- emit commitData(container);
- }
- void MissingFilesPathItemDelegate::handleClear(QWidget *container)
- {
- // An empty string list will indicate that the entry is being
- // blanked and should be deleted.
- container->setProperty(PATH_LIST_PROP, QStringList() << QTStr("MissingFiles.Clear"));
- container->findChild<QLineEdit *>()->clearFocus();
- ((QWidget *)container->parent())->setFocus();
- emit commitData(container);
- }
- /**
- Model
- **/
- MissingFilesModel::MissingFilesModel(QObject *parent) : QAbstractTableModel(parent)
- {
- QStyle *style = QApplication::style();
- warningIcon = style->standardIcon(QStyle::SP_MessageBoxWarning);
- }
- int MissingFilesModel::rowCount(const QModelIndex &) const
- {
- return files.length();
- }
- int MissingFilesModel::columnCount(const QModelIndex &) const
- {
- return MissingFilesColumn::Count;
- }
- int MissingFilesModel::found() const
- {
- int res = 0;
- for (int i = 0; i < files.length(); i++) {
- if (files[i].state != Missing && files[i].state != Cleared)
- res++;
- }
- return res;
- }
- QVariant MissingFilesModel::data(const QModelIndex &index, int role) const
- {
- QVariant result = QVariant();
- if (index.row() >= files.length()) {
- return QVariant();
- } else if (role == Qt::DisplayRole) {
- QFileInfo fi(files[index.row()].originalPath);
- switch (index.column()) {
- case MissingFilesColumn::Source:
- result = files[index.row()].source;
- break;
- case MissingFilesColumn::OriginalPath:
- result = fi.fileName();
- break;
- case MissingFilesColumn::NewPath:
- result = files[index.row()].newPath;
- break;
- case MissingFilesColumn::State:
- switch (files[index.row()].state) {
- case MissingFilesState::Missing:
- result = QTStr("MissingFiles.Missing");
- break;
- case MissingFilesState::Replaced:
- result = QTStr("MissingFiles.Replaced");
- break;
- case MissingFilesState::Found:
- result = QTStr("MissingFiles.Found");
- break;
- case MissingFilesState::Cleared:
- result = QTStr("MissingFiles.Cleared");
- break;
- }
- break;
- }
- } else if (role == Qt::DecorationRole && index.column() == MissingFilesColumn::Source) {
- OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
- OBSSourceAutoRelease source = obs_get_source_by_name(files[index.row()].source.toStdString().c_str());
- if (source) {
- result = main->GetSourceIcon(obs_source_get_id(source));
- }
- } else if (role == Qt::FontRole && index.column() == MissingFilesColumn::State) {
- QFont font = QFont();
- font.setBold(true);
- result = font;
- } else if (role == Qt::ToolTipRole && index.column() == MissingFilesColumn::State) {
- switch (files[index.row()].state) {
- case MissingFilesState::Missing:
- result = QTStr("MissingFiles.Missing");
- break;
- case MissingFilesState::Replaced:
- result = QTStr("MissingFiles.Replaced");
- break;
- case MissingFilesState::Found:
- result = QTStr("MissingFiles.Found");
- break;
- case MissingFilesState::Cleared:
- result = QTStr("MissingFiles.Cleared");
- break;
- default:
- break;
- }
- } else if (role == Qt::ToolTipRole) {
- switch (index.column()) {
- case MissingFilesColumn::OriginalPath:
- result = files[index.row()].originalPath;
- break;
- case MissingFilesColumn::NewPath:
- result = files[index.row()].newPath;
- break;
- default:
- break;
- }
- }
- return result;
- }
- Qt::ItemFlags MissingFilesModel::flags(const QModelIndex &index) const
- {
- Qt::ItemFlags flags = QAbstractTableModel::flags(index);
- if (index.column() == MissingFilesColumn::OriginalPath) {
- flags &= ~Qt::ItemIsEditable;
- } else if (index.column() == MissingFilesColumn::NewPath && index.row() != files.length()) {
- flags |= Qt::ItemIsEditable;
- }
- return flags;
- }
- void MissingFilesModel::fileCheckLoop(QList<MissingFileEntry> files, QString path, bool skipPrompt)
- {
- loop = false;
- QUrl url = QUrl().fromLocalFile(path);
- QString dir = url.toDisplayString(QUrl::RemoveScheme | QUrl::RemoveFilename | QUrl::PreferLocalFile);
- bool prompted = skipPrompt;
- for (int i = 0; i < files.length(); i++) {
- if (files[i].state != MissingFilesState::Missing)
- continue;
- QUrl origFile = QUrl().fromLocalFile(files[i].originalPath);
- QString filename = origFile.fileName();
- QString testFile = dir + filename;
- if (os_file_exists(testFile.toStdString().c_str())) {
- if (!prompted) {
- QMessageBox::StandardButton button =
- QMessageBox::question(nullptr, QTStr("MissingFiles.AutoSearch"),
- QTStr("MissingFiles.AutoSearchText"));
- if (button == QMessageBox::No)
- break;
- prompted = true;
- }
- QModelIndex in = index(i, MissingFilesColumn::NewPath);
- setData(in, testFile, 0);
- }
- }
- loop = true;
- }
- bool MissingFilesModel::setData(const QModelIndex &index, const QVariant &value, int role)
- {
- bool success = false;
- if (role == MissingFilesRole::NewPathsToProcessRole) {
- QStringList list = value.toStringList();
- int row = index.row() + 1;
- beginInsertRows(QModelIndex(), row, row);
- MissingFileEntry entry;
- entry.originalPath = list[0].replace("\\", "/");
- entry.source = list[1];
- files.insert(row, entry);
- row++;
- endInsertRows();
- success = true;
- } else {
- QString path = value.toString();
- if (index.column() == MissingFilesColumn::NewPath) {
- files[index.row()].newPath = value.toString();
- QString fileName = QUrl(path).fileName();
- QString origFileName = QUrl(files[index.row()].originalPath).fileName();
- if (path.isEmpty()) {
- files[index.row()].state = MissingFilesState::Missing;
- } else if (path.compare(QTStr("MissingFiles.Clear")) == 0) {
- files[index.row()].state = MissingFilesState::Cleared;
- } else if (fileName.compare(origFileName) == 0) {
- files[index.row()].state = MissingFilesState::Found;
- if (loop)
- fileCheckLoop(files, path, false);
- } else {
- files[index.row()].state = MissingFilesState::Replaced;
- if (loop)
- fileCheckLoop(files, path, false);
- }
- emit dataChanged(index, index);
- success = true;
- }
- }
- return success;
- }
- QVariant MissingFilesModel::headerData(int section, Qt::Orientation orientation, int role) const
- {
- QVariant result = QVariant();
- if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) {
- switch (section) {
- case MissingFilesColumn::State:
- result = QTStr("MissingFiles.State");
- break;
- case MissingFilesColumn::Source:
- result = QTStr("Basic.Main.Source");
- break;
- case MissingFilesColumn::OriginalPath:
- result = QTStr("MissingFiles.MissingFile");
- break;
- case MissingFilesColumn::NewPath:
- result = QTStr("MissingFiles.NewFile");
- break;
- }
- }
- return result;
- }
- OBSMissingFiles::OBSMissingFiles(obs_missing_files_t *files, QWidget *parent)
- : QDialog(parent),
- filesModel(new MissingFilesModel),
- ui(new Ui::OBSMissingFiles)
- {
- setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
- ui->setupUi(this);
- ui->tableView->setModel(filesModel);
- ui->tableView->setItemDelegateForColumn(MissingFilesColumn::OriginalPath,
- new MissingFilesPathItemDelegate(false, ""));
- ui->tableView->setItemDelegateForColumn(MissingFilesColumn::NewPath,
- new MissingFilesPathItemDelegate(true, ""));
- ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch);
- ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::Source,
- QHeaderView::ResizeMode::ResizeToContents);
- ui->tableView->horizontalHeader()->setMaximumSectionSize(width() / 3);
- ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::State,
- QHeaderView::ResizeMode::ResizeToContents);
- ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged);
- ui->warningIcon->setPixmap(filesModel->warningIcon.pixmap(QSize(32, 32)));
- for (size_t i = 0; i < obs_missing_files_count(files); i++) {
- obs_missing_file_t *f = obs_missing_files_get_file(files, (int)i);
- const char *oldPath = obs_missing_file_get_path(f);
- const char *name = obs_missing_file_get_source_name(f);
- addMissingFile(oldPath, name);
- }
- QString found = QTStr("MissingFiles.NumFound").arg("0", QString::number(obs_missing_files_count(files)));
- ui->found->setText(found);
- fileStore = files;
- connect(ui->doneButton, &QPushButton::clicked, this, &OBSMissingFiles::saveFiles);
- connect(ui->browseButton, &QPushButton::clicked, this, &OBSMissingFiles::browseFolders);
- connect(ui->cancelButton, &QPushButton::clicked, this, &OBSMissingFiles::close);
- connect(filesModel, &MissingFilesModel::dataChanged, this, &OBSMissingFiles::dataChanged);
- QModelIndex index = filesModel->createIndex(0, 1);
- QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection,
- Q_ARG(const QModelIndex &, index));
- }
- OBSMissingFiles::~OBSMissingFiles()
- {
- obs_missing_files_destroy(fileStore);
- }
- void OBSMissingFiles::addMissingFile(const char *originalPath, const char *sourceName)
- {
- QStringList list;
- list.append(originalPath);
- list.append(sourceName);
- QModelIndex insertIndex = filesModel->index(filesModel->rowCount() - 1, MissingFilesColumn::Source);
- filesModel->setData(insertIndex, list, MissingFilesRole::NewPathsToProcessRole);
- }
- void OBSMissingFiles::saveFiles()
- {
- for (int i = 0; i < filesModel->files.length(); i++) {
- MissingFilesState state = filesModel->files[i].state;
- if (state != MissingFilesState::Missing) {
- obs_missing_file_t *f = obs_missing_files_get_file(fileStore, i);
- QString path = filesModel->files[i].newPath;
- if (state == MissingFilesState::Cleared) {
- obs_missing_file_issue_callback(f, "");
- } else {
- char *p = bstrdup(path.toStdString().c_str());
- obs_missing_file_issue_callback(f, p);
- bfree(p);
- }
- }
- }
- QDialog::accept();
- }
- void OBSMissingFiles::browseFolders()
- {
- QString dir = QFileDialog::getExistingDirectory(this, QTStr("MissingFiles.SelectDir"), "",
- QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
- if (dir != "") {
- dir += "/";
- filesModel->fileCheckLoop(filesModel->files, dir, true);
- }
- }
- void OBSMissingFiles::dataChanged()
- {
- QString found =
- QTStr("MissingFiles.NumFound")
- .arg(QString::number(filesModel->found()), QString::number(obs_missing_files_count(fileStore)));
- ui->found->setText(found);
- ui->tableView->resizeColumnToContents(MissingFilesColumn::State);
- ui->tableView->resizeColumnToContents(MissingFilesColumn::Source);
- }
- QIcon OBSMissingFiles::GetWarningIcon()
- {
- return filesModel->warningIcon;
- }
- void OBSMissingFiles::SetWarningIcon(const QIcon &icon)
- {
- ui->warningIcon->setPixmap(icon.pixmap(QSize(32, 32)));
- filesModel->warningIcon = icon;
- }
|