123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- /******************************************************************************
- Copyright (C) 2019-2020 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 "window-importer.hpp"
- #include "obs-app.hpp"
- #include <QPushButton>
- #include <QLineEdit>
- #include <QToolButton>
- #include <QMimeData>
- #include <QStyledItemDelegate>
- #include <QDirIterator>
- #include <QDropEvent>
- #include "qt-wrappers.hpp"
- #include "importers/importers.hpp"
- extern bool SceneCollectionExists(const char *findName);
- enum ImporterColumn {
- Selected,
- Name,
- Path,
- Program,
- Count
- };
- enum ImporterEntryRole {
- EntryStateRole = Qt::UserRole,
- NewPath,
- AutoPath,
- CheckEmpty
- };
- /**********************************************************
- Delegate - Presents cells in the grid.
- **********************************************************/
- ImporterEntryPathItemDelegate::ImporterEntryPathItemDelegate()
- : QStyledItemDelegate()
- {
- }
- QWidget *ImporterEntryPathItemDelegate::createEditor(
- QWidget *parent, const QStyleOptionViewItem & /* option */,
- const QModelIndex &index) const
- {
- bool empty = index.model()
- ->index(index.row(), ImporterColumn::Path)
- .data(ImporterEntryRole::CheckEmpty)
- .value<bool>();
- QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum,
- QSizePolicy::Policy::Expanding,
- QSizePolicy::ControlType::PushButton);
- QWidget *container = new QWidget(parent);
- auto browseCallback = [this, container]() {
- const_cast<ImporterEntryPathItemDelegate *>(this)->handleBrowse(
- container);
- };
- auto clearCallback = [this, container]() {
- const_cast<ImporterEntryPathItemDelegate *>(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);
- QObject::connect(text, SIGNAL(editingFinished()), this,
- SLOT(updateText()));
- 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 output cells
- // or the insertion point's input cell.
- if (!empty) {
- 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 ImporterEntryPathItemDelegate::setEditorData(
- QWidget *editor, const QModelIndex &index) const
- {
- QLineEdit *text = editor->findChild<QLineEdit *>();
- text->setText(index.data().toString());
- editor->setProperty(PATH_LIST_PROP, QVariant());
- }
- void ImporterEntryPathItemDelegate::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();
- model->setData(index, list, ImporterEntryRole::NewPath);
- } else {
- QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
- model->setData(index, lineEdit->text());
- }
- }
- void ImporterEntryPathItemDelegate::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 ImporterEntryPathItemDelegate::handleBrowse(QWidget *container)
- {
- QString Pattern = "(*.json *.bpres *.xml *.xconfig)";
- QLineEdit *text = container->findChild<QLineEdit *>();
- QString currentPath = text->text();
- bool isSet = false;
- QStringList paths = OpenFiles(
- container, QTStr("Importer.SelectCollection"), currentPath,
- QTStr("Importer.Collection") + QString(" ") + Pattern);
- if (!paths.empty()) {
- container->setProperty(PATH_LIST_PROP, paths);
- isSet = true;
- }
- if (isSet)
- emit commitData(container);
- }
- void ImporterEntryPathItemDelegate::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());
- emit commitData(container);
- }
- void ImporterEntryPathItemDelegate::updateText()
- {
- QLineEdit *lineEdit = dynamic_cast<QLineEdit *>(sender());
- QWidget *editor = lineEdit->parentWidget();
- emit commitData(editor);
- }
- /**
- Model
- **/
- int ImporterModel::rowCount(const QModelIndex &) const
- {
- return options.length() + 1;
- }
- int ImporterModel::columnCount(const QModelIndex &) const
- {
- return ImporterColumn::Count;
- }
- QVariant ImporterModel::data(const QModelIndex &index, int role) const
- {
- QVariant result = QVariant();
- if (index.row() >= options.length()) {
- if (role == ImporterEntryRole::CheckEmpty)
- result = true;
- else
- return QVariant();
- } else if (role == Qt::DisplayRole) {
- switch (index.column()) {
- case ImporterColumn::Path:
- result = options[index.row()].path;
- break;
- case ImporterColumn::Program:
- result = options[index.row()].program;
- break;
- case ImporterColumn::Name:
- result = options[index.row()].name;
- }
- } else if (role == Qt::EditRole) {
- if (index.column() == ImporterColumn::Name) {
- result = options[index.row()].name;
- }
- } else if (role == Qt::CheckStateRole) {
- switch (index.column()) {
- case ImporterColumn::Selected:
- if (options[index.row()].program != "")
- result = options[index.row()].selected
- ? Qt::Checked
- : Qt::Unchecked;
- else
- result = Qt::Unchecked;
- }
- } else if (role == ImporterEntryRole::CheckEmpty) {
- result = options[index.row()].empty;
- }
- return result;
- }
- Qt::ItemFlags ImporterModel::flags(const QModelIndex &index) const
- {
- Qt::ItemFlags flags = QAbstractTableModel::flags(index);
- if (index.column() == ImporterColumn::Selected &&
- index.row() != options.length()) {
- flags |= Qt::ItemIsUserCheckable;
- } else if (index.column() == ImporterColumn::Path ||
- (index.column() == ImporterColumn::Name &&
- index.row() != options.length())) {
- flags |= Qt::ItemIsEditable;
- }
- return flags;
- }
- void ImporterModel::checkInputPath(int row)
- {
- ImporterEntry &entry = options[row];
- if (entry.path.isEmpty()) {
- entry.program = "";
- entry.empty = true;
- entry.selected = false;
- entry.name = "";
- } else {
- entry.empty = false;
- std::string program = DetectProgram(entry.path.toStdString());
- entry.program = QTStr(program.c_str());
- if (program.empty()) {
- entry.selected = false;
- } else {
- std::string name =
- GetSCName(entry.path.toStdString(), program);
- entry.name = name.c_str();
- }
- }
- emit dataChanged(index(row, 0), index(row, ImporterColumn::Count));
- }
- bool ImporterModel::setData(const QModelIndex &index, const QVariant &value,
- int role)
- {
- if (role == ImporterEntryRole::NewPath) {
- QStringList list = value.toStringList();
- if (list.size() == 0) {
- if (index.row() < options.size()) {
- beginRemoveRows(QModelIndex(), index.row(),
- index.row());
- options.removeAt(index.row());
- endRemoveRows();
- }
- } else {
- if (list.size() > 0 && index.row() < options.length()) {
- options[index.row()].path = list[0];
- checkInputPath(index.row());
- list.removeAt(0);
- }
- if (list.size() > 0) {
- int row = index.row();
- int lastRow = row + list.size() - 1;
- beginInsertRows(QModelIndex(), row, lastRow);
- for (QString path : list) {
- ImporterEntry entry;
- entry.path = path;
- options.insert(row, entry);
- row++;
- }
- endInsertRows();
- for (row = index.row(); row <= lastRow; row++) {
- checkInputPath(row);
- }
- }
- }
- } else if (index.row() == options.length()) {
- QString path = value.toString();
- if (!path.isEmpty()) {
- ImporterEntry entry;
- entry.path = path;
- entry.selected = role != ImporterEntryRole::AutoPath;
- entry.empty = false;
- beginInsertRows(QModelIndex(), options.length() + 1,
- options.length() + 1);
- options.append(entry);
- endInsertRows();
- checkInputPath(index.row());
- }
- } else if (index.column() == ImporterColumn::Selected) {
- bool select = value.toBool();
- options[index.row()].selected = select;
- } else if (index.column() == ImporterColumn::Path) {
- QString path = value.toString();
- options[index.row()].path = path;
- checkInputPath(index.row());
- } else if (index.column() == ImporterColumn::Name) {
- QString name = value.toString();
- options[index.row()].name = name;
- }
- emit dataChanged(index, index);
- return true;
- }
- QVariant ImporterModel::headerData(int section, Qt::Orientation orientation,
- int role) const
- {
- QVariant result = QVariant();
- if (role == Qt::DisplayRole &&
- orientation == Qt::Orientation::Horizontal) {
- switch (section) {
- case ImporterColumn::Path:
- result = QTStr("Importer.Path");
- break;
- case ImporterColumn::Program:
- result = QTStr("Importer.Program");
- break;
- case ImporterColumn::Name:
- result = QTStr("Name");
- }
- }
- return result;
- }
- /**
- Window
- **/
- OBSImporter::OBSImporter(QWidget *parent)
- : QDialog(parent),
- optionsModel(new ImporterModel),
- ui(new Ui::OBSImporter)
- {
- setAcceptDrops(true);
- setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
- ui->setupUi(this);
- ui->tableView->setModel(optionsModel);
- ui->tableView->setItemDelegateForColumn(
- ImporterColumn::Path, new ImporterEntryPathItemDelegate());
- ui->tableView->horizontalHeader()->setSectionResizeMode(
- QHeaderView::ResizeMode::ResizeToContents);
- ui->tableView->horizontalHeader()->setSectionResizeMode(
- ImporterColumn::Path, QHeaderView::ResizeMode::Stretch);
- connect(optionsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
- this, SLOT(dataChanged()));
- ui->tableView->setEditTriggers(
- QAbstractItemView::EditTrigger::CurrentChanged);
- ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Import"));
- ui->buttonBox->button(QDialogButtonBox::Open)->setText(QTStr("Add"));
- connect(ui->buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
- this, SLOT(importCollections()));
- connect(ui->buttonBox->button(QDialogButtonBox::Open),
- SIGNAL(clicked()), this, SLOT(browseImport()));
- connect(ui->buttonBox->button(QDialogButtonBox::Close),
- SIGNAL(clicked()), this, SLOT(close()));
- ImportersInit();
- bool autoSearchPrompt = config_get_bool(App()->GlobalConfig(),
- "General", "AutoSearchPrompt");
- if (!autoSearchPrompt) {
- QMessageBox::StandardButton button = OBSMessageBox::question(
- parent, QTStr("Importer.AutomaticCollectionPrompt"),
- QTStr("Importer.AutomaticCollectionText"));
- if (button == QMessageBox::Yes) {
- config_set_bool(App()->GlobalConfig(), "General",
- "AutomaticCollectionSearch", true);
- } else {
- config_set_bool(App()->GlobalConfig(), "General",
- "AutomaticCollectionSearch", false);
- }
- config_set_bool(App()->GlobalConfig(), "General",
- "AutoSearchPrompt", true);
- }
- bool autoSearch = config_get_bool(App()->GlobalConfig(), "General",
- "AutomaticCollectionSearch");
- OBSImporterFiles f;
- if (autoSearch)
- f = ImportersFindFiles();
- for (size_t i = 0; i < f.size(); i++) {
- QString path = f[i].c_str();
- path.replace("\\", "/");
- addImportOption(path, true);
- }
- f.clear();
- ui->tableView->resizeColumnsToContents();
- QModelIndex index =
- optionsModel->createIndex(optionsModel->rowCount() - 1, 2);
- QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex",
- Qt::QueuedConnection,
- Q_ARG(const QModelIndex &, index));
- }
- void OBSImporter::addImportOption(QString path, bool automatic)
- {
- QStringList list;
- list.append(path);
- QModelIndex insertIndex = optionsModel->index(
- optionsModel->rowCount() - 1, ImporterColumn::Path);
- optionsModel->setData(insertIndex, list,
- automatic ? ImporterEntryRole::AutoPath
- : ImporterEntryRole::NewPath);
- }
- void OBSImporter::dropEvent(QDropEvent *ev)
- {
- for (QUrl url : ev->mimeData()->urls()) {
- QFileInfo fileInfo(url.toLocalFile());
- if (fileInfo.isDir()) {
- QDirIterator dirIter(fileInfo.absoluteFilePath(),
- QDir::Files);
- while (dirIter.hasNext()) {
- addImportOption(dirIter.next(), false);
- }
- } else {
- addImportOption(fileInfo.canonicalFilePath(), false);
- }
- }
- }
- void OBSImporter::dragEnterEvent(QDragEnterEvent *ev)
- {
- if (ev->mimeData()->hasUrls())
- ev->accept();
- }
- void OBSImporter::browseImport()
- {
- QString Pattern = "(*.json *.bpres *.xml *.xconfig)";
- QStringList paths = OpenFiles(
- this, QTStr("Importer.SelectCollection"), "",
- QTStr("Importer.Collection") + QString(" ") + Pattern);
- if (!paths.empty()) {
- for (int i = 0; i < paths.count(); i++) {
- addImportOption(paths[i], false);
- }
- }
- }
- bool GetUnusedName(std::string &name)
- {
- if (!SceneCollectionExists(name.c_str()))
- return false;
- std::string newName;
- int inc = 2;
- do {
- newName = name;
- newName += " ";
- newName += std::to_string(inc++);
- } while (SceneCollectionExists(newName.c_str()));
- name = newName;
- return true;
- }
- void OBSImporter::importCollections()
- {
- setEnabled(false);
- char dst[512];
- GetConfigPath(dst, 512, "obs-studio/basic/scenes/");
- for (int i = 0; i < optionsModel->rowCount() - 1; i++) {
- int selected = optionsModel->index(i, ImporterColumn::Selected)
- .data(Qt::CheckStateRole)
- .value<int>();
- if (selected == Qt::Unchecked)
- continue;
- std::string pathStr =
- optionsModel->index(i, ImporterColumn::Path)
- .data(Qt::DisplayRole)
- .value<QString>()
- .toStdString();
- std::string nameStr =
- optionsModel->index(i, ImporterColumn::Name)
- .data(Qt::DisplayRole)
- .value<QString>()
- .toStdString();
- json11::Json res;
- ImportSC(pathStr, nameStr, res);
- if (res != json11::Json()) {
- json11::Json::object out = res.object_items();
- std::string name = res["name"].string_value();
- std::string file;
- if (GetUnusedName(name)) {
- json11::Json::object newOut = out;
- newOut["name"] = name;
- out = newOut;
- }
- GetUnusedSceneCollectionFile(name, file);
- std::string save = dst;
- save += "/";
- save += file;
- save += ".json";
- std::string out_str = json11::Json(out).dump();
- bool success = os_quick_write_utf8_file(save.c_str(),
- out_str.c_str(),
- out_str.size(),
- false);
- blog(LOG_INFO, "Import Scene Collection: %s (%s) - %s",
- name.c_str(), file.c_str(),
- success ? "SUCCESS" : "FAILURE");
- }
- }
- close();
- }
- void OBSImporter::dataChanged()
- {
- ui->tableView->resizeColumnToContents(ImporterColumn::Name);
- }
|