0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-19 20:32:15 +02:00
obs-studio/UI/window-missing-files.cpp
PatTheMav 710d99ef4d UI: Improve incremental compile times via explicit file includes
When a source file contains an explicit include with a filename
following the "moc_<actual-filename>.cpp" pattern, then CMake's
AUTOMOC generation tool will recognize the matching pair and generate
the replacement header file and add the required include directory
entries.

For all files which do contain Q_OBJECT or similar declarations but do
not have an explicit include directive, the global mocs_compilation.cpp
file will still be generated (which groups all "missing" generated
headers).

The larger this global file is, the more expensive incremental
compilation will be as this file (and all its contained generated
headers) will be re-generated regardless of whether actual changes
occurred.
2024-08-22 16:45:12 -04:00

603 lines
16 KiB
C++

/******************************************************************************
Copyright (C) 2019 by Dillon Pentz <dillon@vodbox.io>
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;
}