diff --git a/CMakeLists.txt b/CMakeLists.txt index 11fd132..0922a47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,8 +68,8 @@ endif() target_link_libraries(${PROJECT_NAME} PRIVATE OBS::${OBS_FRONTEND_API_NAME}) -find_package(Qt6 COMPONENTS Widgets Core) -target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets) +find_package(Qt6 COMPONENTS Widgets Core Concurrent) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets Qt6::Concurrent) target_compile_options(${PROJECT_NAME} PRIVATE $<$:-Wno-quoted-include-in-framework-header -Wno-comma>) @@ -88,6 +88,8 @@ target_sources( src/quick-access-dock.hpp src/quick-access.cpp src/quick-access.hpp + src/quick-access-source.cpp + src/quick-access-source.hpp src/version.h) target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") diff --git a/data/images/magnifying-glass-solid-white.svg b/data/images/magnifying-glass-solid-white.svg new file mode 100644 index 0000000..408e98e --- /dev/null +++ b/data/images/magnifying-glass-solid-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/images/magnifying-glass-solid.svg b/data/images/magnifying-glass-solid.svg new file mode 100644 index 0000000..929198a --- /dev/null +++ b/data/images/magnifying-glass-solid.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/quick-access-dock.cpp b/src/quick-access-dock.cpp index 1df7119..049e2a6 100644 --- a/src/quick-access-dock.cpp +++ b/src/quick-access-dock.cpp @@ -1,67 +1,149 @@ #include "quick-access-dock.hpp" +#include "quick-access-utility.hpp" #include "quick-access.hpp" #include #include #include #include #include +#include +#include +#include +#include #include "version.h" -QuickAccessDock::QuickAccessDock(QWidget *parent, obs_data_t *obsData) - : QWidget(parent) +extern QuickAccessUtility *qau; + +const std::vector SearchTypes{SearchType::Source, SearchType::Type, + SearchType::File, SearchType::Url, + SearchType::Filters}; +const std::map SearchTypeNames{ + {SearchType::Source, "Source"}, + {SearchType::Type, "Source Type"}, + {SearchType::File, "File Path"}, + {SearchType::Url, "URL"}, + {SearchType::Filters, "Filters"}}; + +bool operator==(const SourceVisibility &lhs, const QuickAccessSource *rhs) { - const auto mainWindow = - static_cast(obs_frontend_get_main_window()); + return lhs.source == rhs; +} +QuickAccessDock::QuickAccessDock(QWidget *parent, obs_data_t *obsData, + bool modal) + : QFrame(parent), + _modal(modal) +{ _dockName = obs_data_get_string(obsData, "dock_name"); _dockType = obs_data_get_string(obsData, "dock_type"); _dockId = obs_data_get_string(obsData, "dock_id"); + _dockId = obs_data_get_string(obsData, "dock_id"); _showProperties = obs_data_get_bool(obsData, "show_properties"); _showFilters = obs_data_get_bool(obsData, "show_filters"); _showScenes = obs_data_get_bool(obsData, "show_scenes"); _clickableScenes = obs_data_get_bool(obsData, "clickable_scenes"); + InitializeSearch(); + + if (_dockType == "Manual") { + obs_data_array_t *items = + obs_data_get_array(obsData, "dock_sources"); + auto numItems = obs_data_array_count(items); + for (size_t i = 0; i < numItems; i++) { + auto item = obs_data_array_item(items, i); + auto sourceName = + obs_data_get_string(item, "source_name"); + auto source = obs_get_source_by_name(sourceName); + if (!source) { + continue; + } + std::string id = obs_source_get_uuid(source); + _sources.push_back(qau->GetSource(id)); + obs_source_release(source); + obs_data_release(item); + } + std::vector sourceStates; + for (auto &source : _sources) { + sourceStates.push_back({source, nullptr, true}); + } + _displayGroups.push_back({"Manual", SearchType::None, nullptr, + false, sourceStates, _sources}); + obs_data_array_release(items); + for (auto &source : _sources) { + source->addDock(this); + } + } else if (_dockType == "Dynamic") { + _currentScene = qau->GetCurrentScene(); + UpdateDynamicDock(false); + } + + if (!_modal) { + DrawDock(obsData); + } + _widget = new QuickAccess(this, this, "quick_access_widget"); + setMinimumWidth(200); auto l = new QVBoxLayout; l->setContentsMargins(0, 0, 0, 0); l->addWidget(_widget); setLayout(l); - _widget->Load(obsData); - if (!_dockWidget) { - _InitializeDockWidget(); - } - const auto d = static_cast(parentWidget()); - if (obs_data_get_bool(obsData, "dock_hidden")) { - d->hide(); - } else { - d->show(); - } + _ready = true; +} - const auto floating = obs_data_get_bool(obsData, "dock_floating"); - if (d->isFloating() != floating) { - d->setFloating(floating); +QuickAccessDock::~QuickAccessDock() +{ + blog(LOG_INFO, "QuickAccessDock::~QuickAccessDock()"); + _ClearSources(); + if (_dockWidget) { + delete _dockWidget; } +} - const auto area = static_cast( - obs_data_get_int(obsData, "dock_area")); - if (area != mainWindow->dockWidgetArea(d)) { - mainWindow->addDockWidget(area, d); +void QuickAccessDock::InitializeSearch() +{ + if (_dockType != "Source Search") { + return; + } + _sources = qau->GetAllSources(); + std::sort(_sources.begin(), _sources.end(), + [](QuickAccessSource *a, QuickAccessSource *b) { + return a->getName() < b->getName(); + }); + std::vector sources; + for (auto &source : _sources) { + source->addDock(this); + sources.push_back({source, nullptr, false}); + } + _displayGroups.clear(); + _indexer.clear(); + size_t i = 0; + for (auto &st : SearchTypes) { + _displayGroups.push_back({SearchTypeNames.at(st), st, nullptr, + false, sources, _sources}); + _indexer[st] = i; + i++; } +} - const char *geometry = obs_data_get_string(obsData, "dock_geometry"); - if (geometry && strlen(geometry)) { - d->restoreGeometry( - QByteArray::fromBase64(QByteArray(geometry))); +void QuickAccessDock::SetCurrentScene(QuickAccessSource *currentScene) +{ + _currentScene = currentScene; + if (!_ready) { + return; + } + blog(LOG_INFO, "QuickAccessDock::SetCurrentScene"); + if (_dockType == "Dynamic") { + UpdateDynamicDock(true); } + // Set up dynamic dock here. } -QuickAccessDock::~QuickAccessDock() +void QuickAccessDock::SearchFocus() { - blog(LOG_INFO, "QuickAccessDock::~QuickAccessDock()"); - if (_dockWidget) { - delete _dockWidget; + if (_widget) { + _widget->SearchFocus(); } } @@ -74,43 +156,79 @@ void QuickAccessDock::SetName(std::string name) } } -void QuickAccessDock::CleanupSourceHandlers() +void QuickAccessDock::CleanupSourceHandlers() {} + +void QuickAccessDock::SetItemsButtonVisibility() { if (_widget) { - _widget->CleanupSourceHandlers(); + _widget->SetItemsButtonVisibility(); } } -void QuickAccessDock::SetItemsButtonVisibility() +void QuickAccessDock::DismissModal() { - if (_widget) { - _widget->SetItemsButtonVisibility(); + if (_modal) { + auto modal = + dynamic_cast(parentWidget()); + modal->close(); } } +void QuickAccessDock::_ClearSources() +{ + for (auto &source : _sources) { + source->removeDock(this); + } + _sources.clear(); +} + void QuickAccessDock::Load(obs_data_t *obsData, bool created) { UNUSED_PARAMETER(created); + DrawDock(obsData); +} + +void QuickAccessDock::Search(std::string searchTerm) +{ + for (auto &source : _sources) { + source->BuildSearchTerms(); + } + for (auto &dg : _displayGroups) { + dg.headerVisible = false; + for (auto &source : dg.sources) { + source.visible = false; + } + } + size_t i = 0; + for (auto &source : _sources) { + for (auto &st : source->search(searchTerm)) { + _displayGroups[_indexer[st]].sources[i].visible = true; + _displayGroups[_indexer[st]].headerVisible = true; + } + i++; + } + if (_widget) { + QMetaObject::invokeMethod( + QCoreApplication::instance()->thread(), + [this]() { _widget->UpdateVisibility(); }); + } +} + +void QuickAccessDock::DrawDock(obs_data_t *obsData) +{ const auto mainWindow = static_cast(obs_frontend_get_main_window()); - _dockName = obs_data_get_string(obsData, "dock_name"); - _dockType = obs_data_get_string(obsData, "dock_type"); - _dockId = obs_data_get_string(obsData, "dock_id"); - _showProperties = obs_data_get_bool(obsData, "show_properties"); - _showFilters = obs_data_get_bool(obsData, "show_filters"); - _showScenes = obs_data_get_bool(obsData, "show_scenes"); - _clickableScenes = obs_data_get_bool(obsData, "clickable_scenes"); + obs_frontend_add_dock_by_id( + ("quick-access-dock_" + this->_dockId).c_str(), + this->_dockName.c_str(), this); + _dockInjected = true; - _widget->Load(obsData); - if (!_dockWidget) { - _InitializeDockWidget(); - } const auto d = static_cast(parentWidget()); - if (obs_data_get_bool(obsData, "dock_hidden")) { - d->hide(); - } else { - d->show(); + + const auto floating = obs_data_get_bool(obsData, "dock_floating"); + if (d->isFloating() != floating) { + d->setFloating(floating); } const auto area = static_cast( @@ -119,43 +237,22 @@ void QuickAccessDock::Load(obs_data_t *obsData, bool created) mainWindow->addDockWidget(area, d); } - const auto floating = obs_data_get_bool(obsData, "dock_floating"); - if (d->isFloating() != floating) { - d->setFloating(floating); - } - const char *geometry = obs_data_get_string(obsData, "dock_geometry"); if (geometry && strlen(geometry)) { d->restoreGeometry( QByteArray::fromBase64(QByteArray(geometry))); } -} -void QuickAccessDock::_InitializeDockWidget() -{ -#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0) - obs_frontend_add_dock_by_id( - ("quick-access-dock_" + this->_dockId).c_str(), - this->_dockName.c_str(), this); -#else - _dockWidget = new QDockWidget( - static_cast(obs_frontend_get_main_window())); - _dockWidget->setObjectName( - ("quick-access-dock_" + this->_dockId).c_str()); - _dockWidget->setWindowTitle(this->_dockName.c_str()); - _dockWidget->setWidget(this); - _dockWidget->setFeatures(QDockWidget::DockWidgetClosable | - QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - _dockWidget->setFloating(true); - _dockWidget->hide(); - obs_frontend_add_dock(_dockWidget); -#endif - _dockInjected = true; + if (obs_data_get_bool(obsData, "dock_hidden")) { + d->hide(); + } else { + d->show(); + } } void QuickAccessDock::Save(obs_data_t *obsData) { + std::unique_lock lock(_m); const auto mainWindow = static_cast(obs_frontend_get_main_window()); auto docks = obs_data_get_array(obsData, "docks"); @@ -186,41 +283,159 @@ void QuickAccessDock::Save(obs_data_t *obsData) obs_data_set_bool(dockData, "dock_floating", ((QDockWidget *)parentWidget())->isFloating()); - _widget->Save(dockData); + if (_dockType == "Manual" && _displayGroups.size() != 0) { + auto itemsArr = obs_data_array_create(); + for (auto &source : _displayGroups[0].sources2) { + auto itemObj = obs_data_create(); + source->save(itemObj); + obs_data_array_push_back(itemsArr, itemObj); + obs_data_release(itemObj); + } + obs_data_set_array(dockData, "dock_sources", itemsArr); + obs_data_array_release(itemsArr); + } + obs_data_array_push_back(docks, dockData); obs_data_release(dockData); obs_data_array_release(docks); } -void QuickAccessDock::SourceCreated(obs_source_t *source) +void QuickAccessDock::SourceCreated(QuickAccessSource *source) { - UNUSED_PARAMETER(source); - if (!_widget) { - return; - } if (_dockType == "Source Search") { - _widget->LoadAllSources(); + AddSource(source); + } +} + +void QuickAccessDock::SourceDestroyed(QuickAccessSource *source) +{ + if (_dockType == "Search Source" || _dockType == "Manual") { + RemoveSource(source); } + // TODO: Handle Dynamic Dock. } -void QuickAccessDock::SourceDestroyed() +void QuickAccessDock::SourceUpdate() {} + +void QuickAccessDock::SourceRename(QuickAccessSource *source) { + UNUSED_PARAMETER(source); // Dynamic sources are handled from scene item add/delete events if (_switchingSC || !_widget) { return; } + _widget->Redraw(); +} + +void QuickAccessDock::RemoveSource(QuickAccessSource *source, bool removeDock) +{ + std::unique_lock lock(_m); + for (auto &group : _displayGroups) { + auto it = std::find(group.sources.begin(), group.sources.end(), + source); + if (it != group.sources.end()) { + _widget->RemoveSource(source, group.name); + group.sources.erase(it); + } + } + if (auto it = std::find(_sources.begin(), _sources.end(), source); + it != _sources.end()) { + _sources.erase(it); + } + if (removeDock) { + source->removeDock(this); + } +} + +void QuickAccessDock::AddSource(QuickAccessSource *source, int index) +{ if (_dockType == "Manual") { - _widget->RemoveNullSources(); - } else if (_dockType != "Dynamic") { - _widget->LoadAllSources(); + if (std::find(_sources.begin(), _sources.end(), source) != + _sources.end()) { + return; + } + if (index == -1) { + _sources.push_back(source); + _displayGroups[0].sources.push_back( + {source, nullptr, true}); + } else { + _sources.insert(_sources.begin() + index, source); + _displayGroups[0].sources.insert( + _displayGroups[0].sources.begin() + index, + {source, nullptr, true}); + } + _widget->AddSource(source, _displayGroups[0].name); + source->addDock(this); + } else if (_dockType == "Source Search") { + auto sIt = _sources.begin(); + while (sIt != _sources.end()) { + if ((*sIt)->getName() > source->getName()) { + break; + } + sIt++; + } + + // Add the source to the dock's raw source list. + _sources.insert(sIt, source); + + // Then add to each of the search display groups. + for (auto &dg : _displayGroups) { + auto it = dg.sources.begin(); + + // Find location to insert + while (it != dg.sources.end()) { + if (it->source->getName() > source->getName()) { + break; + } + it++; + } + //int index = it - dg.sources.begin(); + dg.sources.insert(it, {source, nullptr, false}); + _widget->AddSource(source, dg.name); + } + source->addDock(this); } } -void QuickAccessDock::SourceRename(obs_source_t *source) +void QuickAccessDock::UpdateDynamicDock(bool updateWidget) { - // Dynamic sources are handled from scene item add/delete events - if (_switchingSC || !_widget) { - return; + _ClearSources(); + if (_currentScene) { + // _currentScene is not set up with links to children. + // so grab the fully populated version from qau->GetSource + /*QuickAccessSource *cur = + qau->GetSource(_currentScene->getUUID());*/ + //_AddToDynDock(_currentScene); + _sources = qau->GetCurrentSceneSources(); + for (auto &source : _sources) { + source->addDock(this); + } + } + _displayGroups.clear(); + //_displayGroups.push_back({ "DSK" }); + std::vector sourceStates; + for (auto &source : _sources) { + sourceStates.push_back({source, nullptr, true}); + } + _displayGroups.push_back({"Scene", SearchType::None, nullptr, true, + sourceStates, _sources}); + if (updateWidget) { + QMetaObject::invokeMethod( + QCoreApplication::instance()->thread(), + [this]() { _widget->Load(); }); + } +} + +void QuickAccessDock::_AddToDynDock(QuickAccessSource *scene) +{ + for (auto &source : scene->children()) { + if (std::find(_sources.begin(), _sources.end(), source) == + _sources.end()) { + _sources.push_back(source); + source->addDock(this); + if (source->sourceType() != SourceClass::Source) { + _AddToDynDock(source); + } + } } - _widget->SourceRename(source); } diff --git a/src/quick-access-dock.hpp b/src/quick-access-dock.hpp index 344b4f7..f8703b9 100644 --- a/src/quick-access-dock.hpp +++ b/src/quick-access-dock.hpp @@ -4,19 +4,48 @@ #include #include +#include +#include #include -#include "quick-access.hpp" +#include +#include +#include +#include + +//#include "quick-access.hpp" +#include "quick-access-source.hpp" +class QuickAccess; typedef const char *(*translateFunc)(const char *); +class QuickAccessSource; + +struct SourceVisibility { + QuickAccessSource *source; + QListWidgetItem *listItem; + bool visible; +}; + +bool operator==(const SourceVisibility &lhs, const QuickAccessSource *rhs); + +struct QuickAccessItemGroup { + std::string name; + SearchType searchType; + QListWidgetItem *headerItem; + bool headerVisible; + std::vector sources; + std::vector sources2; +}; -class QuickAccessDock : public QWidget { +class QuickAccessDock : public QFrame { Q_OBJECT public: - QuickAccessDock(QWidget *parent, obs_data_t *obsData); + QuickAccessDock(QWidget *parent, obs_data_t *obsData, + bool modal = false); ~QuickAccessDock(); void Load(obs_data_t *obsData, bool created = false); void Save(obs_data_t *obsData); + void InitializeSearch(); inline std::string GetName() { return _dockName; } inline std::string GetType() { return _dockType; } inline std::string GetId() { return _dockId; } @@ -28,28 +57,47 @@ class QuickAccessDock : public QWidget { inline void SetFilters(bool on) { _showFilters = on; } inline void SetScenes(bool on) { _showScenes = on; } inline void SetClickableScenes(bool on) { _clickableScenes = on; } + void SetCurrentScene(QuickAccessSource *currentScene); void SetName(std::string name); inline QDockWidget *GetDockWidget() { return _dockWidget; } void SwitchingSceneCollections(bool state) { _switchingSC = state; } + inline std::vector Sources() { return _sources; } + inline auto &DisplayGroups() { return _displayGroups; } void SetItemsButtonVisibility(); - void SourceCreated(obs_source_t *source); - void SourceDestroyed(); - void SourceRename(obs_source_t *source); + void SourceCreated(QuickAccessSource *source); + void SourceDestroyed(QuickAccessSource *source); + void SourceUpdate(); + void SourceRename(QuickAccessSource *source); void CleanupSourceHandlers(); + void RemoveSource(QuickAccessSource *source, bool removeDock = true); + void AddSource(QuickAccessSource *source, int index = -1); + void UpdateDynamicDock(bool updateWidget = true); + void Search(std::string searchTerm); + void SearchFocus(); + void DismissModal(); + void DrawDock(obs_data_t *obsData); private: - void _InitializeDockWidget(); + void _ClearSources(); + void _AddToDynDock(QuickAccessSource *source); QDockWidget *_dockWidget = nullptr; QuickAccess *_widget = nullptr; + std::vector _sources; + std::vector _displayGroups; + std::mutex _m; std::string _dockName; std::string _dockType; std::string _dockId; + QuickAccessSource *_currentScene; bool _showProperties = false; bool _showFilters = false; bool _showScenes = false; bool _clickableScenes = false; bool _dockInjected = false; bool _switchingSC = false; + bool _modal = false; + bool _ready = false; + std::map _indexer; }; diff --git a/src/quick-access-source.cpp b/src/quick-access-source.cpp new file mode 100644 index 0000000..20cb21f --- /dev/null +++ b/src/quick-access-source.cpp @@ -0,0 +1,743 @@ +#include "quick-access-source.hpp" +#include "quick-access-utility.hpp" +#include "quick-access-dock.hpp" +#include "version.h" + +#include +#include +#include +#include +#include + +#define BROWSER_SOURCE_ID "browser_source" +#define IMAGE_SOURCE_ID "image_source" +#define MEDIA_SOURCE_ID "ffmpeg_source" + +#define START_LOC 35 +#define INC 30 + +extern QuickAccessUtility *qau; +const std::vector SearchTypes{SearchType::Source, SearchType::Type, + SearchType::File, SearchType::Url, + SearchType::Filters}; +const std::map SearchTypeNames{ + {SearchType::Source, "Source"}, + {SearchType::Type, "Source Type"}, + {SearchType::File, "File Path"}, + {SearchType::Url, "URL"}, + {SearchType::Filters, "Filters"}}; + +bool QuickAccessSource::registered = false; + +QuickAccessSourceDelegate::QuickAccessSourceDelegate(QObject *parent, + QuickAccessDock *dock) + : QStyledItemDelegate(parent), + _dock(dock) +{ + _propertiesState = QStyle::State_Enabled; + _filtersState = QStyle::State_Enabled; + _parentScenesState = QStyle::State_Enabled; +} + +void QuickAccessSourceDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const auto model = + static_cast(index.model()); + auto item = model->item(index.row()); + std::string mode = obs_frontend_is_theme_dark() ? "theme:Dark/" + : "theme:Light/"; + QString text = item->getName().c_str(); + QRect rect = option.rect; + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + if (option.state & QStyle::State_MouseOver) + painter->fillRect(option.rect, option.palette.highlight()); + auto icon = item->icon(); + QRect iconRect(rect); + iconRect.setWidth(22); + iconRect.setHeight(22); + iconRect.setX(rect.x() + 5); + iconRect.setY(rect.y() + 5); + icon.paint(painter, iconRect); + // Paint the icon + + QRect textRect(rect); + textRect.setWidth(rect.width() - 107); + textRect.setHeight(30); + textRect.setX(32); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + + const QWidget *widget = option.widget; + QListWidget *lw = new QListWidget(); + QStyle *style = lw ? lw->style() : QApplication::style(); + + int loc = START_LOC; + int inc = INC; + + if (_dock->ShowScenes()) { + QRect ParentScenesButtonRect(rect); + ParentScenesButtonRect.setX(rect.width() - loc); + ParentScenesButtonRect.setHeight(30); + ParentScenesButtonRect.setWidth(30); + QStyleOptionButton parentScenesButton; + parentScenesButton.rect = ParentScenesButtonRect; + QIcon scenesIcon; + std::string scenesIconPath = mode + "sources/scene.svg"; + scenesIcon.addFile(scenesIconPath.c_str(), QSize(), + QIcon::Normal, QIcon::Off); + parentScenesButton.icon = scenesIcon; + parentScenesButton.iconSize = QSize(16, 16); + parentScenesButton.state = _filtersState | + QStyle::State_Enabled; + style->drawControl(QStyle::CE_PushButtonLabel, + &parentScenesButton, painter, widget); + loc += inc; + } + + if (_dock->ShowFilters()) { + QRect filtersButtonRect(rect); + filtersButtonRect.setX(rect.width() - loc); + filtersButtonRect.setHeight(30); + filtersButtonRect.setWidth(30); + QStyleOptionButton filtersButton; + filtersButton.rect = filtersButtonRect; + QIcon filterIcon; + std::string filtersIconPath = mode + "filter.svg"; + filterIcon.addFile(filtersIconPath.c_str(), QSize(), + QIcon::Normal, QIcon::Off); + filtersButton.icon = filterIcon; + filtersButton.iconSize = QSize(16, 16); + filtersButton.state = _filtersState | QStyle::State_Enabled; + style->drawControl(QStyle::CE_PushButtonLabel, &filtersButton, + painter, widget); + loc += inc; + } + + if (_dock->ShowProperties() && item->hasProperties()) { + QRect propertiesButtonRect(rect); + propertiesButtonRect.setX(rect.width() - loc); + propertiesButtonRect.setHeight(30); + propertiesButtonRect.setWidth(30); + QStyleOptionButton propertiesButton; + propertiesButton.rect = propertiesButtonRect; + QIcon propsIcon; + std::string propertiesIconPath = mode + "settings/general.svg"; + propsIcon.addFile(propertiesIconPath.c_str(), QSize(), + QIcon::Normal, QIcon::Off); + propertiesButton.icon = propsIcon; + propertiesButton.iconSize = QSize(16, 16); + propertiesButton.state = _propertiesState | + QStyle::State_Enabled; + style->drawControl(QStyle::CE_PushButtonLabel, + &propertiesButton, painter, widget); + loc += inc; + } + textRect.setWidth(rect.width() - (loc + 2)); + + //style->drawControl(QStyle::CE_ItemViewItem, &option, painter, widget); + delete lw; +} + +QSize QuickAccessSourceDelegate::sizeHint( + const QStyleOptionViewItem & /*option*/, + const QModelIndex & /*index*/) const +{ + //hard coding size for test purpose, + //actual size hint can be calculated from option param + return QSize(200, 30); +} + +bool QuickAccessSourceDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + UNUSED_PARAMETER(model); + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick) { + + } else { + //ignoring other mouse event and reseting button's state + _propertiesState = QStyle::State_Raised; + _filtersState = QStyle::State_Raised; + _parentScenesState = QStyle::State_Raised; + return true; + } + + QMouseEvent *mouseEvent = static_cast(event); + + int loc = START_LOC; + int inc = INC; + + if (event->type() == QEvent::MouseButtonRelease) { + emit itemSelected(index); + } + + if (_dock->ShowScenes()) { + QRect parentScenesButtonRect(option.rect); + parentScenesButtonRect.setX(option.rect.width() - loc); + parentScenesButtonRect.setWidth(30); + parentScenesButtonRect.setHeight(30); + + if (!parentScenesButtonRect.contains(mouseEvent->pos())) { + _parentScenesState = QStyle::State_Raised; + } else if (event->type() == QEvent::MouseButtonPress) { + _parentScenesState = QStyle::State_Sunken; + } else if (event->type() == QEvent::MouseButtonRelease) { + _parentScenesState = QStyle::State_Raised; + emit openParentScenesClicked(index); + return true; + } + loc += inc; + } + + if (_dock->ShowFilters()) { + + QRect filtersButtonRect(option.rect); + filtersButtonRect.setX(option.rect.width() - loc); + filtersButtonRect.setWidth(30); + filtersButtonRect.setHeight(30); + + if (!filtersButtonRect.contains(mouseEvent->pos())) { + _filtersState = QStyle::State_Raised; + } else if (event->type() == QEvent::MouseButtonPress) { + _filtersState = QStyle::State_Sunken; + } else if (event->type() == QEvent::MouseButtonRelease) { + _filtersState = QStyle::State_Raised; + emit openFiltersClicked(index); + return true; + } + loc += inc; + } + + if (_dock->ShowProperties()) { + QRect propertiesButtonRect(option.rect); + propertiesButtonRect.setX(option.rect.width() - loc); + propertiesButtonRect.setWidth(30); + propertiesButtonRect.setHeight(30); + + if (!propertiesButtonRect.contains(mouseEvent->pos())) { + _propertiesState = QStyle::State_Raised; + } else if (event->type() == QEvent::MouseButtonPress) { + _propertiesState = QStyle::State_Sunken; + } else if (event->type() == QEvent::MouseButtonRelease) { + _propertiesState = QStyle::State_Raised; + emit openPropertiesClicked(index); + return true; + } + } + + if (_dock->ClickableScenes() && + event->type() == QEvent::MouseButtonDblClick) { + blog(LOG_INFO, "DBL CLICK!!!!!"); + } else if (_dock->ClickableScenes() && + event->type() == QEvent::MouseButtonRelease) { + blog(LOG_INFO, "SINGLE CLICK!!!!"); + emit activateScene(index); + } + + return true; +} + +QuickAccessSourceModel::QuickAccessSourceModel(QObject *parent, + SearchType searchType) + : QAbstractListModel(parent), + _searchType(searchType) +{ +} + +Qt::DropActions QuickAccessSourceModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::ItemFlags QuickAccessSourceModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + + if (index.isValid()) + return Qt::ItemIsSelectable | + defaultFlags; //return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags; + else + return Qt::ItemIsSelectable | + defaultFlags; //Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | defaultFlags; +} + +bool QuickAccessSourceModel::setData(const QModelIndex &index, + const QVariant &value, int role) +{ + UNUSED_PARAMETER(role); + //auto dat = value.value(); + auto dat = static_cast(value.value()); + _data->at(index.row()) = dat; + return true; +} + +bool QuickAccessSourceModel::insertRows(int row, int count, + const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + + for (int i = 0; i != count; ++i) { + auto it = _data->begin() + row + i; + auto var = nullptr; + _data->insert(it, var); + } + return true; +} + +bool QuickAccessSourceModel::removeRows(int row, int count, + const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + beginRemoveRows(parent, row, row + count - 1); + for (int i = 0; i != count; ++i) { + auto it = _data->begin() + row; + _data->erase(it); + } + endRemoveRows(); + return true; +} + +void QuickAccessSourceModel::swapRows(int rowA, int rowB) +{ + auto &data = *_data; + beginResetModel(); + std::swap(data[rowA], data[rowB]); + endResetModel(); +} + +void QuickAccessSourceModel::addSource(QuickAccessSource *source) +{ + // TODO: Insert alphabatized + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _data->push_back(source); + endInsertRows(); +} + +void QuickAccessSourceModel::removeSource(QuickAccessSource *source) +{ + auto it = std::find(_data->begin(), _data->end(), source); + if (it == _data->end()) { + return; + } + int idx = static_cast(it - _data->begin()); + beginRemoveRows(QModelIndex(), idx, idx); + _data->erase(it); + endRemoveRows(); +} + +void QuickAccessSourceModel::setSearchTerm(std::string searchTerm) +{ + UNUSED_PARAMETER(searchTerm); +} + +int QuickAccessSourceModel::rowCount(const QModelIndex &parent) const +{ + UNUSED_PARAMETER(parent); + return static_cast(_data->size()); +} + +QuickAccessSource *QuickAccessSourceModel::item(int row) const +{ + if (row < 0 || row >= rowCount()) { + return nullptr; + } + return _data->at(row); +} + +QVariant QuickAccessSourceModel::data(const QModelIndex &index, int role) const +{ + UNUSED_PARAMETER(role); + if (index.row() < 0 || index.row() >= rowCount()) { + return {}; + } + + QuickAccessSource *ptr = _data->at(index.row()); + QVariant qv(QVariant::fromValue(static_cast(ptr))); + return qv; +} + +QuickAccessSource::QuickAccessSource(obs_source_t *source) +{ + if (!QuickAccessSource::registered) { + qRegisterMetaType(); + } + _source = obs_source_get_weak_source(source); + _tmpName = obs_source_get_name(source); + blog(LOG_INFO, "!!!!! QAS:Grabbed\t%s", _tmpName.c_str()); + _sourceClass = obs_source_is_group(source) ? SourceClass::Group + : obs_source_is_scene(source) ? SourceClass::Scene + : SourceClass::Source; + BuildSearchTerms(); +} + +QuickAccessSource::~QuickAccessSource() +{ + blog(LOG_INFO, "!!!!! QAS:Released\t%s", _tmpName.c_str()); + for (auto &dock : _docks) { + dock->RemoveSource(this, false); + } + for (auto &parent : _parents) { + parent->removeChild(this); + } + for (auto &child : _children) { + child->removeParent(this); + } + + obs_weak_source_release(_source); +} + +obs_source_t *QuickAccessSource::get() +{ + return obs_weak_source_get_source(_source); +} + +QIcon QuickAccessSource::icon() const +{ + auto source = obs_weak_source_get_source(_source); + if (!source) { + return qau->GetSceneIcon(); + } + const char *id = obs_source_get_id(source); + obs_source_release(source); + + if (strcmp(id, "scene") == 0) + return qau->GetSceneIcon(); + else if (strcmp(id, "group") == 0) + return qau->GetGroupIcon(); + return qau->GetIconFromType(id); +} + +signal_handler_t *QuickAccessSource::getSignalHandler() +{ + auto source = obs_weak_source_get_source(_source); + auto signalHandler = obs_source_get_signal_handler(source); + obs_source_release(source); + return signalHandler; +} + +std::string QuickAccessSource::getName() const +{ + auto source = obs_weak_source_get_source(_source); + if (!source) { + return std::string(""); + } + std::string name = obs_source_get_name(source); + obs_source_release(source); + return name; +} + +std::string QuickAccessSource::getUUID() const +{ + auto source = obs_weak_source_get_source(_source); + if (!source) { + return std::string(""); + } + std::string name = obs_source_get_uuid(source); + obs_source_release(source); + return name; +} + +void QuickAccessSource::rename(std::string name) +{ + obs_source_t *source = obs_weak_source_get_source(_source); + obs_source_set_name(source, name.c_str()); + obs_source_release(source); +} + +bool QuickAccessSource::save(obs_data_t *itemObj) +{ + if (removing()) { + return false; + } + std::string sourceName = getName(); + obs_data_set_string(itemObj, "source_name", sourceName.c_str()); + return true; +} + +std::vector QuickAccessSource::search(std::string searchTerm) +{ + std::vector hits; + for (auto &st : SearchTypes) { + for (auto &haystack : _searchTerms[st]) { + auto it = std::search(haystack.begin(), haystack.end(), + searchTerm.begin(), + searchTerm.end(), + [](char a, char b) { + return tolower(a) == + tolower(b); + }); + if (it != haystack.end()) { + hits.push_back(st); + break; + } + } + } + return hits; +} + +bool QuickAccessSource::hasMatch(std::string &searchTerm, SearchType st) +{ + for (auto &haystack : _searchTerms[st]) { + auto it = std::search(haystack.begin(), haystack.end(), + searchTerm.begin(), searchTerm.end(), + [](char a, char b) { + return tolower(a) == tolower(b); + }); + if (it != haystack.end()) { + return true; + } + } + return false; +} + +void QuickAccessSource::BuildSearchTerms() +{ + std::unique_lock lock(_m); + auto source = obs_weak_source_get_source(_source); + _searchTerms[SearchType::Source].clear(); + _searchTerms[SearchType::Type].clear(); + _searchTerms[SearchType::Filters].clear(); + _searchTerms[SearchType::Url].clear(); + _searchTerms[SearchType::File].clear(); + + if (!source) { + return; + } + + // Source Name + _searchTerms[SearchType::Source].push_back(obs_source_get_name(source)); + _searchTerms[SearchType::Source].push_back(obs_source_get_uuid(source)); + + // Source Type Id and Name + const char *source_id = obs_source_get_id(source); + // blog(LOG_INFO, "Source ID: %s", source_id); + const char *source_type_name = obs_source_get_display_name(source_id); + if (!source_type_name) { + return; + } + _searchTerms[SearchType::Type].push_back(source_id); + _searchTerms[SearchType::Type].push_back( + obs_source_get_display_name(source_id)); + + std::vector filters; + // Source Filters + obs_source_enum_filters(source, GetFilters, &filters); + + for (auto filter : filters) { + const char *filter_id = obs_source_get_id(filter); + const char *name = obs_source_get_display_name(filter_id); + if (!name) { + continue; + } + _searchTerms[SearchType::Filters].push_back(filter_id); + _searchTerms[SearchType::Filters].push_back( + obs_source_get_display_name(filter_id)); + _searchTerms[SearchType::Filters].push_back( + obs_source_get_name(filter)); + } + + obs_data_t *data = obs_source_get_settings(source); + // Browser source urls + if (strcmp(source_id, BROWSER_SOURCE_ID) == 0) { + std::string url = obs_data_get_string(data, "url"); + _searchTerms[SearchType::Url].push_back(url); + } + + // Media file path/input + if (strcmp(source_id, MEDIA_SOURCE_ID) == 0) { + bool localFile = obs_data_get_bool(data, "is_local_file"); + if (localFile) { + _searchTerms[SearchType::File].push_back( + obs_data_get_string(data, "local_file")); + } else { + _searchTerms[SearchType::Url].push_back( + obs_data_get_string(data, "input")); + } + } + + // Image file path + if (strcmp(source_id, IMAGE_SOURCE_ID) == 0) { + std::string file = obs_data_get_string(data, "file"); + _searchTerms[SearchType::File].push_back(file); + } + obs_data_release(data); + + obs_source_release(source); +} + +void QuickAccessSource::update() +{ + BuildSearchTerms(); +} + +void QuickAccessSource::addDock(QuickAccessDock *dock) +{ + std::unique_lock lock(_m); + _docks.insert(dock); +} + +void QuickAccessSource::removeDock(QuickAccessDock *dock) +{ + std::unique_lock lock(_m); + if (auto it = _docks.find(dock); it != _docks.end()) { + _docks.erase(it); + } +} + +void QuickAccessSource::removeParent(QuickAccessSource *parent) +{ + //std::unique_lock lock(_m); + if (_parents.size() == 0) { + return; + } + auto it = std::find(_parents.begin(), _parents.end(), parent); + while (it != _parents.end()) { + _parents.erase(it); + it = std::find(_parents.begin(), _parents.end(), parent); + } +} + +void QuickAccessSource::removeChild(QuickAccessSource *child) +{ + //std::unique_lock lock(_m); + if (_children.size() == 0) { + return; + } + // Child can be added multiple times to a parent scene/group. + auto it = std::find(_children.begin(), _children.end(), child); + while (it != _children.end()) { + _children.erase(it); + it = std::find(_children.begin(), _children.end(), child); + } +} + +void QuickAccessSource::openProperties() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + obs_frontend_open_source_properties(source); + obs_source_release(source); +} + +void QuickAccessSource::openFilters() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + obs_frontend_open_source_filters(source); + obs_source_release(source); +} + +void QuickAccessSource::openInteract() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + obs_frontend_open_source_interaction(source); + obs_source_release(source); +} + +void QuickAccessSource::activateScene() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + if (obs_source_is_scene(source)) { + bool studioMode = obs_frontend_preview_program_mode_active(); + if (studioMode) { + obs_frontend_set_current_preview_scene(source); + } else { + obs_frontend_set_current_scene(source); + } + } + obs_source_release(source); +} + +bool QuickAccessSource::hasProperties() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + bool props = obs_source_configurable(source); + obs_source_release(source); + return props; +} + +bool QuickAccessSource::hasInteract() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + uint32_t flags = obs_source_get_output_flags(source); + obs_source_release(source); + return (flags & OBS_SOURCE_INTERACTION) == OBS_SOURCE_INTERACTION; +} + +bool QuickAccessSource::hasRefresh() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + std::string sourceType = obs_source_get_id(source); + obs_source_release(source); + return sourceType == "browser_source"; +} + +void QuickAccessSource::refreshBrowser() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + obs_properties_t *props = obs_source_properties(source); + + obs_property_t *p = obs_properties_get(props, "refreshnocache"); + obs_property_button_clicked(p, source); + obs_source_release(source); +} + +std::string QuickAccessSource::activeState() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + std::string type_str = obs_source_get_id(source); + std::string ret = ""; + if (type_str == "dshow_input") { + auto settings = obs_source_get_settings(source); + bool now_active = obs_data_get_bool(settings, "active"); + ret = now_active ? "Deactivate" : "Activate"; + obs_data_release(settings); + } + // macos-avcapture-fast and av_capture_input on macos + obs_source_release(source); + return ret; +} + +void QuickAccessSource::toggleActivation() const +{ + obs_source_t *source = obs_weak_source_get_source(_source); + if (!source) { + return; + } + auto settings = obs_source_get_settings(source); + bool nowActive = obs_data_get_bool(settings, "active"); + + obs_data_release(settings); + obs_source_release(source); + + calldata_t cd = {}; + calldata_set_bool(&cd, "active", !nowActive); + proc_handler_t *ph = obs_source_get_proc_handler(source); + proc_handler_call(ph, "activate", &cd); + calldata_free(&cd); +} + +QDataStream &operator<<(QDataStream &out, QuickAccessSource *const &rhs) +{ + out.writeRawData(reinterpret_cast(&rhs), sizeof(rhs)); + return out; +} + +QDataStream &operator>>(QDataStream &in, QuickAccessSource *&rhs) +{ + in.readRawData(reinterpret_cast(&rhs), sizeof(rhs)); + return in; +} + +void GetFilters(obs_source_t *parentScene, obs_source_t *filter, void *param) +{ + UNUSED_PARAMETER(parentScene); + std::vector *filters = + static_cast *>(param); + filters->push_back(filter); +} diff --git a/src/quick-access-source.hpp b/src/quick-access-source.hpp new file mode 100644 index 0000000..3c9f7cd --- /dev/null +++ b/src/quick-access-source.hpp @@ -0,0 +1,162 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QuickAccessDock; +class QuickAccessSource; + +enum class SearchType { None, Source, Type, Filters, Url, File }; + +enum class SourceClass { Source, Scene, Group }; + +class QuickAccessSourceDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + QuickAccessSourceDelegate(QObject *parent = nullptr, + QuickAccessDock *dock = nullptr); + + virtual void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + virtual QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index); + +signals: + void openPropertiesClicked(const QModelIndex &index); + void openParentScenesClicked(const QModelIndex &index); + void openFiltersClicked(const QModelIndex &index); + void activateScene(const QModelIndex &index); + void itemSelected(const QModelIndex &index); + +private: + QStyle::State _propertiesState; + QStyle::State _filtersState; + QStyle::State _parentScenesState; + QuickAccessDock *_dock; +}; + +class QuickAccessSourceModel : public QAbstractListModel { + Q_OBJECT + +public: + explicit QuickAccessSourceModel( + QObject *parent = nullptr, + SearchType searchType = SearchType::None); + inline void setSources(std::vector *newData) + { + _data = newData; + } + void addSource(QuickAccessSource *source); + void removeSource(QuickAccessSource *source); + void setSearchTerm(std::string searchTerm); + QuickAccessSource *item(int row) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::DisplayRole) override; + bool insertRows(int row, int count, const QModelIndex &parent) override; + bool removeRows(int row, int count, const QModelIndex &parent) override; + + void swapRows(int rowA, int rowB); + + Qt::DropActions supportedDropActions() const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + +private: + std::vector *_data; + SearchType _searchType; +}; + +typedef std::map> SearchTermMap; + +class QuickAccessSource { +public: + QuickAccessSource(obs_source_t *); + ~QuickAccessSource(); + + void BuildSearchTerms(); + + std::string getName() const; + std::string getUUID() const; + obs_source_t *get(); + void addDock(QuickAccessDock *); + void removeDock(QuickAccessDock *); + inline bool removing() { return _removing; } + inline void addParent(QuickAccessSource *parent) + { + _parents.push_back(parent); + } + inline void addChild(QuickAccessSource *child) + { + _children.push_back(child); + } + inline void markForRemoval() { _removing = true; } + void removeParent(QuickAccessSource *parent); + void removeChild(QuickAccessSource *child); + signal_handler_t *getSignalHandler(); + inline std::vector children() { return _children; } + inline std::vector parents() { return _parents; } + inline SourceClass sourceType() { return _sourceClass; } + std::vector search(std::string searchTerm); + bool hasMatch(std::string &searchTerm, SearchType st); + void update(); + void openProperties() const; + void openFilters() const; + void openInteract() const; + void refreshBrowser() const; + void toggleActivation() const; + void activateScene() const; + bool hasProperties() const; + bool hasInteract() const; + bool hasRefresh() const; + std::string activeState() const; + + bool save(obs_data_t *itemObj); + inline bool isSource(obs_source_t *source) + { + return obs_weak_source_references_source(_source, source); + } + void rename(std::string name); + QIcon icon() const; + +private: + obs_weak_source_t *_source; + SourceClass _sourceClass; + SearchTermMap _searchTerms; + bool _removing = false; + std::vector _parents; + std::vector _children; + std::set _docks; + std::mutex _m; + // TODO: Get rid of _tmpName + std::string _tmpName; + static bool registered; +}; + +Q_DECLARE_METATYPE(QuickAccessSource *) + +QDataStream &operator<<(QDataStream &out, QuickAccessSource *const &rhs); +QDataStream &operator>>(QDataStream &in, QuickAccessSource *&rhs); + +void GetFilters(obs_source_t *parentScene, obs_source_t *filter, void *param); diff --git a/src/quick-access-utility.cpp b/src/quick-access-utility.cpp index 1c1bf6d..708beb5 100644 --- a/src/quick-access-utility.cpp +++ b/src/quick-access-utility.cpp @@ -1,5 +1,6 @@ #include "quick-access-utility.hpp" #include "quick-access-dock.hpp" +#include "quick-access.hpp" #include #include @@ -8,78 +9,133 @@ #include #include #include +#include #include "version.h" -#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(29, 1, 0) -#include -#include -#endif - #define QT_UTF8(str) QString::fromUtf8(str) #define QT_TO_UTF8(str) str.toUtf8().constData() QuickAccessUtility *qau = nullptr; QuickAccessUtilityDialog *qauDialog = nullptr; +QuickAccessSearchModal *qauSearch = nullptr; QuickAccessUtilityDialog *QuickAccessUtilityDialog::dialog = nullptr; +QuickAccessSearchModal *QuickAccessSearchModal::dialog = nullptr; QuickAccessUtility::QuickAccessUtility(obs_module_t *m) : _module(m), - _firstRun(false) + _firstRun(false), + _currentScene(nullptr) { obs_frontend_add_event_callback(QuickAccessUtility::FrontendCallback, this); + + _quick_search_hotkey_id = obs_hotkey_register_frontend( + "Quick Search", "Opens Quick Search", + QuickAccessSearchModal::OpenQuickSearch, this); } QuickAccessUtility::~QuickAccessUtility() { blog(LOG_INFO, "QuickAccessUtility::~QuickAccessUtility"); + //delete _currentScene; // Dont need to delete dock pointers, as they are managed by OBS. obs_frontend_remove_event_callback(QuickAccessUtility::FrontendCallback, this); + obs_hotkey_unregister(_quick_search_hotkey_id); } void QuickAccessUtility::SourceCreated(void *data, calldata_t *params) { + if (!qau->loaded) { + return; + } blog(LOG_INFO, "Source Created!"); UNUSED_PARAMETER(data); obs_source_t *source = static_cast(calldata_ptr(params, "source")); + std::string uuid = obs_source_get_uuid(source); + QuickAccessUtility::AddSource(nullptr, source); + if (qau->_allSources.count(uuid) == 0) { + return; + } + QuickAccessSource *qaSource = qau->_allSources[uuid].get(); QMetaObject::invokeMethod( - QCoreApplication::instance()->thread(), [source]() { + QCoreApplication::instance()->thread(), [qaSource]() { for (auto &dock : qau->_docks) { if (dock) { - dock->SourceCreated(source); + dock->SourceCreated(qaSource); } } + QuickAccessSearchModal::dialog->SourceCreated(qaSource); }); } void QuickAccessUtility::SourceDestroyed(void *data, calldata_t *params) { UNUSED_PARAMETER(data); - UNUSED_PARAMETER(params); + obs_source_t *source = + static_cast(calldata_ptr(params, "source")); + std::string uuid = obs_source_get_uuid(source); blog(LOG_INFO, "Source Destroyed!"); - QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), []() { - for (auto &dock : qau->_docks) { - if (dock) { - dock->SourceDestroyed(); - } - } - }); + //QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), [uuid]() { + std::unique_lock lock(qau->_m); + auto it = qau->_allSources.find(uuid); + if (it != qau->_allSources.end()) { + + QMetaObject::invokeMethod( + QCoreApplication::instance()->thread(), [it]() { + auto qaSource = it->second.get(); + for (auto &dock : qau->_docks) { + if (dock) { + dock->SourceDestroyed(qaSource); + } + } + QuickAccessSearchModal::dialog->SourceDestroyed( + qaSource); + it->second->markForRemoval(); + qau->_allSources.erase(it); + }); + } + lock.unlock(); + //}); +} + +void QuickAccessUtility::SourceUpdate(void *data, calldata_t *params) +{ + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(params); + + obs_source_t *source = + static_cast(calldata_ptr(params, "source")); + //blog(LOG_INFO, "Updated Source: %s", obs_source_get_name(source)); + std::string uuid = obs_source_get_uuid(source); + if (qau->_allSources.count(uuid) == 0) { + return; + } + qau->_allSources[uuid]->update(); } void QuickAccessUtility::SourceRename(void *data, calldata_t *params) { UNUSED_PARAMETER(data); + if (!qau->loaded) { + return; + } obs_source_t *source = static_cast(calldata_ptr(params, "source")); + std::string uuid = obs_source_get_uuid(source); + if (qau->_allSources.count(uuid) == 0) { + return; + } + QuickAccessSource *qaSource = qau->_allSources[uuid].get(); + qaSource->update(); QMetaObject::invokeMethod( - QCoreApplication::instance()->thread(), [source]() { + QCoreApplication::instance()->thread(), [qaSource]() { for (auto &dock : qau->_docks) { if (dock) { - dock->SourceRename(source); + dock->SourceRename(qaSource); } } }); @@ -90,30 +146,39 @@ obs_module_t *QuickAccessUtility::GetModule() return _module; } +void QuickAccessUtility::UnloadDocks() +{ + std::unique_lock lock(qau->_m); + //delete _currentScene; + qau->_currentScene = nullptr; + qau->RemoveDocks(); + qau->_allSources.clear(); +} + void QuickAccessUtility::RemoveDock(int idx, bool cleanup) { auto dock = _docks.at(idx); if (cleanup) { dock->CleanupSourceHandlers(); } -#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0) obs_frontend_remove_dock( ("quick-access-dock_" + dock->GetId()).c_str()); -#else - dock->parentWidget()->close(); - delete (dock->parentWidget()); - delete (dock); -#endif _docks.erase(_docks.begin() + idx); } void QuickAccessUtility::Load(obs_data_t *data) { + std::unique_lock lock(_m); blog(LOG_INFO, "QAU::Load called."); - for (auto &dock : _docks) { - delete dock; - } - _docks.clear(); + RemoveDocks(); + _allSources.clear(); + // Add Scenes and sources to _allSources + obs_enum_all_sources(QuickAccessUtility::AddSource, qau); + obs_enum_scenes(QuickAccessUtility::AddSource, qau); + + // Link scenes/groups with their children + obs_enum_scenes(QuickAccessUtility::LinkScenes, qau); + const auto mainWindow = static_cast(obs_frontend_get_main_window()); auto qauData = obs_data_get_obj(data, "quick_access_utility"); @@ -133,7 +198,33 @@ void QuickAccessUtility::Load(obs_data_t *data) obs_data_release(dockData); } obs_data_array_release(docks); + + auto quickSearchHotkey = + obs_data_get_array(qauData, "quick_search_hotkey"); + obs_hotkey_load(_quick_search_hotkey_id, quickSearchHotkey); + obs_data_array_release(quickSearchHotkey); + obs_data_release(qauData); + + // Set up the quick search dialog + // This shouldn't need to be done, but for some reason + // the first time we pop open the search, it is a tiny + // window and then disappears. + if (!QuickAccessSearchModal::dialog) { + const auto main_window = static_cast( + obs_frontend_get_main_window()); + const QRect &hostRect = main_window->geometry(); + + QuickAccessSearchModal::dialog = + new QuickAccessSearchModal(static_cast( + obs_frontend_get_main_window())); + auto searchDlg = QuickAccessSearchModal::dialog; + searchDlg->open(); + searchDlg->move(hostRect.center() - searchDlg->rect().center()); + searchDlg->adjustSize(); + } + + loaded = true; } void QuickAccessUtility::Save(obs_data_t *data) @@ -148,79 +239,253 @@ void QuickAccessUtility::Save(obs_data_t *data) dock->Save(saveData); } + auto quickSearchHotkey = obs_hotkey_save(_quick_search_hotkey_id); + obs_data_set_array(saveData, "quick_search_hotkey", quickSearchHotkey); + obs_data_set_obj(data, "quick_access_utility", saveData); obs_data_array_release(dockArray); + obs_data_array_release(quickSearchHotkey); obs_data_release(saveData); + + QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), + [this]() { + for (auto &dock : _docks) { + if (dock) { + dock->SourceUpdate(); + } + } + }); } void QuickAccessUtility::RemoveDocks() { for (auto &dock : _docks) { - dock->CleanupSourceHandlers(); - } - for (auto &dock : _docks) { -#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0) obs_frontend_remove_dock( ("quick-access-dock_" + dock->GetId()).c_str()); -#else - dock->parentWidget()->close(); - delete (dock->parentWidget()); - delete (dock); -#endif } _docks.clear(); } +QuickAccessSource *QuickAccessUtility::GetSource(std::string uuid) +{ + auto search = _allSources.find(uuid); + return search != _allSources.end() ? search->second.get() : nullptr; +} + +std::vector QuickAccessUtility::GetAllSources() +{ + std::vector sources; + for (auto const &[key, val] : _allSources) { + sources.push_back(val.get()); + } + return sources; +} + +void QuickAccessUtility::_SetCurrentSceneSources() +{ + _currentSceneSources.clear(); + if (_currentScene) { + _AddChildren(_currentScene); + } +} + +void QuickAccessUtility::_AddChildren(QuickAccessSource *scene) +{ + for (auto child : scene->children()) { + if (std::find(_currentSceneSources.begin(), + _currentSceneSources.end(), + child) == _currentSceneSources.end()) { + _currentSceneSources.push_back(child); + if (child->sourceType() != SourceClass::Source) { + _AddChildren(child); + } + } + } +} + +void QuickAccessUtility::_SetupSignals() +{ + signal_handler_t *signalHandler = obs_get_signal_handler(); + signal_handler_connect_ref(signalHandler, "source_create", + QuickAccessUtility::SourceCreated, qau); + signal_handler_connect_ref(signalHandler, "source_destroy", + QuickAccessUtility::SourceDestroyed, qau); + signal_handler_connect_ref(signalHandler, "source_rename", + QuickAccessUtility::SourceRename, qau); + signal_handler_connect_ref(signalHandler, "source_update", + QuickAccessUtility::SourceUpdate, qau); + signal_handler_connect_ref(signalHandler, "source_filter_add", + QuickAccessUtility::SourceUpdate, qau); + signal_handler_connect_ref(signalHandler, "source_filter_remove", + QuickAccessUtility::SourceUpdate, qau); +} + +void QuickAccessUtility::_TearDownSignals() +{ + signal_handler_t *signalHandler = obs_get_signal_handler(); + signal_handler_disconnect(signalHandler, "source_create", + QuickAccessUtility::SourceCreated, qau); + signal_handler_disconnect(signalHandler, "source_destroy", + QuickAccessUtility::SourceDestroyed, qau); + signal_handler_disconnect(signalHandler, "source_rename", + QuickAccessUtility::SourceRename, qau); + signal_handler_disconnect(signalHandler, "source_update", + QuickAccessUtility::SourceUpdate, qau); + signal_handler_disconnect(signalHandler, "source_filter_add", + QuickAccessUtility::SourceUpdate, qau); + signal_handler_disconnect(signalHandler, "source_filter_remove", + QuickAccessUtility::SourceUpdate, qau); + if (_currentScene) { + signal_handler_t *oldSignalHandler = + _currentScene->getSignalHandler(); + signal_handler_disconnect( + oldSignalHandler, "item_add", + QuickAccessUtility::SourceAddedToScene, qau); + signal_handler_disconnect( + oldSignalHandler, "item_remove", + QuickAccessUtility::SourceRemovedFromScene, qau); + //delete _currentScene; + //_currentScene = nullptr; + } +} + void QuickAccessUtility::FrontendCallback(enum obs_frontend_event event, void *data) { UNUSED_PARAMETER(data); - if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED || - event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { - blog(LOG_INFO, - "QAU::Scene Collection Changed/Finished Loading"); - signal_handler_connect_ref(obs_get_signal_handler(), - "source_create", - QuickAccessUtility::SourceCreated, - qau); - signal_handler_connect_ref(obs_get_signal_handler(), - "source_destroy", - QuickAccessUtility::SourceDestroyed, - qau); - signal_handler_connect_ref(obs_get_signal_handler(), - "source_rename", - QuickAccessUtility::SourceRename, - qau); + if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { + blog(LOG_INFO, "============== QAU::Finished Loading"); + qau->_sceneCollectionChanging = false; + qau->_SetupSignals(); + qau->SceneChanged(); } else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP) { - blog(LOG_INFO, "QAU::Scene Collection Cleanup/Exit"); - signal_handler_disconnect(obs_get_signal_handler(), - "source_create", - QuickAccessUtility::SourceCreated, - qau); - signal_handler_disconnect(obs_get_signal_handler(), - "source_destroy", - QuickAccessUtility::SourceDestroyed, - qau); - signal_handler_disconnect(obs_get_signal_handler(), - "source_rename", - QuickAccessUtility::SourceRename, - qau); - if (qau->_sceneCollectionChanging) { - QMetaObject::invokeMethod( - QCoreApplication::instance()->thread(), - []() { qau->RemoveDocks(); }); - qau->_sceneCollectionChanging = false; - } + blog(LOG_INFO, "============== QAU::Scene Collection Cleanup"); + QMetaObject::invokeMethod( + QCoreApplication::instance()->thread(), + []() { qau->UnloadDocks(); }); + qau->loaded = false; } else if (event == OBS_FRONTEND_EVENT_EXIT) { - blog(LOG_INFO, "QAU::Frontend Exit"); + blog(LOG_INFO, "============== QAU::Frontend Exit"); } else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING) { - blog(LOG_INFO, "QAU::Scene Collection Changing"); + blog(LOG_INFO, "============== QAU::Scene Collection Changing"); + qau->_TearDownSignals(); qau->_sceneCollectionChanging = true; + } else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) { + blog(LOG_INFO, "============== QAU::SCENE COLLECTION CHANGED"); + qau->_sceneCollectionChanging = false; + qau->_SetupSignals(); + qau->InitializeSearch(); + qau->SceneChanged(); } else if (event == OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN) { - blog(LOG_INFO, "QAU::SCRIPTING SHUTDOWN"); + blog(LOG_INFO, "============== QAU::SCRIPTING SHUTDOWN"); + qau->_TearDownSignals(); QMetaObject::invokeMethod( - QCoreApplication::instance()->thread(), - []() { qau->RemoveDocks(); }); + QCoreApplication::instance()->thread(), []() { + std::unique_lock lock(qau->_m); + qau->RemoveDocks(); + qau->_allSources.clear(); + if (QuickAccessUtilityDialog::dialog) { + delete QuickAccessUtilityDialog::dialog; + } + }); + } else if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) { + blog(LOG_INFO, "============== QAU::SCENE CHANGED"); + if (!qau->_sceneCollectionChanging) { + qau->SceneChanged(); + } + } +} + +void QuickAccessUtility::InitializeSearch() +{ + QuickAccessSearchModal::dialog->InitializeSearch(); +} + +void QuickAccessUtility::SceneChanged() +{ + obs_source_t *newScene = obs_frontend_get_current_scene(); + + std::string uuid = obs_source_get_uuid(newScene); + QuickAccessSource *qaNewScene = _allSources[uuid].get(); + if (qaNewScene) { + if (_currentScene) { + signal_handler_t *oldSignalHandler = + _currentScene->getSignalHandler(); + signal_handler_disconnect( + oldSignalHandler, "item_add", + QuickAccessUtility::SourceAddedToScene, qau); + signal_handler_disconnect( + oldSignalHandler, "item_remove", + QuickAccessUtility::SourceRemovedFromScene, + qau); + //delete _currentScene; + _currentScene = nullptr; + } + signal_handler_t *signalHandler = + obs_source_get_signal_handler(newScene); + _currentScene = qaNewScene; + + signal_handler_connect_ref( + signalHandler, "item_add", + QuickAccessUtility::SourceAddedToScene, qau); + signal_handler_connect_ref( + signalHandler, "item_remove", + QuickAccessUtility::SourceRemovedFromScene, qau); + _SetCurrentSceneSources(); + for (auto &dock : _docks) { + dock->SetCurrentScene(qaNewScene); + } + } + obs_source_release(newScene); +} + +void QuickAccessUtility::SourceAddedToScene(void *data, calldata_t *params) +{ + blog(LOG_INFO, "SOURCE ADDED TO THE SCENE!"); + UNUSED_PARAMETER(data); + obs_scene_t *parentScene = + static_cast(calldata_ptr(params, "scene")); + obs_source_t *parentSource = obs_scene_get_source(parentScene); + obs_sceneitem_t *sceneItem = + static_cast(calldata_ptr(params, "item")); + obs_source_t *source = obs_sceneitem_get_source(sceneItem); + std::string sourceId = obs_source_get_uuid(source); + std::string sceneId = obs_source_get_uuid(parentSource); + + QuickAccessSource *parent = qau->_allSources[sceneId].get(); + QuickAccessSource *child = qau->_allSources[sourceId].get(); + + child->addParent(parent); + parent->addChild(child); + + qau->_SetCurrentSceneSources(); + for (auto &dock : qau->_docks) { + dock->SetCurrentScene(qau->_currentScene); + } +} + +void QuickAccessUtility::SourceRemovedFromScene(void *data, calldata_t *params) +{ + blog(LOG_INFO, "SOURCE REMOVED FROM THE SCENE!"); + UNUSED_PARAMETER(data); + obs_scene_t *parentScene = + static_cast(calldata_ptr(params, "scene")); + obs_source_t *parentSource = obs_scene_get_source(parentScene); + obs_sceneitem_t *sceneItem = + static_cast(calldata_ptr(params, "item")); + obs_source_t *source = obs_sceneitem_get_source(sceneItem); + std::string sourceId = obs_source_get_uuid(source); + std::string sceneId = obs_source_get_uuid(parentSource); + + QuickAccessSource *parent = qau->_allSources[sceneId].get(); + QuickAccessSource *child = qau->_allSources[sourceId].get(); + + child->removeParent(parent); + parent->removeChild(child); + + qau->_SetCurrentSceneSources(); + for (auto &dock : qau->_docks) { + dock->SetCurrentScene(qau->_currentScene); } } @@ -325,6 +590,43 @@ QIcon QuickAccessUtility::GetGroupIcon() const return main_window->property("groupIcon").value(); } +bool QuickAccessUtility::AddSource(void *data, obs_source_t *source) +{ + UNUSED_PARAMETER(data); + obs_source_type st = obs_source_get_type(source); + if (st == OBS_SOURCE_TYPE_FILTER || st == OBS_SOURCE_TYPE_TRANSITION) { + return true; + } + std::string uuid = obs_source_get_uuid(source); + if (qau->_allSources.find(uuid) == qau->_allSources.end()) { + qau->_allSources.emplace( + uuid, std::make_unique(source)); + } + return true; +} + +bool QuickAccessUtility::LinkScenes(void *data, obs_source_t *source) +{ + obs_scene_t *scene = obs_group_or_scene_from_source(source); + obs_scene_enum_items(scene, QuickAccessUtility::LinkSceneItem, data); + return true; +} + +bool QuickAccessUtility::LinkSceneItem(obs_scene_t *scene, + obs_sceneitem_t *sceneItem, void *data) +{ + QuickAccessUtility &self = *static_cast(data); + obs_source_t *source = obs_sceneitem_get_source(sceneItem); + std::string sourceId = obs_source_get_uuid(source); + obs_source_t *sceneSrc = obs_scene_get_source(scene); + std::string sceneId = obs_source_get_uuid(sceneSrc); + QuickAccessSource *qasSource = self._allSources[sourceId].get(); + QuickAccessSource *qasScene = self._allSources[sceneId].get(); + self._allSources[sourceId].get()->addParent(qasScene); + self._allSources[sceneId].get()->addChild(qasSource); + return true; +} + QuickAccessUtilityDialog::QuickAccessUtilityDialog(QWidget *parent) : QDialog(parent) { @@ -393,7 +695,7 @@ QuickAccessUtilityDialog::QuickAccessUtilityDialog(QWidget *parent) _layout->addWidget(header); - _dockList = new QuickAccessList(this, nullptr); + _dockList = new QListWidget(this); _dockList->setObjectName(QStringLiteral("docks")); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); sizePolicy.setHorizontalStretch(0); @@ -471,6 +773,7 @@ QuickAccessUtilityDialog::~QuickAccessUtilityDialog() qau->mainWindowOpen = false; qau->dialog = nullptr; } + QuickAccessUtilityDialog::dialog = nullptr; } void QuickAccessUtilityDialog::on_actionAddDock_triggered() @@ -490,8 +793,14 @@ void QuickAccessUtilityDialog::on_actionRemoveDock_triggered() void OpenQAUDialog() { + const auto main_window = + static_cast(obs_frontend_get_main_window()); + const QRect &hostRect = main_window->geometry(); if (qau->mainWindowOpen) { QuickAccessUtilityDialog::dialog->show(); + QuickAccessUtilityDialog::dialog->move( + hostRect.center() - + QuickAccessUtilityDialog::dialog->rect().center()); QuickAccessUtilityDialog::dialog->raise(); QuickAccessUtilityDialog::dialog->activateWindow(); } else { @@ -501,9 +810,42 @@ void OpenQAUDialog() QuickAccessUtilityDialog::dialog->setAttribute( Qt::WA_DeleteOnClose); QuickAccessUtilityDialog::dialog->show(); + QuickAccessUtilityDialog::dialog->move( + hostRect.center() - + QuickAccessUtilityDialog::dialog->rect().center()); } } +void QuickAccessSearchModal::OpenQuickSearch(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(data); + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + if (!pressed) { + return; + } + const auto main_window = + static_cast(obs_frontend_get_main_window()); + const QRect &hostRect = main_window->geometry(); + QuickAccessSearchModal *dialog = QuickAccessSearchModal::dialog; + if (dialog != nullptr) { + dialog->open(); + dialog->move(hostRect.center() - dialog->rect().center()); + dialog->raise(); + dialog->activateWindow(); + } else if (dialog == nullptr) { + QuickAccessSearchModal::dialog = + new QuickAccessSearchModal(static_cast( + obs_frontend_get_main_window())); + dialog = QuickAccessSearchModal::dialog; + dialog->open(); + dialog->move(hostRect.center() - dialog->rect().center()); + dialog->adjustSize(); + } + dialog->SearchFocus(); +} + DockListItem::DockListItem(QuickAccessDock *dock, QWidget *parent) : QFrame(parent), _dock(dock) @@ -680,7 +1022,6 @@ void CreateDockDialog::on_create_dock() formData.showFilters = _showFilters->isChecked(); formData.showScenes = _showScenes->isChecked(); formData.clickableScenes = _clickThroughScenes->isChecked(); - formData.clickableScenes = _clickThroughScenes->isChecked(); qau->CreateDock(formData); done(DialogCode::Accepted); } @@ -691,6 +1032,77 @@ void CreateDockDialog::on_cancel() done(DialogCode::Rejected); } +QuickAccessSearchModal::QuickAccessSearchModal(QWidget *parent) +{ + UNUSED_PARAMETER(parent); + setMinimumSize(400, 600); + resize(400, 600); + // Set up data + auto dockData = obs_data_create(); + obs_data_set_string(dockData, "dock_name", "Quick Search"); + obs_data_set_string(dockData, "dock_type", "Source Search"); + + char *dockId = os_generate_uuid(); + obs_data_set_string(dockData, "dock_id", dockId); + bfree(dockId); + + obs_data_set_bool(dockData, "show_properties", true); + obs_data_set_bool(dockData, "show_filters", true); + obs_data_set_bool(dockData, "show_scenes", true); + obs_data_set_bool(dockData, "clickable_scenes", true); + auto sourcesArray = obs_data_array_create(); + obs_data_set_array(dockData, "dock_sources", sourcesArray); + + _layout = new QVBoxLayout(); + // Create new dock + _widget = new QuickAccessDock(this, dockData, true); + obs_data_array_release(sourcesArray); + obs_data_release(dockData); + + _layout->addWidget(_widget); + setLayout(_layout); + setWindowTitle("Quick Search"); + setWindowFlags(Qt::Popup); + setStyleSheet("border-radius: 20px;"); + //setModal(true); + qau->searchDialog = this; + qau->quickSearchOpen = true; +} + +QuickAccessSearchModal::~QuickAccessSearchModal() +{ + if (qau) { + qau->quickSearchOpen = false; + qau->searchDialog = nullptr; + } +} + +void QuickAccessSearchModal::InitializeSearch() +{ + _widget->InitializeSearch(); +} + +void QuickAccessSearchModal::SearchFocus() +{ + if (_widget) { + _widget->SearchFocus(); + } +} + +void QuickAccessSearchModal::SourceCreated(QuickAccessSource *source) +{ + if (_widget) { + _widget->SourceCreated(source); + } +} + +void QuickAccessSearchModal::SourceDestroyed(QuickAccessSource *source) +{ + if (_widget) { + _widget->SourceDestroyed(source); + } +} + extern "C" EXPORT void InitializeQAU(obs_module_t *module, translateFunc translate) { @@ -700,6 +1112,11 @@ extern "C" EXPORT void InitializeQAU(obs_module_t *module, "Quick Access Utility"); action->connect(action, &QAction::triggered, OpenQAUDialog); + //QAction* action2 = (QAction*)obs_frontend_add_tools_menu_qaction( + // "Quick Search" + //); + //action2->connect(action2, &QAction::triggered, OpenQuickSearch); + obs_frontend_add_save_callback(frontendSaveLoad, qau); } diff --git a/src/quick-access-utility.hpp b/src/quick-access-utility.hpp index 727d0e2..a3f82e3 100644 --- a/src/quick-access-utility.hpp +++ b/src/quick-access-utility.hpp @@ -8,17 +8,25 @@ #include #include #include +#include +#include #include #include #include #include +#include #include -#include "quick-access-dock.hpp" +#include +#include + +class QuickAccessDock; +class QuickAccessSource; class QuickAccessUtility; extern QuickAccessUtility *qad; class QuickAccessUtilityDialog; +class QuickAccessSearchModal; struct CreateDockFormData { std::string dockName; @@ -38,14 +46,28 @@ class QuickAccessUtility { void Save(obs_data_t *data); void RemoveDock(int idx, bool cleanup = false); void RemoveDocks(); + void SceneChanged(); + void UnloadDocks(); + void InitializeSearch(); inline std::vector GetDocks() { return _docks; } inline bool SourceCloneInstalled() { return _sourceCloneInstalled; } inline bool dskInstalled() { return _dskInstalled; } + inline QuickAccessSource *GetCurrentScene() { return _currentScene; } + inline std::vector GetCurrentSceneSources() + { + return _currentSceneSources; + } + + QuickAccessSource *GetSource(std::string); + std::vector GetAllSources(); obs_module_t *GetModule(); bool mainWindowOpen = false; + bool quickSearchOpen = false; + bool loaded = false; QuickAccessUtilityDialog *dialog = nullptr; + QuickAccessSearchModal *searchDialog = nullptr; QIcon GetIconFromType(const char *type) const; QIcon GetSceneIcon() const; @@ -57,15 +79,34 @@ class QuickAccessUtility { static void SourceCreated(void *data, calldata_t *params); static void SourceDestroyed(void *data, calldata_t *params); static void SourceRename(void *data, calldata_t *params); + static void SourceUpdate(void *data, calldata_t *params); + static void SourceAddedToScene(void *data, calldata_t *params); + static void SourceRemovedFromScene(void *data, calldata_t *params); static void CheckModule(void *data, obs_module_t *module); + static bool AddSource(void *data, obs_source_t *source); + static bool LinkScenes(void *data, obs_source_t *source); + static bool LinkSceneItem(obs_scene_t *scene, + obs_sceneitem_t *sceneItem, void *data); private: + void _SetupSignals(); + void _TearDownSignals(); + void _SetCurrentSceneSources(); + void _AddChildren(QuickAccessSource *scene); + obs_module_t *_module = nullptr; std::vector _docks; + std::map> _allSources; + QuickAccessSource *_currentScene = nullptr; + std::vector _currentSceneSources; bool _firstRun; - bool _sceneCollectionChanging = false; + bool _sceneCollectionChanging = true; bool _sourceCloneInstalled = false; bool _dskInstalled = false; + + obs_hotkey_id _quick_search_hotkey_id; + + std::mutex _m; }; class CreateDockDialog : public QDialog { @@ -93,6 +134,24 @@ private slots: void on_cancel(); }; +class QuickAccessSearchModal : public QDialog { + Q_OBJECT +public: + QuickAccessSearchModal(QWidget *parent = nullptr); + ~QuickAccessSearchModal(); + static void OpenQuickSearch(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed); + static QuickAccessSearchModal *dialog; + void SearchFocus(); + void InitializeSearch(); + void SourceCreated(QuickAccessSource *source); + void SourceDestroyed(QuickAccessSource *source); + +private: + QLayout *_layout = nullptr; + QuickAccessDock *_widget = nullptr; +}; + class QuickAccessUtilityDialog : public QDialog { Q_OBJECT public: diff --git a/src/quick-access.cpp b/src/quick-access.cpp index 65304b8..9c2a726 100644 --- a/src/quick-access.cpp +++ b/src/quick-access.cpp @@ -1,8 +1,11 @@ +#if !defined(_WIN32) && !defined(__APPLE__) && defined(__x86_64__) +#include +#endif + #include "quick-access.hpp" #include "quick-access-dock.hpp" #include "quick-access-utility.hpp" -#include #include #include #include @@ -13,12 +16,16 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include +#include #include #include "version.h" @@ -26,473 +33,75 @@ #define QT_UTF8(str) QString::fromUtf8(str) #define QT_TO_UTF8(str) str.toUtf8().constData() -extern QuickAccessUtility *qau; - -QuickAccessList::QuickAccessList(QWidget *parent, QuickAccessDock *dock) - : QListWidget(parent), - _dock(dock) -{ - _qa = dynamic_cast(parent); -} - -void QuickAccessList::dropEvent(QDropEvent *event) -{ - QListWidget::dropEvent(event); - _qa->updateEnabled(); -} - -void QuickAccessList::mousePressEvent(QMouseEvent *event) -{ - QListWidget::mousePressEvent(event); - if (!indexAt(event->pos()).isValid()) { - clearSelection(); - } else { - if (_dock && _dock->ClickableScenes()) { - auto item = currentItem(); - auto widget = dynamic_cast( - itemWidget(item)); - widget->SwitchToScene(); - } - } -} +#define BROWSER_SOURCE_ID "browser_source" +#define IMAGE_SOURCE_ID "image_source" +#define MEDIA_SOURCE_ID "ffmpeg_source" -QuickAccessItem::QuickAccessItem(QWidget *parent, QuickAccessItem *original) - : QFrame(parent), - _dock(original->_dock), - _configurable(false) -{ - obs_source_t *source = obs_weak_source_get_source(original->_source); - _source = obs_source_get_weak_source(source); - obs_source_release(source); -} +extern QuickAccessUtility *qau; -QuickAccessItem::QuickAccessItem(QWidget *parent, QuickAccessDock *dock, - obs_source_t *source) - : QFrame(parent), - _dock(dock) +QuickAccessSourceList::QuickAccessSourceList(QWidget *parent, + SearchType searchType) + : QListView(parent), + _searchType(searchType), + _activeSearch(false), + _numActive(0) { - _source = obs_source_get_weak_source(source); - _configurable = obs_source_configurable(source); - const char *id = obs_source_get_id(source); - - setAttribute(Qt::WA_TranslucentBackground); + _qaParent = dynamic_cast(parent); + setContextMenuPolicy(Qt::ActionsContextMenu); setMouseTracking(true); - setStyleSheet("background: none"); - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = qau->GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = qau->GetGroupIcon(); - else - icon = qau->GetIconFromType(id); - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - _iconLabel = new QLabel(this); - _iconLabel->setPixmap(pixmap); - _iconLabel->setStyleSheet("background: none"); - - _label = new QLabel(this); - _label->setText(obs_source_get_name(source)); - _label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - _label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - _label->setAttribute(Qt::WA_TranslucentBackground); - - auto layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(_iconLabel); - layout->addSpacing(2); - layout->addWidget(_label); - - _actionProperties = new QPushButton(); - _actionProperties->setProperty("themeID", "propertiesIconSmall"); - _actionProperties->setDisabled(false); - _actionProperties->setAccessibleDescription( - "Opens the source properties window."); - _actionProperties->setAccessibleName("Open Source Properties"); - _actionProperties->setToolTip("Open Source Properties"); - _actionProperties->setStyleSheet("padding: 0px; background: none"); - connect(_actionProperties, &QPushButton::released, this, - &QuickAccessItem::on_actionProperties_triggered); - - _actionFilters = new QPushButton(); - _actionFilters->setProperty("themeID", "filtersIcon"); - _actionFilters->setDisabled(false); - _actionFilters->setAccessibleDescription( - "Opens the source filters window."); - _actionFilters->setAccessibleName("Open Source Filters"); - _actionFilters->setToolTip("Open Source Filters"); - _actionFilters->setStyleSheet("padding: 0px; background: none"); - connect(_actionFilters, &QPushButton::released, this, - &QuickAccessItem::on_actionFilters_triggered); - - _actionScenes = new QPushButton(); - QIcon sceneIcon; - sceneIcon = qau->GetSceneIcon(); - _actionScenes->setIcon(sceneIcon); - _actionScenes->setDisabled(false); - _actionScenes->setAccessibleDescription( - "Opens list of all parent scenes"); - _actionScenes->setAccessibleName("Show Parent Scenes"); - _actionScenes->setToolTip("Show Parent Scenes"); - _actionScenes->setStyleSheet("padding: 0px; background: none"); - connect(_actionScenes, &QPushButton::released, this, - &QuickAccessItem::on_actionScenes_triggered); - - layout->addWidget(_actionProperties); - layout->addWidget(_actionFilters); - layout->addWidget(_actionScenes); - - setLayout(layout); - SetButtonVisibility(); -} - -void QuickAccessItem::SetButtonVisibility() -{ - _actionProperties->setVisible(_configurable && _dock->ShowProperties()); - _actionFilters->setVisible(_dock->ShowFilters()); - _actionScenes->setVisible(_dock->ShowScenes()); -} - -const char *QuickAccessItem::GetSourceName() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - const char *name = obs_source_get_name(source); - obs_source_release(source); - return name; -} - -void QuickAccessItem::UpdateLabel() -{ - _label->setText(GetSourceName()); - repaint(); -} - -QuickAccessItem::~QuickAccessItem() -{ - delete _label; - delete _iconLabel; - delete _actionProperties; - delete _actionFilters; - delete _actionScenes; - _clearSceneItems(); - obs_weak_source_release(_source); -} - -void QuickAccessItem::Save(obs_data_t *itemObj) -{ - obs_source_t *source = obs_weak_source_get_source(_source); - if (!source) { - return; - } - - const char *sourceName = obs_source_get_name(source); - obs_data_set_string(itemObj, "source_name", sourceName); - - obs_source_release(source); -} - -void QuickAccessItem::on_actionProperties_triggered() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - if (!source) { - return; - } - obs_frontend_open_source_properties(source); - obs_source_release(source); -} - -void QuickAccessItem::on_actionFilters_triggered() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - if (!source) { - return; - } - obs_frontend_open_source_filters(source); - obs_source_release(source); -} - -void QuickAccessItem::_getSceneItems() -{ - _clearSceneItems(); - obs_enum_scenes(QuickAccessItem::GetSceneItemsFromScene, this); -} - -bool QuickAccessItem::GetSceneItemsFromScene(void *data, obs_source_t *s) -{ - obs_scene_t *scene = obs_scene_from_source(s); - obs_scene_enum_items(scene, QuickAccessItem::AddSceneItems, data); - return true; -} - -bool QuickAccessItem::AddSceneItems(obs_scene_t *scene, - obs_sceneitem_t *sceneItem, void *data) -{ - UNUSED_PARAMETER(scene); - - auto qai = static_cast(data); - auto source = obs_sceneitem_get_source(sceneItem); - if (obs_source_is_group(source)) { - obs_scene_t *group = obs_group_from_source(source); - obs_scene_enum_items(group, QuickAccessItem::AddSceneItems, - data); - } - if (obs_weak_source_references_source(qai->_source, source)) { - obs_sceneitem_addref(sceneItem); - qai->_sceneItems.push_back(sceneItem); - } - return true; -} - -bool QuickAccessItem::IsSource(obs_source_t *s) -{ - obs_source_t *source = obs_weak_source_get_source(_source); - bool ret = source == s; - obs_source_release(source); - return ret; -} - -bool QuickAccessItem::IsNullSource() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - bool ret = source == nullptr; - if (source) { - obs_source_release(source); - } - return ret; -} - -bool QuickAccessItem::IsInteractive() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - uint32_t flags = obs_source_get_output_flags(source); - obs_source_release(source); - return (flags & OBS_SOURCE_INTERACTION) == OBS_SOURCE_INTERACTION; -} - -void QuickAccessItem::AddToScene(obs_source_t *sceneSrc) -{ - obs_source_t *source = GetSource(); - obs_scene_t *scene = obs_scene_from_source(sceneSrc); - obs_scene_add(scene, source); - obs_source_release(source); -} - -void QuickAccessItem::OpenFilters() -{ - on_actionFilters_triggered(); -} - -void QuickAccessItem::OpenProperties() -{ - on_actionProperties_triggered(); -} - -void QuickAccessItem::OpenInteract() -{ - obs_source_t *source = GetSource(); - obs_frontend_open_source_interaction(source); - obs_source_release(source); -} - -QMenu *QuickAccessItem::_CreateSceneMenu() -{ - QMenu *popup = new QMenu("SceneMenu", this); - - auto wa = new QWidgetAction(popup); - auto t = new QLineEdit; - t->connect(t, &QLineEdit::textChanged, [popup](const QString text) { - foreach(auto action, popup->actions()) action->setVisible( - action->text().isEmpty() || - action->text().contains(text, Qt::CaseInsensitive)); - }); - wa->setDefaultWidget(t); - popup->addAction(wa); - popup->setStyleSheet("QMenu { menu-scrollable: 1; }"); - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare( - name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *pop, - obs_sceneitem_t *sceneItem) { - auto scene = obs_sceneitem_get_scene(sceneItem); - obs_source_t *sceneSource = obs_scene_get_source(scene); - const char *name = obs_source_get_name(sceneSource); - QString qname = name; - - QWidgetAction *popupItem = new QWidgetAction(this); - QWidget *itemWidget = new QuickAccessSceneItem(this, sceneItem); - popupItem->setDefaultWidget(itemWidget); - popupItem->setParent(pop); - - connect(popupItem, &QWidgetAction::triggered, this, - [this, name]() { - obs_source_t *sceneClicked = - obs_get_source_by_name(name); - if (obs_frontend_preview_program_mode_active()) { - obs_frontend_set_current_preview_scene( - sceneClicked); - } else { - obs_frontend_set_current_scene( - sceneClicked); - } - - obs_source_release(sceneClicked); - }); - - QAction *after = getActionAfter(pop, qname); - pop->insertAction(after, popupItem); - return true; - }; - for (auto &src : _sceneItems) { - addSource(popup, src); - } - - connect(popup, &QMenu::hovered, this, [popup](QAction *act) { - QList menuActions = - popup->findChildren(); - for (auto menuAction : menuActions) { - auto widget = static_cast( - menuAction->defaultWidget()); - widget->setHighlight(menuAction == act); - } - }); - - return popup; -} - -void QuickAccessItem::_AddScenePopupMenu(const QPoint &pos) -{ - QScopedPointer popup(_CreateSceneMenu()); - if (popup) { - popup->exec(pos); - } + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectItems); + setProperty("showDropIndicator", QVariant(true)); + setDragEnabled(false); + setAcceptDrops(false); + setDragDropMode(QAbstractItemView::InternalMove); + setDefaultDropAction(Qt::TargetMoveAction); + _setupContextMenu(); + //setAttribute(Qt::WA_TranslucentBackground); } -void QuickAccessItem::_clearSceneItems() +void QuickAccessSourceList::_setupContextMenu() { - for (auto &sceneItem : _sceneItems) { - obs_sceneitem_release(sceneItem); - } - _sceneItems.clear(); -} - -void QuickAccessItem::on_actionScenes_triggered() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - if (!source) { - return; - } - _getSceneItems(); - _AddScenePopupMenu(QCursor::pos()); - obs_source_release(source); -} - -void QuickAccessItem::SwitchToScene() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - if (obs_source_is_scene(source)) { - if (obs_frontend_preview_program_mode_active()) { - obs_frontend_set_current_preview_scene(source); - } else { - obs_frontend_set_current_scene(source); - } - } - - obs_source_release(source); -} - -bool QuickAccessItem::Configurable() -{ - obs_source_t *source = obs_weak_source_get_source(_source); - bool configurable = obs_source_configurable(source); - obs_source_release(source); - return configurable; -} - -void QuickAccessItem::RenameSource(std::string name) -{ - obs_source_t *source = obs_weak_source_get_source(_source); - obs_source_set_name(source, name.c_str()); - obs_source_release(source); -} - -QuickAccess::QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name) - : QWidget(parent), - _dock(dock) -{ - setObjectName(name); - auto layout = new QVBoxLayout(this); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); - - _sourceList = new QuickAccessList(this, _dock); - _sourceList->setObjectName(QStringLiteral("sources")); - QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth( - _sourceList->sizePolicy().hasHeightForWidth()); - _sourceList->setSizePolicy(sizePolicy); - _sourceList->setContextMenuPolicy(Qt::CustomContextMenu); - _sourceList->setFrameShape(QFrame::NoFrame); - _sourceList->setFrameShadow(QFrame::Plain); - _sourceList->setProperty("showDropIndicator", QVariant(true)); - if (_dock->GetType() == "Manual") { - _sourceList->setDragEnabled(true); - _sourceList->setDragDropMode(QAbstractItemView::InternalMove); - _sourceList->setDefaultDropAction(Qt::TargetMoveAction); - } else { - _sourceList->setDragEnabled(false); - _sourceList->setDragDropMode(QAbstractItemView::NoDragDrop); - _sourceList->viewport()->setAcceptDrops(false); - } - - connect(_sourceList, SIGNAL(itemSelectionChanged()), this, - SLOT(on_sourceList_itemSelectionChanged())); - - _sourceList->setContextMenuPolicy(Qt::ActionsContextMenu); - - _actionCtxtAddCurrent = new QAction(_sourceList); + _actionCtxtAddCurrent = new QAction(this); _actionCtxtAddCurrent->setText("Add to Current Scene"); connect(_actionCtxtAddCurrent, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - obs_source_t *sceneSrc = obs_frontend_get_current_scene(); - widget->AddToScene(sceneSrc); - obs_source_release(sceneSrc); + auto source = currentSource(); + obs_source_t *src = source->get(); + + bool studio = obs_frontend_preview_program_mode_active(); + obs_source_t *currentScene = + studio ? obs_frontend_get_current_preview_scene() + : obs_frontend_get_current_scene(); + + obs_scene_t *scene = obs_scene_from_source(currentScene); + obs_scene_add(scene, src); + obs_source_release(src); + obs_source_release(currentScene); + _qaParent->DismissModal(); }); - _sourceList->addAction(_actionCtxtAddCurrent); + addAction(_actionCtxtAddCurrent); - _actionCtxtAddCurrentClone = new QAction(_sourceList); + _actionCtxtAddCurrentClone = new QAction(this); _actionCtxtAddCurrentClone->setText("Add Clone to Current Scene"); connect(_actionCtxtAddCurrentClone, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - obs_source_t *sceneSrc = obs_frontend_get_current_scene(); + auto source = currentSource(); + bool studio = obs_frontend_preview_program_mode_active(); + obs_source_t *sceneSrc = + studio ? obs_frontend_get_current_preview_scene() + : obs_frontend_get_current_scene(); const char *sourceCloneId = "source-clone"; const char *vId = obs_get_latest_input_type_id(sourceCloneId); - blog(LOG_INFO, "Source Clone v_id is: %s", vId); - std::string sourceName = widget->GetSourceName(); + std::string sourceName = source->getName(); std::string newSourceName = sourceName + " CLONE"; // Pop open QDialog to ask for new cloned source name. bool ok; - QString text = QInputDialog::getText( - this, "Name of new source", - "Clone Name:", QLineEdit::Normal, "Testing...", &ok); + QString text = + QInputDialog::getText(this, "Name of new source", + "Clone Name:", QLineEdit::Normal, + newSourceName.c_str(), &ok); if (ok && !text.isEmpty()) { newSourceName = text.toStdString(); } else { @@ -511,36 +120,32 @@ QuickAccess::QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name) obs_source_release(sceneSrc); obs_source_release(newSource); + _qaParent->DismissModal(); }); - _sourceList->addAction(_actionCtxtAddCurrentClone); + addAction(_actionCtxtAddCurrentClone); - _actionCtxtProperties = new QAction(_sourceList); + _actionCtxtProperties = new QAction(this); _actionCtxtProperties->setText("Properties"); connect(_actionCtxtProperties, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - widget->OpenProperties(); + auto source = currentSource(); + source->openProperties(); }); - _sourceList->addAction(_actionCtxtProperties); + addAction(_actionCtxtProperties); - _actionCtxtFilters = new QAction(_sourceList); + _actionCtxtFilters = new QAction(this); _actionCtxtFilters->setText("Filters"); connect(_actionCtxtFilters, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - widget->OpenFilters(); + auto source = currentSource(); + source->openFilters(); }); - _sourceList->addAction(_actionCtxtFilters); + addAction(_actionCtxtFilters); - _actionCtxtRenameSource = new QAction(_sourceList); + _actionCtxtRenameSource = new QAction(this); _actionCtxtRenameSource->setText("Rename Source"); connect(_actionCtxtRenameSource, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - std::string currentName = widget->GetSourceName(); + auto source = currentSource(); + + std::string currentName = source->getName(); bool ok; QString text = QInputDialog::getText( @@ -548,69 +153,225 @@ QuickAccess::QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name) currentName.c_str(), &ok); if (ok && !text.isEmpty()) { std::string newSourceName = text.toStdString(); - widget->RenameSource(newSourceName); + source->rename(newSourceName); } else { return; } }); - _sourceList->addAction(_actionCtxtRenameSource); + addAction(_actionCtxtRenameSource); - _actionCtxtInteract = new QAction(_sourceList); + _actionCtxtInteract = new QAction(this); _actionCtxtInteract->setText("Interact"); connect(_actionCtxtInteract, &QAction::triggered, this, [this]() { - QListWidgetItem *item = _sourceList->currentItem(); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - widget->OpenInteract(); + auto source = currentSource(); + source->openInteract(); }); - _sourceList->addAction(_actionCtxtInteract); + addAction(_actionCtxtInteract); - if (_dock->GetType() == "Manual") { - _actionCtxtAdd = new QAction(_sourceList); - _actionCtxtAdd->setText("Add Sources"); - connect(_actionCtxtAdd, &QAction::triggered, this, - [this]() { on_actionAddSource_triggered(); }); - _sourceList->addAction(_actionCtxtAdd); - - _actionCtxtRemoveFromDock = new QAction(_sourceList); - _actionCtxtRemoveFromDock->setText("Remove From Dock"); - connect(_actionCtxtRemoveFromDock, &QAction::triggered, this, - [this]() { on_actionRemoveSource_triggered(); }); - _sourceList->addAction(_actionCtxtRemoveFromDock); + _actionCtxtRefresh = new QAction(this); + _actionCtxtRefresh->setText("Refresh"); + connect(_actionCtxtRefresh, &QAction::triggered, this, [this]() { + auto source = currentSource(); + source->refreshBrowser(); + }); + addAction(_actionCtxtRefresh); + + _actionCtxtToggleActivation = new QAction(this); + _actionCtxtToggleActivation->setText("(De)Activate"); + connect(_actionCtxtToggleActivation, &QAction::triggered, this, + [this]() { + auto source = currentSource(); + source->toggleActivation(); + }); + addAction(_actionCtxtToggleActivation); +} + +QuickAccessSource *QuickAccessSourceList::currentSource() +{ + QModelIndex index = currentIndex(); + auto m = dynamic_cast(model()); + return m->item(index.row()); +} + +void QuickAccessSourceList::mousePressEvent(QMouseEvent *event) +{ + auto idx = indexAt(event->pos()); + if (idx.isValid()) { + setCurrentIndex(idx); + _qaParent->ClearSelections(this); + // Check if source is interactive + auto sourceModel = + dynamic_cast(model()); + if (sourceModel) { + auto source = sourceModel->item(idx.row()); + _actionCtxtInteract->setVisible(source && + source->hasInteract()); + _actionCtxtProperties->setVisible( + source && source->hasProperties()); + _actionCtxtRefresh->setVisible(source && + source->hasRefresh()); + auto activate = source->activeState(); + if (activate != "") { + _actionCtxtToggleActivation->setText( + activate.c_str()); + } + _actionCtxtToggleActivation->setVisible(activate != ""); + } + } + QListView::mousePressEvent(event); +} + +QSize QuickAccessSourceList::sizeHint() const +{ + QMargins margins = contentsMargins(); + int rows = _activeSearch ? _numActive : model()->rowCount(); + if (rows == 0) + return QSize(width(), 0); + //int minHeight = minimumHeight(); + int height = + rows * sizeHintForRow(0) + margins.top() + margins.bottom(); + return QSize(width(), height); +} + +void QuickAccessSourceList::search(std::string searchTerm) +{ + auto m = dynamic_cast(model()); + if (!m) { + return; + } + int rows = m->rowCount(); + if (searchTerm == "") { + _activeSearch = false; + for (int i = 0; i < rows; i++) { + setRowHidden(i, false); + } + _numActive = rows; + } else { + _activeSearch = true; + _numActive = 0; + setUpdatesEnabled(false); + for (int i = 0; i < rows; i++) { + auto source = m->item(i); + bool match = source->hasMatch(searchTerm, _searchType); + if (match) { + _numActive++; + } + setRowHidden(i, !(match)); + } + setUpdatesEnabled(true); + } + updateGeometry(); +} + +QuickAccess::QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name) + : QWidget(parent), + _dock(dock) +{ + std::string imageBaseDir = + obs_get_module_data_path(obs_current_module()); + imageBaseDir += "/images/"; + setObjectName(name); + auto layout = new QVBoxLayout(this); + setAttribute(Qt::WA_TranslucentBackground); + layout->setSpacing(0); + layout->setContentsMargins(2, 2, 2, 2); + + _listsContainer = new QScrollArea(this); + _createListContainer(); + + _emptySearch = new QWidget(this); + QLabel *emptySearchLabel = new QLabel(this); + QLabel *searchImage = new QLabel(this); + std::string imgPath = imageBaseDir + "magnifying-glass-solid-white.svg"; + QPixmap magImgPixmap = QIcon(imgPath.c_str()).pixmap(QSize(48, 48)); + searchImage->setPixmap(magImgPixmap); + searchImage->setAlignment(Qt::AlignCenter); + + emptySearchLabel->setText( + "Type in the search bar above to search for either a source name, source type, filter name, filter type, file path, or URL."); + emptySearchLabel->setWordWrap(true); + emptySearchLabel->setAlignment(Qt::AlignCenter); + //emptySearchLabel->setStyleSheet("QLabel {font-size: 18pt;}"); + QVBoxLayout *emptySearchLayout = new QVBoxLayout(); + QWidget *spacer1 = new QWidget(this); + QWidget *spacer2 = new QWidget(this); + spacer1->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + spacer2->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + emptySearchLayout->addWidget(spacer1); + emptySearchLayout->addWidget(searchImage); + emptySearchLayout->addWidget(emptySearchLabel); + emptySearchLayout->addWidget(spacer2); + _emptySearch->setLayout(emptySearchLayout); + + _emptyManual = new QWidget(this); + QLabel *emptyManualLabel = new QLabel(this); + emptyManualLabel->setText("Add a source..."); + QVBoxLayout *emptyManualLayout = new QVBoxLayout(); + emptyManualLayout->addWidget(emptyManualLabel); + _emptyManual->setLayout(emptyManualLayout); + + _emptyDynamic = new QWidget(this); + QLabel *emptyDynamicLabel = new QLabel(this); + emptyDynamicLabel->setText("No sources in the current scene..."); + QVBoxLayout *emptyDynamicLayout = new QVBoxLayout(); + emptyDynamicLayout->addWidget(emptyDynamicLabel); + _emptyDynamic->setLayout(emptyDynamicLayout); + + _noSearchResults = new QWidget(this); + QLabel *noSearchResultsLabel = new QLabel(this); + noSearchResultsLabel->setText("No sources found matching search..."); + QVBoxLayout *noSearchResultsLayout = new QVBoxLayout(); + noSearchResultsLayout->addWidget(noSearchResultsLabel); + _noSearchResults->setLayout(noSearchResultsLayout); + + _contents = new QStackedWidget(this); + _contents->addWidget(_listsContainer); //0 + _contents->addWidget(_emptySearch); //1 + _contents->addWidget(_emptyManual); //2 + _contents->addWidget(_emptyDynamic); //3 + _contents->addWidget(_noSearchResults); //4 + if (_dock->GetType() == "Source Search") { + _contents->setCurrentIndex(1); } std::string dockType = _dock->GetType(); - if (dockType == "Source Search" || dockType == "Dynamic") { + if (dockType == "Source Search" /* || dockType == "Dynamic"*/) { _searchText = new QLineEdit; _searchText->setPlaceholderText("Search..."); + _searchText->setClearButtonEnabled(true); + _searchText->setFocusPolicy(Qt::StrongFocus); _searchText->connect( _searchText, &QLineEdit::textChanged, [this, dockType](const QString text) { - for (int i = 0; i < _sourceList->count(); i++) { - QListWidgetItem *item = - _sourceList->item(i); - QuickAccessItem *widget = - dynamic_cast( - _sourceList->itemWidget( - item)); - if (widget) { - QString wName = - widget->GetSourceName(); - item->setHidden( - (text.isEmpty() && - dockType == - "Source Search") || - !wName.contains( - text, - Qt::CaseInsensitive)); + if (dockType == "Source Search") { + _noSearch = text.size() == 0; + int totalMatches = 0; + for (auto &qa : _qaLists) { + qa.listView->search( + text.toStdString()); + int numMatches = + qa.listView + ->visibleCount(); + totalMatches += numMatches; + qa.listView->setHidden( + numMatches == 0); + qa.headerLabel->setHidden( + numMatches == 0); + } + if (_noSearch) { + _contents->setCurrentIndex(1); + } else if (totalMatches == 0) { + _contents->setCurrentIndex(4); + } else { + _contents->setCurrentIndex(0); } } }); layout->addWidget(_searchText); } - layout->addWidget(_sourceList); + layout->addWidget(_contents); _actionsToolbar = new QToolBar(this); _actionsToolbar->setObjectName(QStringLiteral("actionsToolbar")); _actionsToolbar->setIconSize(QSize(16, 16)); @@ -689,212 +450,343 @@ QuickAccess::QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name) layout->addWidget(_actionsToolbar); layout->addItem(new QSpacerItem(150, 0, QSizePolicy::Fixed, QSizePolicy::Minimum)); - - if (_dock->GetType() == "Dynamic") { - QuickAccess::SceneChangeCallback( - OBS_FRONTEND_EVENT_SCENE_CHANGED, this); - obs_frontend_add_event_callback( - QuickAccess::SceneChangeCallback, this); - } - - on_sourceList_itemSelectionChanged(); } QuickAccess::~QuickAccess() { blog(LOG_INFO, "QuickAccess::~QuickAccess() called (Dock was destroyed)"); - obs_frontend_remove_event_callback(QuickAccess::SceneChangeCallback, - this); if (_current) { obs_weak_source_release(_current); } + _clearSceneItems(); source_signal_handler = nullptr; } -void QuickAccess::CleanupSourceHandlers() -{ - if (source_signal_handler) { - blog(LOG_INFO, "QuickAccess::CleanupSourceHandlers()"); - signal_handler_disconnect(source_signal_handler, "item_add", - QuickAccess::ItemAddedToScene, this); - signal_handler_disconnect(source_signal_handler, "item_remove", - QuickAccess::ItemRemovedFromScene, - this); - source_signal_handler = nullptr; +void QuickAccess::_createListContainer() +{ + _qaLists.clear(); + _sourceModels.clear(); + for (auto &dg : _dock->DisplayGroups()) { + // Shouldn't this be, no auto model line, then + // std::make_unique<...>() ? + // Why are we making a pointer to then copy it? + //auto model = new QuickAccessSourceModel(); + _sourceModels.emplace_back( + std::make_unique()); + auto model = _sourceModels[_sourceModels.size() - 1].get(); + _qaLists.push_back( + {dg.name, nullptr, + new QuickAccessSourceList(this, dg.searchType), + model}); + model->setSources(&dg.sources2); + } + + auto widget = _listsContainer->widget(); + if (widget) { + delete widget; + } + _listsContainer->setStyleSheet("background: transparent"); + _listsContainer->setWidgetResizable(true); + _listsContainer->setAttribute(Qt::WA_TranslucentBackground); + auto listsWidget = new QWidget(); + auto lcLayout = new QVBoxLayout(); + int i = 0; + for (auto &qa : _qaLists) { + auto header = new QLabel(); + header->setText(qa.header.c_str()); + header->setStyleSheet("QLabel { font-size: 18px }"); + header->setSizePolicy(QSizePolicy::Preferred, + QSizePolicy::Fixed); + header->setHidden(qa.header == "Manual"); + qa.headerLabel = header; + lcLayout->addWidget(header); + qa.listView->setModel(qa.model); + qa.listView->setSizePolicy(QSizePolicy::Preferred, + QSizePolicy::Fixed); + qa.listView->setMinimumHeight(0); + QuickAccessSourceDelegate *itemDelegate = + new QuickAccessSourceDelegate(qa.listView, _dock); + qa.listView->setItemDelegate(itemDelegate); + connect(itemDelegate, + &QuickAccessSourceDelegate::openPropertiesClicked, this, + [this](const QModelIndex &index) { + const QuickAccessSourceModel *model = + dynamic_cast< + const QuickAccessSourceModel *>( + index.model()); + QuickAccessSource *source = + model->item(index.row()); + source->openProperties(); + }); + connect(itemDelegate, + &QuickAccessSourceDelegate::openFiltersClicked, this, + [this, qa](const QModelIndex &index) { + const QuickAccessSourceModel *model = + dynamic_cast< + const QuickAccessSourceModel *>( + index.model()); + QuickAccessSource *source = + model->item(index.row()); + source->openFilters(); + }); + connect(itemDelegate, + &QuickAccessSourceDelegate::openParentScenesClicked, + this, [this, qa](const QModelIndex &index) { + const QuickAccessSourceModel *model = + dynamic_cast< + const QuickAccessSourceModel *>( + index.model()); + QuickAccessSource *source = + model->item(index.row()); + _currentSource = source; + _getSceneItems(); + auto pos = QCursor::pos(); + QScopedPointer popup( + _CreateParentSceneMenu()); + if (popup) { + popup->exec(pos); + } + }); + connect(itemDelegate, &QuickAccessSourceDelegate::itemSelected, + this, [this, i](const QModelIndex &index) { + if (_dock->GetType() != "Manual") { + return; + } + _activeIndex = i; + size_t numRows = index.model()->rowCount(); + size_t currentRow = index.row(); + _actionRemoveSource->setEnabled(true); + _actionSourceUp->setEnabled(currentRow > 0); + _actionSourceDown->setEnabled(currentRow < + numRows - 1); + + for (auto x : _actionsToolbar->actions()) { + auto wdgt = + _actionsToolbar->widgetForAction( + x); + + if (!wdgt) { + continue; + } + wdgt->style()->unpolish(wdgt); + wdgt->style()->polish(wdgt); + } + }); + //if (_dock->ClickableScenes()) { + connect(itemDelegate, &QuickAccessSourceDelegate::activateScene, + this, [this](const QModelIndex &index) { + const QuickAccessSourceModel *model = + dynamic_cast< + const QuickAccessSourceModel *>( + index.model()); + QuickAccessSource *source = + model->item(index.row()); + source->activateScene(); + }); + //} + lcLayout->addWidget(qa.listView); + ++i; } + + QWidget *lSpacer = new QWidget(this); + lSpacer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + lcLayout->addWidget(lSpacer); + listsWidget->setLayout(lcLayout); + _listsContainer->setWidget(listsWidget); } -void QuickAccess::SceneChangeCallback(enum obs_frontend_event event, void *data) +void QuickAccess::paintEvent(QPaintEvent *) { - QuickAccess *qa = static_cast(data); - QMetaObject::invokeMethod( - QCoreApplication::instance()->thread(), [qa, event]() { - if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) { - blog(LOG_INFO, "SCENE CHANGE!"); - qa->CleanupSourceHandlers(); - obs_source_t *current = - obs_frontend_get_current_scene(); - if (qa->_current) { - obs_weak_source_release(qa->_current); - } - qa->_current = - obs_source_get_weak_source(current); - qa->source_signal_handler = - obs_source_get_signal_handler(current); - signal_handler_connect( - qa->source_signal_handler, "item_add", - QuickAccess::ItemAddedToScene, qa); - signal_handler_connect( - qa->source_signal_handler, - "item_remove", - QuickAccess::ItemRemovedFromScene, qa); - obs_source_release(current); - qa->_LoadDynamicScenes(); - } else if (event == - OBS_FRONTEND_EVENT_FINISHED_LOADING) { - blog(LOG_INFO, "FINISHED LOADING..."); - // Load the scene list if is a search bar. - qa->_active = true; - } else if (event == OBS_FRONTEND_EVENT_EXIT) { - blog(LOG_INFO, "EXITING..."); - qa->_active = false; - } else if (event == - OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING) { - blog(LOG_INFO, "SCENE COLLECTION CHANGING"); - qa->_dock->SwitchingSceneCollections(true); - qa->_active = false; - qa->CleanupSourceHandlers(); - } - }); + QStyleOption opt; + opt.initFrom(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } -void QuickAccess::_LoadDynamicScenes() +void QuickAccess::SearchFocus() { - if (!_active || !_sourceList) { - return; + if (_searchText) { + _searchText->setFocus(); + _searchText->selectAll(); } - _dynamicScenes.clear(); - _sourceList->clear(); - - //obs_source_t *dsk = obs_get_output_source(8); - //const char *dsk_name = obs_source_get_name(dsk); - //obs_source_release(dsk); - obs_source_t *current = obs_weak_source_get_source(_current); - obs_scene_t *currentScene = obs_scene_from_source(current); - obs_scene_enum_items(currentScene, QuickAccess::DynAddSceneItems, this); - for (auto &sceneName : _dynamicScenes) { - AddSource(sceneName.c_str()); - } - obs_source_release(current); } -bool QuickAccess::DynAddSceneItems(obs_scene_t *scene, - obs_sceneitem_t *sceneItem, void *data) +void QuickAccess::ClearSelections(QuickAccessSourceList *skip) { - UNUSED_PARAMETER(scene); - QuickAccess *qa = static_cast(data); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - std::string sourceName = obs_source_get_name(source); - if (qa->_dynamicScenes.count(sourceName) == 0) { - qa->_dynamicScenes.insert(sourceName); - if (obs_source_is_scene(source)) { - obs_scene_t *currentScene = - obs_scene_from_source(source); - obs_scene_enum_items(currentScene, - QuickAccess::DynAddSceneItems, - data); - } else if (obs_source_is_group(source)) { - obs_scene_t *currentGroup = - obs_group_from_source(source); - obs_scene_enum_items(currentGroup, - QuickAccess::DynAddSceneItems, - data); + for (auto &qa : _qaLists) { + if (qa.listView != skip) { + qa.listView->clearSelection(); } } +} + +void QuickAccess::_clearSceneItems() +{ + for (auto &sceneItem : _sceneItems) { + obs_sceneitem_release(sceneItem); + } + _sceneItems.clear(); +} + +void QuickAccess::_getSceneItems() +{ + _clearSceneItems(); + obs_enum_scenes(QuickAccess::GetSceneItemsFromScene, this); +} + +bool QuickAccess::GetSceneItemsFromScene(void *data, obs_source_t *s) +{ + obs_scene_t *scene = obs_scene_from_source(s); + obs_scene_enum_items(scene, QuickAccess::AddSceneItems, data); return true; } -void QuickAccess::ItemAddedToScene(void *data, calldata_t *params) +bool QuickAccess::AddSceneItems(obs_scene_t *scene, obs_sceneitem_t *sceneItem, + void *data) { - blog(LOG_INFO, "Item added to scene"); - UNUSED_PARAMETER(params); - QuickAccess *qa = static_cast(data); - QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), - [qa]() { qa->_LoadDynamicScenes(); }); + UNUSED_PARAMETER(scene); + + auto qa = static_cast(data); + auto source = obs_sceneitem_get_source(sceneItem); + if (obs_source_is_group(source)) { + obs_scene_t *group = obs_group_from_source(source); + obs_scene_enum_items(group, QuickAccess::AddSceneItems, data); + } + if (qa->_currentSource->isSource(source)) { + obs_sceneitem_addref(sceneItem); + qa->_sceneItems.push_back(sceneItem); + } + return true; } -void QuickAccess::ItemRemovedFromScene(void *data, calldata_t *params) +QMenu *QuickAccess::_CreateParentSceneMenu() { - QuickAccess *qa = static_cast(data); - blog(LOG_INFO, "Item removed from scene"); - UNUSED_PARAMETER(params); - QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), - [qa]() { - if (qa->_active) { - qa->_LoadDynamicScenes(); - } - }); + QMenu *popup = new QMenu("SceneMenu", this); + + auto wa = new QWidgetAction(popup); + auto t = new QLineEdit; + t->connect(t, &QLineEdit::textChanged, [popup](const QString text) { + foreach(auto action, popup->actions()) action->setVisible( + action->text().isEmpty() || + action->text().contains(text, Qt::CaseInsensitive)); + }); + wa->setDefaultWidget(t); + popup->addAction(wa); + popup->setStyleSheet("QMenu { menu-scrollable: 1; }"); + auto getActionAfter = [](QMenu *menu, const QString &name) { + QList actions = menu->actions(); + + for (QAction *menuAction : actions) { + if (menuAction->text().compare( + name, Qt::CaseInsensitive) >= 0) + return menuAction; + } + + return (QAction *)nullptr; + }; + + auto addSource = [this, getActionAfter](QMenu *pop, + obs_sceneitem_t *sceneItem) { + auto scene = obs_sceneitem_get_scene(sceneItem); + obs_source_t *sceneSource = obs_scene_get_source(scene); + const char *name = obs_source_get_name(sceneSource); + QString qname = name; + + QWidgetAction *popupItem = new QWidgetAction(this); + QWidget *itemWidget = new QuickAccessSceneItem(this, sceneItem); + popupItem->setDefaultWidget(itemWidget); + popupItem->setParent(pop); + + connect(popupItem, &QWidgetAction::triggered, this, + [this, name]() { + obs_source_t *sceneClicked = + obs_get_source_by_name(name); + if (obs_frontend_preview_program_mode_active()) { + obs_frontend_set_current_preview_scene( + sceneClicked); + } else { + obs_frontend_set_current_scene( + sceneClicked); + } + + obs_source_release(sceneClicked); + }); + + QAction *after = getActionAfter(pop, qname); + pop->insertAction(after, popupItem); + return true; + }; + for (auto &src : _sceneItems) { + addSource(popup, src); + } + + connect(popup, &QMenu::hovered, this, [popup](QAction *act) { + QList menuActions = + popup->findChildren(); + for (auto menuAction : menuActions) { + auto widget = static_cast( + menuAction->defaultWidget()); + widget->setHighlight(menuAction == act); + } + }); + + return popup; } void QuickAccess::Save(obs_data_t *saveObj) { - auto itemsArr = obs_data_array_create(); + blog(LOG_INFO, "SAVING"); if (_dock->GetType() == "Manual") { - for (int i = 0; i < _sourceList->count(); ++i) { + auto itemsArr = obs_data_array_create(); + auto &displayGroups = _dock->DisplayGroups(); + if (displayGroups.size() == 0) { + return; + } + auto &dg = displayGroups[0]; + for (auto &source : dg.sources2) { auto itemObj = obs_data_create(); - QListWidgetItem *item = _sourceList->item(i); - QuickAccessItem *widget = - dynamic_cast( - _sourceList->itemWidget(item)); - widget->Save(itemObj); + source->save(itemObj); obs_data_array_push_back(itemsArr, itemObj); obs_data_release(itemObj); } + obs_data_set_array(saveObj, "dock_sources", itemsArr); + obs_data_array_release(itemsArr); } - obs_data_set_array(saveObj, "dock_sources", itemsArr); - obs_data_array_release(itemsArr); } -void QuickAccess::Load(obs_data_t *loadObj) +void QuickAccess::Redraw() { - if (_dock->GetType() == "Manual") { - obs_data_array_t *items = - obs_data_get_array(loadObj, "dock_sources"); - auto numItems = obs_data_array_count(items); - for (size_t i = 0; i < numItems; i++) { - auto item = obs_data_array_item(items, i); - auto sourceName = - obs_data_get_string(item, "source_name"); - AddSource(sourceName); - obs_data_release(item); - } - obs_data_array_release(items); - } else if (_dock->GetType() == "Source Search") { - // Get all sources/scenes/groups - LoadAllSources(); + for (auto &qa : _qaLists) { + qa.listView->repaint(); } } -void QuickAccess::LoadAllSources() +void QuickAccess::Load() { - _allSourceNames.clear(); - _sourceList->clear(); - obs_enum_sources(QuickAccess::AddSourceName, this); - obs_enum_scenes(QuickAccess::AddSourceName, this); - for (auto &name : _allSourceNames) { - AddSource(name.c_str(), true); + if (_dock->GetType() == "Dynamic") { + _createListContainer(); + } +} + +void QuickAccess::UpdateVisibility() +{ + bool active = false; + for (auto &group : _dock->DisplayGroups()) { + group.headerItem->setHidden(!group.headerVisible); + if (group.headerVisible) { + active = true; + } + for (auto &source : group.sources) { + source.listItem->setHidden(!source.visible); + } } - QString text = _searchText->text(); - for (int i = 0; i < _sourceList->count(); i++) { - QListWidgetItem *item = _sourceList->item(i); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(item)); - QString name = widget->GetSourceName(); - item->setHidden(text.isEmpty() || - !name.contains(text, Qt::CaseInsensitive)); + if (_noSearch) { + _contents->setCurrentIndex(1); + return; } + _contents->setCurrentIndex(active ? 0 : 4); } bool QuickAccess::AddSourceName(void *data, obs_source_t *source) @@ -905,35 +797,39 @@ bool QuickAccess::AddSourceName(void *data, obs_source_t *source) return true; } -void QuickAccess::SetItemsButtonVisibility() +void QuickAccess::AddSource(QuickAccessSource *source, std::string groupName) { - for (int i = 0; i < _sourceList->count(); i++) { - QListWidgetItem *item = _sourceList->item(i); - auto widget = dynamic_cast( - _sourceList->itemWidget(item)); - widget->SetButtonVisibility(); + auto it = std::find_if( + _qaLists.begin(), _qaLists.end(), + [groupName](QuickAccessSourceListView const &list) { + return list.header == groupName; + }); + if (it != _qaLists.end()) { + // Add the new thing. + it->model->addSource(source); + it->listView->setVisible(true); + it->listView->updateGeometry(); } } -void QuickAccess::AddSource(const char *sourceName, bool hidden) +void QuickAccess::RemoveSource(QuickAccessSource *source, std::string groupName) { - obs_source_t *source = obs_get_source_by_name(sourceName); - auto item = new QListWidgetItem(); - item->setHidden(hidden); - _sourceList->addItem(item); - auto row = new QuickAccessItem(this, _dock, source); - _sourceList->setItemWidget(item, row); - obs_source_release(source); + auto it = std::find_if( + _qaLists.begin(), _qaLists.end(), + [groupName](QuickAccessSourceListView const &list) { + return list.header == groupName; + }); + if (it != _qaLists.end()) { + it->model->removeSource(source); + it->listView->updateGeometry(); + } } -void QuickAccess::AddSourceAtIndex(const char *sourceName, int index) +void QuickAccess::SetItemsButtonVisibility() { - obs_source_t *source = obs_get_source_by_name(sourceName); - auto item = new QListWidgetItem(); - _sourceList->insertItem(index, item); - auto row = new QuickAccessItem(this, _dock, source); - _sourceList->setItemWidget(item, row); - obs_source_release(source); + for (auto &qa : _qaLists) { + qa.listView->repaint(); + } } void QuickAccess::AddSourceMenuItem(obs_source_t *source) @@ -946,6 +842,12 @@ void QuickAccess::_ClearMenuSources() _menuSources.clear(); } +void QuickAccess::DismissModal() +{ + if (_dock) + _dock->DismissModal(); +} + QDialog *QuickAccess::CreateAddSourcePopupMenu() { QDialog *popup = new QDialog(this); @@ -1056,12 +958,9 @@ QDialog *QuickAccess::CreateAddSourcePopupMenu() }; _manualSourceNames.clear(); - for (int i = 0; i < _sourceList->count(); i++) { - auto item = _sourceList->item(i); - auto widget = dynamic_cast( - _sourceList->itemWidget(item)); - const char *sourceName = widget->GetSourceName(); - _manualSourceNames.push_back(sourceName); + // TODO: Get list of current manual source names + for (auto &source : _dock->DisplayGroups()[0].sources2) { + _manualSourceNames.push_back(source->getName()); } for (auto &src : _menuSources) { @@ -1161,7 +1060,12 @@ QDialog *QuickAccess::CreateAddSourcePopupMenu() auto sceneName = item->text(); std::string sceneNameStr = sceneName.toStdString(); - AddSourceAtIndex(sceneNameStr.c_str(), i); + obs_source_t *source = obs_get_source_by_name( + sceneNameStr.c_str()); + std::string uuid = obs_source_get_uuid(source); + auto qaSource = qau->GetSource(uuid); + obs_source_release(source); + _dock->AddSource(qaSource, i); } popup->accept(); }); @@ -1185,13 +1089,14 @@ void QuickAccess::on_actionAddSource_triggered() void QuickAccess::on_actionRemoveSource_triggered() { - auto item = _sourceList->currentItem(); - if (!item) + auto &qa = _qaLists[_activeIndex]; + + auto source = qa.listView->currentSource(); + + if (!source) return; - QuickAccessItem *widget = - dynamic_cast(_sourceList->itemWidget(item)); - std::string sourceName = widget->GetSourceName(); + std::string sourceName = source->getName(); std::string dockName = _dock->GetName(); std::string message = "Are you sure you want to remove " + sourceName + " from " + dockName + "?"; @@ -1202,9 +1107,7 @@ void QuickAccess::on_actionRemoveSource_triggered() confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No); int ret = confirm.exec(); if (ret == QMessageBox::Yes) { - _sourceList->setCurrentItem(nullptr); - _sourceList->removeItemWidget(item); - delete item; + _dock->RemoveSource(source); } } @@ -1218,53 +1121,18 @@ void QuickAccess::on_actionDockProperties_triggered() void QuickAccess::on_actionSourceUp_triggered() { - int index = _sourceList->currentRow(); - if (index == -1) { - return; - } - _sourceList->blockSignals(true); - - QListWidgetItem *widgetItem = _sourceList->item(index); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(widgetItem)); - const char *sourceName = widget->GetSourceName(); - QListWidgetItem *toDelete = _sourceList->takeItem(index); - AddSourceAtIndex(sourceName, index - 1); - _sourceList->blockSignals(false); - _sourceList->setCurrentRow(index - 1); - delete toDelete; -} - -void QuickAccess::on_actionSourceDown_triggered() -{ - int index = _sourceList->currentRow(); - if (index == -1) { + if (_qaLists.size() == 0) { return; } - _sourceList->blockSignals(true); - - QListWidgetItem *widgetItem = _sourceList->item(index); - QuickAccessItem *widget = dynamic_cast( - _sourceList->itemWidget(widgetItem)); - const char *sourceName = widget->GetSourceName(); - QListWidgetItem *toDelete = _sourceList->takeItem(index); - AddSourceAtIndex(sourceName, index + 1); - _sourceList->blockSignals(false); - _sourceList->setCurrentRow(index + 1); - delete toDelete; -} - -void QuickAccess::updateEnabled() -{ - bool itemActions = _sourceList->currentItem() != nullptr; - bool firstElement = itemActions && _sourceList->currentRow() == 0; - bool lastElement = itemActions && _sourceList->currentRow() == - _sourceList->count() - 1; - _actionRemoveSource->setEnabled(itemActions); - _actionSourceUp->setEnabled(itemActions && !firstElement); - _actionSourceDown->setEnabled(itemActions && !lastElement); - - // Refresh Toolbar Styling + QuickAccessSourceListView qa = _qaLists[0]; + auto model = qa.model; + auto index = qa.listView->currentIndex(); + auto newRow = index.row() - 1; + model->swapRows(index.row(), newRow); + auto newIdx = model->index(newRow); + qa.listView->setCurrentIndex(newIdx); + _actionSourceUp->setEnabled(newIdx.row() != 0); + _actionSourceDown->setEnabled(newIdx.row() < model->rowCount() - 1); for (auto x : _actionsToolbar->actions()) { auto widget = _actionsToolbar->widgetForAction(x); @@ -1276,73 +1144,28 @@ void QuickAccess::updateEnabled() } } -void QuickAccess::on_sourceList_itemSelectionChanged() -{ - blog(LOG_INFO, "itemSelectionChanged"); - auto items = _sourceList->selectedItems(); - auto item = items.count() == 1 ? _sourceList->currentItem() : nullptr; - auto widget = - dynamic_cast(_sourceList->itemWidget(item)); - - bool clickItem = !(item == nullptr); - - _actionCtxtFilters->setVisible(clickItem); - _actionCtxtProperties->setVisible(clickItem); - _actionCtxtFilters->setVisible(clickItem); - _actionCtxtAddCurrent->setVisible(clickItem); - _actionCtxtAddCurrentClone->setVisible(clickItem && - qau->SourceCloneInstalled()); - _actionCtxtInteract->setVisible(clickItem && widget->IsInteractive()); - _actionCtxtRenameSource->setVisible(clickItem); - - if (_dock->GetType() == "Manual") { - _actionCtxtAdd->setVisible(!clickItem); - _actionCtxtRemoveFromDock->setVisible(clickItem); - } - - if (!clickItem) { - blog(LOG_INFO, "no item selected"); - return; - } - if (_dock->GetType() == "Manual") { - updateEnabled(); - } - _actionCtxtProperties->setDisabled(!widget->Configurable()); -} - -void QuickAccess::RemoveNullSources() +void QuickAccess::on_actionSourceDown_triggered() { - if (!_active) { + if (_qaLists.size() == 0) { return; } - blog(LOG_INFO, "SIZE OF LIST BEFORE: %i", _sourceList->count()); - std::vector toDelete; - for (int i = 0; i < _sourceList->count(); i++) { - QListWidgetItem *item = _sourceList->item(i); - auto widget = dynamic_cast( - _sourceList->itemWidget(item)); - if (widget && widget->IsNullSource()) { - toDelete.push_back(item); - } - } - for (auto &item : toDelete) { - _sourceList->removeItemWidget(item); - _sourceList->takeItem(_sourceList->row(item)); - delete item; - } - _sourceList->update(); - blog(LOG_INFO, "SIZE OF LIST AFTER: %i", _sourceList->count()); -} + QuickAccessSourceListView qa = _qaLists[0]; + auto model = qa.model; + auto index = qa.listView->currentIndex(); + auto newRow = index.row() + 1; + model->swapRows(index.row(), newRow); + auto newIdx = model->index(newRow); + qa.listView->setCurrentIndex(newIdx); + _actionSourceUp->setEnabled(newIdx.row() != 0); + _actionSourceDown->setEnabled(newIdx.row() < model->rowCount() - 1); + for (auto x : _actionsToolbar->actions()) { + auto widget = _actionsToolbar->widgetForAction(x); -void QuickAccess::SourceRename(obs_source_t *source) -{ - for (int i = 0; i < _sourceList->count(); i++) { - QListWidgetItem *item = _sourceList->item(i); - auto widget = dynamic_cast( - _sourceList->itemWidget(item)); - if (widget && widget->IsSource(source)) { - widget->UpdateLabel(); + if (!widget) { + continue; } + widget->style()->unpolish(widget); + widget->style()->polish(widget); } } @@ -1444,13 +1267,6 @@ void QuickAccessSceneItem::on_actionTransform_triggered() obs_frontend_open_sceneitem_edit_transform(_sceneItem); } -//void QuickAccessSceneItem::mouseReleaseEvent(QMouseEvent *e) -//{ -// // This is to suppress mouse event propegating down -// // to QMenu. -// UNUSED_PARAMETER(e); -//} - void QuickAccessSceneItem::setHighlight(bool h) { setBackgroundRole(h ? QPalette::Highlight : QPalette::Window); @@ -1560,3 +1376,12 @@ bool AddSourceToWidget(void *data, obs_source_t *source) qa->AddSourceMenuItem(source); return true; } + +void EnumerateFilters(obs_source_t *parentScene, obs_source_t *filter, + void *param) +{ + UNUSED_PARAMETER(parentScene); + std::vector *filters = + static_cast *>(param); + filters->push_back(filter); +} diff --git a/src/quick-access.hpp b/src/quick-access.hpp index c2bb4cf..0085687 100644 --- a/src/quick-access.hpp +++ b/src/quick-access.hpp @@ -1,4 +1,9 @@ #pragma once +#include +#include +#include +#include + #include #include @@ -7,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -14,12 +20,58 @@ #include #include #include - -#include -#include - +#include +#include +#include +#include +#include +#include + +enum class SearchType; +class QuickAccessSource; class QuickAccess; class QuickAccessDock; +class QuickAccessSourceModel; + +class QuickAccessSourceList : public QListView { + Q_OBJECT +public: + QuickAccessSourceList(QWidget *parent, SearchType searchType); + QSize sizeHint() const override; + void search(std::string searchTerm); + inline int visibleCount() const { return _numActive; } + QuickAccessSource *currentSource(); + +private: + QuickAccess *_qaParent; + void _setupContextMenu(); + SearchType _searchType; + bool _activeSearch; + int _numActive; + QAction *_actionCtxtAddCurrent; + QAction *_actionCtxtAddCurrentClone; + QAction *_actionCtxtProperties; + QAction *_actionCtxtFilters; + QAction *_actionCtxtRenameSource; + QAction *_actionCtxtInteract; + QAction *_actionCtxtRefresh; + QAction *_actionCtxtToggleActivation; + + QItemSelection _selected, _prior; + +protected: + void mousePressEvent(QMouseEvent *event) override; + +signals: + void selectedItemChanged(const QModelIndex &index); +}; + +struct QuickAccessSourceListView { + std::string header; + QLabel *headerLabel; + QuickAccessSourceList *listView; + QuickAccessSourceModel *model; +}; class QuickAccessSceneItem : public QWidget { Q_OBJECT @@ -42,109 +94,38 @@ private slots: void on_actionTransform_triggered(); }; -class QuickAccessList : public QListWidget { - Q_OBJECT - -public: - QuickAccessList(QWidget *parent, QuickAccessDock *dock); - void dropEvent(QDropEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - -private: - QuickAccess *_qa = nullptr; - QuickAccessDock *_dock; -}; - -class QuickAccessItem : public QFrame { - Q_OBJECT - -public: - QuickAccessItem(QWidget *parent, QuickAccessDock *dock, - obs_source_t *source); - QuickAccessItem(QWidget *parent, QuickAccessItem *original); - ~QuickAccessItem(); - void Save(obs_data_t *saveObj); - const char *GetSourceName(); - void RenameSource(std::string name); - static bool GetSceneItemsFromScene(void *data, obs_source_t *s); - static bool AddSceneItems(obs_scene_t *s, obs_sceneitem_t *si, - void *data); - void SetButtonVisibility(); - bool IsSource(obs_source_t *s); - bool IsNullSource(); - bool IsInteractive(); - void SwitchToScene(); - void UpdateLabel(); - - // Actions - void AddToScene(obs_source_t *scene); - void OpenFilters(); - void OpenProperties(); - void OpenInteract(); - - bool Configurable(); - - inline obs_source_t *GetSource() - { - return obs_weak_source_get_source(_source); - } - -private: - QuickAccessDock *_dock; - QLabel *_label = nullptr; - QLabel *_iconLabel = nullptr; - - QPushButton *_actionProperties = nullptr; - QPushButton *_actionFilters = nullptr; - QPushButton *_actionScenes = nullptr; - - QPushButton *_filters = nullptr; - obs_weak_source_t *_source = nullptr; - std::vector _sceneItems; - bool _configurable; - void _getSceneItems(); - void _clearSceneItems(); - QMenu *_CreateSceneMenu(); - void _AddScenePopupMenu(const QPoint &pos); - -private slots: - void on_actionProperties_triggered(); - void on_actionFilters_triggered(); - void on_actionScenes_triggered(); -}; - class QuickAccess : public QWidget { Q_OBJECT public: QuickAccess(QWidget *parent, QuickAccessDock *dock, QString name); ~QuickAccess(); - void AddSource(const char *sourceName, bool hidden = false); - void AddSourceAtIndex(const char *sourceName, int index); - void Load(obs_data_t *loadObj); - void LoadAllSources(); + void AddSource(QuickAccessSource *source, std::string groupName); + void RemoveSource(QuickAccessSource *source, std::string groupName); + void Load(); + void UpdateVisibility(); void Save(obs_data_t *saveObj); void AddSourceMenuItem(obs_source_t *source); void SetItemsButtonVisibility(); - void updateEnabled(); - void CleanupSourceHandlers(); - + void Redraw(); + void ClearSelections(QuickAccessSourceList *skip); + void SearchFocus(); + void DismissModal(); static bool AddSourceName(void *data, obs_source_t *source); + static bool GetSceneItemsFromScene(void *data, obs_source_t *s); + static bool AddSceneItems(obs_scene_t *s, obs_sceneitem_t *si, + void *data); - static void SceneChangeCallback(enum obs_frontend_event event, - void *data); - static bool DynAddSceneItems(obs_scene_t *scene, - obs_sceneitem_t *sceneItem, void *data); - - static void ItemAddedToScene(void *data, calldata_t *params); - static void ItemRemovedFromScene(void *data, calldata_t *params); - - void RemoveNullSources(); - void SourceRename(obs_source_t *source); + virtual void paintEvent(QPaintEvent *); private: QuickAccessDock *_dock; - QuickAccessList *_sourceList; + QStackedWidget *_contents; + QScrollArea *_listsContainer; + QWidget *_emptySearch; + QWidget *_emptyManual; + QWidget *_emptyDynamic; + QWidget *_noSearchResults; QLineEdit *_searchText; QToolBar *_actionsToolbar; QAction *_actionAddSource = nullptr; @@ -154,16 +135,26 @@ class QuickAccess : public QWidget { QAction *_actionDockProperties = nullptr; QDialog *CreateAddSourcePopupMenu(); obs_weak_source_t *_current = nullptr; + QuickAccessSource *_currentSource = nullptr; signal_handler_t *source_signal_handler = nullptr; void AddSourcePopupMenu(); + QMenu *_CreateParentSceneMenu(); void _ClearMenuSources(); - void _LoadDynamicScenes(); + void _getSceneItems(); + void _clearSceneItems(); + void _createListContainer(); std::vector _menuSources; std::vector _manualSourceNames; std::vector _allSourceNames; std::set _dynamicScenes; + std::vector _sceneItems; + + std::vector _qaLists; + std::vector> _sourceModels; + bool _active = true; bool _switchingSC = false; + bool _noSearch = true; // Context menu actions QAction *_actionCtxtAdd = nullptr; @@ -175,13 +166,15 @@ class QuickAccess : public QWidget { QAction *_actionCtxtRenameSource = nullptr; QAction *_actionCtxtInteract = nullptr; + int _activeIndex = -1; + private slots: void on_actionAddSource_triggered(); void on_actionRemoveSource_triggered(); void on_actionSourceUp_triggered(); void on_actionSourceDown_triggered(); void on_actionDockProperties_triggered(); - void on_sourceList_itemSelectionChanged(); + //void on_sourceList_itemSelectionChanged(); }; class UpdateDockDialog : public QDialog { @@ -211,3 +204,5 @@ private slots: }; bool AddSourceToWidget(void *data, obs_source_t *source); +void EnumerateFilters(obs_source_t *parentScene, obs_source_t *filter, + void *param);