mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-09-24 23:31:36 +08:00
Merge pull request #2015 from obsproject/extra-browser-panels
Add ability to create custom browser docks
This commit is contained in:
@@ -146,10 +146,12 @@ if(BROWSER_AVAILABLE_INTERNAL)
|
||||
list(APPEND obs_PLATFORM_SOURCES
|
||||
obf.c
|
||||
auth-oauth.cpp
|
||||
window-extra-browsers.cpp
|
||||
)
|
||||
list(APPEND obs_PLATFORM_HEADERS
|
||||
obf.h
|
||||
auth-oauth.hpp
|
||||
window-extra-browsers.hpp
|
||||
)
|
||||
|
||||
if(TWITCH_ENABLED)
|
||||
@@ -324,6 +326,7 @@ set(obs_UI
|
||||
forms/OBSBasicSettings.ui
|
||||
forms/OBSBasicSourceSelect.ui
|
||||
forms/OBSBasicInteraction.ui
|
||||
forms/OBSExtraBrowsers.ui
|
||||
forms/OBSUpdate.ui
|
||||
forms/OBSRemux.ui
|
||||
forms/OBSAbout.ui)
|
||||
|
||||
@@ -98,6 +98,11 @@ AlreadyRunning.LaunchAnyway="Launch Anyway"
|
||||
DockCloseWarning.Title="Closing Dockable Window"
|
||||
DockCloseWarning.Text="You just closed a dockable window. If you'd like to show it again, use the View → Docks menu on the menu bar."
|
||||
|
||||
# extra browser panels dialog
|
||||
ExtraBrowsers="Custom Browser Docks"
|
||||
ExtraBrowsers.Info="Add a dock by giving it a name and URL, then click Apply or Close to configure where it is on your screen. You can add or remove docks at any time."
|
||||
ExtraBrowsers.DockName="Dock Name"
|
||||
|
||||
# Auth
|
||||
Auth.Authing.Title="Authenticating..."
|
||||
Auth.Authing.Text="Authenticating with %1, please wait..."
|
||||
@@ -572,6 +577,7 @@ Basic.MainMenu.View.Toolbars="&Toolbars"
|
||||
Basic.MainMenu.View.Docks="Docks"
|
||||
Basic.MainMenu.View.Docks.ResetUI="Reset UI"
|
||||
Basic.MainMenu.View.Docks.LockUI="Lock UI"
|
||||
Basic.MainMenu.View.Docks.CustomBrowserDocks="Custom Browser Docks..."
|
||||
Basic.MainMenu.View.Toolbars.Listboxes="&Listboxes"
|
||||
Basic.MainMenu.View.SceneTransitions="S&cene Transitions"
|
||||
Basic.MainMenu.View.StatusBar="&Status Bar"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OBSExtraBrowsers</class>
|
||||
<widget class="QWidget" name="OBSExtraBrowsers">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>785</width>
|
||||
<height>353</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ExtraBrowsers</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>ExtraBrowsers.Info</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="table">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>23</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>23</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>23</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="apply">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="close">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>OBSExtraBrowsers</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>520</x>
|
||||
<y>286</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>435</x>
|
||||
<y>-19</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QDataStream>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#include <QX11Info>
|
||||
@@ -293,3 +294,32 @@ void ExecThreadedWithoutBlocking(std::function<void()> func,
|
||||
else
|
||||
ExecuteFuncSafeBlockMsgBox(func, title, text);
|
||||
}
|
||||
|
||||
bool LineEditCanceled(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = reinterpret_cast<QKeyEvent *>(event);
|
||||
return keyEvent->key() == Qt::Key_Escape;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LineEditChanged(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = reinterpret_cast<QKeyEvent *>(event);
|
||||
|
||||
switch (keyEvent->key()) {
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::FocusOut) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -102,3 +102,6 @@ static inline Qt::ConnectionType WaitConnection()
|
||||
? Qt::DirectConnection
|
||||
: Qt::BlockingQueuedConnection;
|
||||
}
|
||||
|
||||
bool LineEditCanceled(QEvent *event);
|
||||
bool LineEditChanged(QEvent *event);
|
||||
|
||||
+7
-19
@@ -342,25 +342,13 @@ bool SourceTreeItem::eventFilter(QObject *object, QEvent *event)
|
||||
if (editor != object)
|
||||
return false;
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
|
||||
switch (keyEvent->key()) {
|
||||
case Qt::Key_Escape:
|
||||
QMetaObject::invokeMethod(this, "ExitEditMode",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(bool, false));
|
||||
return true;
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
QMetaObject::invokeMethod(this, "ExitEditMode",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(bool, true));
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::FocusOut) {
|
||||
if (LineEditCanceled(event)) {
|
||||
QMetaObject::invokeMethod(this, "ExitEditMode",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(bool, false));
|
||||
return true;
|
||||
}
|
||||
if (LineEditChanged(event)) {
|
||||
QMetaObject::invokeMethod(this, "ExitEditMode",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(bool, true));
|
||||
|
||||
@@ -1689,6 +1689,23 @@ void OBSBasic::OBSInit()
|
||||
OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false);
|
||||
statsDock->setWidget(statsDlg);
|
||||
|
||||
/* ----------------------------- */
|
||||
/* add custom browser docks */
|
||||
|
||||
#ifdef BROWSER_AVAILABLE
|
||||
if (cef) {
|
||||
QAction *action = new QAction(QTStr("Basic.MainMenu."
|
||||
"View.Docks."
|
||||
"CustomBrowserDocks"));
|
||||
ui->viewMenuDocks->insertAction(ui->toggleScenes, action);
|
||||
connect(action, &QAction::triggered, this,
|
||||
&OBSBasic::ManageExtraBrowserDocks);
|
||||
ui->viewMenuDocks->insertSeparator(ui->toggleScenes);
|
||||
|
||||
LoadExtraBrowserDocks();
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *dockStateStr = config_get_string(
|
||||
App()->GlobalConfig(), "BasicWindow", "DockState");
|
||||
if (!dockStateStr) {
|
||||
@@ -3794,9 +3811,16 @@ void OBSBasic::closeEvent(QCloseEvent *event)
|
||||
SaveProjectNow();
|
||||
auth.reset();
|
||||
|
||||
delete extraBrowsers;
|
||||
|
||||
config_set_string(App()->GlobalConfig(), "BasicWindow", "DockState",
|
||||
saveState().toBase64().constData());
|
||||
|
||||
#ifdef BROWSER_AVAILABLE
|
||||
SaveExtraBrowserDocks();
|
||||
ClearExtraBrowserDocks();
|
||||
#endif
|
||||
|
||||
if (api)
|
||||
api->on_event(OBS_FRONTEND_EVENT_EXIT);
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ class OBSBasic : public OBSMainWindow {
|
||||
friend class AutoConfig;
|
||||
friend class AutoConfigStreamPage;
|
||||
friend class RecordButton;
|
||||
friend class ExtraBrowsersModel;
|
||||
friend class ExtraBrowsersDelegate;
|
||||
friend struct OBSStudioAPI;
|
||||
|
||||
enum class MoveDir { Up, Down, Left, Right };
|
||||
@@ -200,6 +202,7 @@ private:
|
||||
|
||||
QPointer<QWidget> stats;
|
||||
QPointer<QWidget> remux;
|
||||
QPointer<QWidget> extraBrowsers;
|
||||
|
||||
QPointer<QMenu> startStreamMenu;
|
||||
|
||||
@@ -420,6 +423,19 @@ private:
|
||||
|
||||
bool NoSourcesConfirmation();
|
||||
|
||||
#ifdef BROWSER_AVAILABLE
|
||||
QList<QSharedPointer<QDockWidget>> extraBrowserDocks;
|
||||
QList<QSharedPointer<QAction>> extraBrowserDockActions;
|
||||
QStringList extraBrowserDockTargets;
|
||||
|
||||
void ClearExtraBrowserDocks();
|
||||
void LoadExtraBrowserDocks();
|
||||
void SaveExtraBrowserDocks();
|
||||
void ManageExtraBrowserDocks();
|
||||
void AddExtraBrowserDock(const QString &title, const QString &url,
|
||||
bool firstCreate);
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void DeferSaveBegin();
|
||||
void DeferSaveEnd();
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
#include "window-extra-browsers.hpp"
|
||||
#include "window-basic-main.hpp"
|
||||
#include "qt-wrappers.hpp"
|
||||
#include "window-dock.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include <json11.hpp>
|
||||
|
||||
#include "ui_OBSExtraBrowsers.h"
|
||||
|
||||
#include <browser-panel.hpp>
|
||||
extern QCef *cef;
|
||||
extern QCefCookieManager *panel_cookies;
|
||||
|
||||
using namespace json11;
|
||||
|
||||
#define OBJ_NAME_SUFFIX "_extraBrowser"
|
||||
|
||||
enum class Column : int {
|
||||
Title,
|
||||
Url,
|
||||
Delete,
|
||||
|
||||
Count,
|
||||
};
|
||||
|
||||
class ExtraBrowser : public OBSDock {
|
||||
public:
|
||||
inline ExtraBrowser() : OBSDock() {}
|
||||
|
||||
QScopedPointer<QCefWidget> cefWidget;
|
||||
|
||||
inline void SetWidget(QCefWidget *widget_)
|
||||
{
|
||||
setWidget(widget_);
|
||||
cefWidget.reset(widget_);
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void ExtraBrowsersModel::Reset()
|
||||
{
|
||||
items.clear();
|
||||
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
|
||||
for (int i = 0; i < main->extraBrowserDocks.size(); i++) {
|
||||
ExtraBrowser *dock = reinterpret_cast<ExtraBrowser *>(
|
||||
main->extraBrowserDocks[i].data());
|
||||
|
||||
Item item;
|
||||
item.prevIdx = i;
|
||||
item.title = dock->windowTitle();
|
||||
item.url = main->extraBrowserDockTargets[i];
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
int ExtraBrowsersModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
int count = items.size() + 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
int ExtraBrowsersModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)Column::Count;
|
||||
}
|
||||
|
||||
QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int column = index.column();
|
||||
int idx = index.row();
|
||||
int count = items.size();
|
||||
bool validRole = role == Qt::DisplayRole ||
|
||||
role == Qt::AccessibleTextRole;
|
||||
|
||||
if (!validRole)
|
||||
return QVariant();
|
||||
|
||||
if (idx >= 0 && idx < count) {
|
||||
switch (column) {
|
||||
case (int)Column::Title:
|
||||
return items[idx].title;
|
||||
case (int)Column::Url:
|
||||
return items[idx].url;
|
||||
}
|
||||
} else if (idx == count) {
|
||||
switch (column) {
|
||||
case (int)Column::Title:
|
||||
return newTitle;
|
||||
case (int)Column::Url:
|
||||
return newURL;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant ExtraBrowsersModel::headerData(int section,
|
||||
Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
bool validRole = role == Qt::DisplayRole ||
|
||||
role == Qt::AccessibleTextRole;
|
||||
|
||||
if (validRole && orientation == Qt::Orientation::Horizontal) {
|
||||
switch (section) {
|
||||
case (int)Column::Title:
|
||||
return QTStr("ExtraBrowsers.DockName");
|
||||
case (int)Column::Url:
|
||||
return QStringLiteral("URL");
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
|
||||
|
||||
if (index.column() != (int)Column::Delete)
|
||||
flags |= Qt::ItemIsEditable;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
class DelButton : public QPushButton {
|
||||
public:
|
||||
inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {}
|
||||
|
||||
QPersistentModelIndex index;
|
||||
};
|
||||
|
||||
class EditWidget : public QLineEdit {
|
||||
public:
|
||||
inline EditWidget(QWidget *parent, QModelIndex index_)
|
||||
: QLineEdit(parent), index(index_)
|
||||
{
|
||||
}
|
||||
|
||||
QPersistentModelIndex index;
|
||||
};
|
||||
|
||||
void ExtraBrowsersModel::AddDeleteButton(int idx)
|
||||
{
|
||||
QTableView *widget = reinterpret_cast<QTableView *>(parent());
|
||||
|
||||
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding,
|
||||
QSizePolicy::PushButton);
|
||||
policy.setWidthForHeight(true);
|
||||
|
||||
QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr);
|
||||
|
||||
QPushButton *del = new DelButton(index);
|
||||
del->setProperty("themeID", "trashIcon");
|
||||
del->setSizePolicy(policy);
|
||||
del->setFlat(true);
|
||||
connect(del, &QPushButton::clicked, this,
|
||||
&ExtraBrowsersModel::DeleteItem);
|
||||
|
||||
widget->setIndexWidget(index, del);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::CheckToAdd()
|
||||
{
|
||||
if (newTitle.isEmpty() || newURL.isEmpty())
|
||||
return;
|
||||
|
||||
int idx = items.size() + 1;
|
||||
beginInsertRows(QModelIndex(), idx, idx);
|
||||
|
||||
Item item;
|
||||
item.prevIdx = -1;
|
||||
item.title = newTitle;
|
||||
item.url = newURL;
|
||||
items.push_back(item);
|
||||
|
||||
newTitle = "";
|
||||
newURL = "";
|
||||
|
||||
endInsertRows();
|
||||
|
||||
AddDeleteButton(idx - 1);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::UpdateItem(Item &item)
|
||||
{
|
||||
int idx = item.prevIdx;
|
||||
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
ExtraBrowser *dock = reinterpret_cast<ExtraBrowser *>(
|
||||
main->extraBrowserDocks[idx].data());
|
||||
dock->setWindowTitle(item.title);
|
||||
dock->setObjectName(item.title + OBJ_NAME_SUFFIX);
|
||||
main->extraBrowserDockActions[idx]->setText(item.title);
|
||||
|
||||
if (main->extraBrowserDockTargets[idx] != item.url) {
|
||||
dock->cefWidget->setURL(QT_TO_UTF8(item.url));
|
||||
main->extraBrowserDockTargets[idx] = item.url;
|
||||
}
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::DeleteItem()
|
||||
{
|
||||
QTableView *widget = reinterpret_cast<QTableView *>(parent());
|
||||
|
||||
DelButton *del = reinterpret_cast<DelButton *>(sender());
|
||||
int row = del->index.row();
|
||||
|
||||
/* there's some sort of internal bug in Qt and deleting certain index
|
||||
* widgets or "editors" that can cause a crash inside Qt if the widget
|
||||
* is not manually removed, at least on 5.7 */
|
||||
widget->setIndexWidget(del->index, nullptr);
|
||||
del->deleteLater();
|
||||
|
||||
/* --------- */
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
|
||||
int prevIdx = items[row].prevIdx;
|
||||
items.removeAt(row);
|
||||
|
||||
if (prevIdx != -1) {
|
||||
int i = 0;
|
||||
for (; i < deleted.size() && deleted[i] < prevIdx; i++)
|
||||
;
|
||||
deleted.insert(i, prevIdx);
|
||||
}
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::Apply()
|
||||
{
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
|
||||
for (Item &item : items) {
|
||||
if (item.prevIdx != -1) {
|
||||
UpdateItem(item);
|
||||
} else {
|
||||
main->AddExtraBrowserDock(item.title, item.url, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = deleted.size() - 1; i >= 0; i--) {
|
||||
int idx = deleted[i];
|
||||
main->extraBrowserDockActions.removeAt(idx);
|
||||
main->extraBrowserDockTargets.removeAt(idx);
|
||||
main->extraBrowserDocks.removeAt(idx);
|
||||
}
|
||||
|
||||
deleted.clear();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::TabSelection(bool forward)
|
||||
{
|
||||
QListView *widget = reinterpret_cast<QListView *>(parent());
|
||||
QItemSelectionModel *selModel = widget->selectionModel();
|
||||
|
||||
QModelIndex sel = selModel->currentIndex();
|
||||
int row = sel.row();
|
||||
int col = sel.column();
|
||||
|
||||
switch (sel.column()) {
|
||||
case (int)Column::Title:
|
||||
if (!forward) {
|
||||
if (row == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
row -= 1;
|
||||
}
|
||||
|
||||
col += 1;
|
||||
break;
|
||||
|
||||
case (int)Column::Url:
|
||||
if (forward) {
|
||||
if (row == items.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
row += 1;
|
||||
}
|
||||
|
||||
col -= 1;
|
||||
}
|
||||
|
||||
sel = createIndex(row, col, nullptr);
|
||||
selModel->setCurrentIndex(sel, QItemSelectionModel::Clear);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::Init()
|
||||
{
|
||||
for (int i = 0; i < items.count(); i++)
|
||||
AddDeleteButton(i);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent,
|
||||
const QStyleOptionViewItem &,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = new EditWidget(parent, index);
|
||||
text->installEventFilter(const_cast<ExtraBrowsersDelegate *>(this));
|
||||
text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::LineEdit));
|
||||
return text;
|
||||
}
|
||||
|
||||
void ExtraBrowsersDelegate::setEditorData(QWidget *editor,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = reinterpret_cast<QLineEdit *>(editor);
|
||||
text->blockSignals(true);
|
||||
text->setText(index.data().toString());
|
||||
text->blockSignals(false);
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
QLineEdit *edit = qobject_cast<QLineEdit *>(object);
|
||||
if (!edit)
|
||||
return false;
|
||||
|
||||
if (LineEditCanceled(event)) {
|
||||
RevertText(edit);
|
||||
}
|
||||
if (LineEditChanged(event)) {
|
||||
UpdateText(edit);
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Tab) {
|
||||
model->TabSelection(true);
|
||||
} else if (keyEvent->key() == Qt::Key_Backtab) {
|
||||
model->TabSelection(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::ValidName(const QString &name) const
|
||||
{
|
||||
for (auto &item : model->items) {
|
||||
if (name.compare(item.title, Qt::CaseInsensitive) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_)
|
||||
{
|
||||
EditWidget *edit = reinterpret_cast<EditWidget *>(edit_);
|
||||
int row = edit->index.row();
|
||||
int col = edit->index.column();
|
||||
bool newItem = (row == model->items.size());
|
||||
|
||||
QString oldText;
|
||||
if (col == (int)Column::Title) {
|
||||
oldText = newItem ? model->newTitle : model->items[row].title;
|
||||
} else {
|
||||
oldText = newItem ? model->newURL : model->items[row].url;
|
||||
}
|
||||
|
||||
edit->setText(oldText);
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_)
|
||||
{
|
||||
EditWidget *edit = reinterpret_cast<EditWidget *>(edit_);
|
||||
int row = edit->index.row();
|
||||
int col = edit->index.column();
|
||||
bool newItem = (row == model->items.size());
|
||||
|
||||
QString text = edit->text().trimmed();
|
||||
|
||||
if (!newItem && text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (col == (int)Column::Title) {
|
||||
QString oldText = newItem ? model->newTitle
|
||||
: model->items[row].title;
|
||||
bool same = oldText.compare(text, Qt::CaseInsensitive) == 0;
|
||||
|
||||
if (!same && !ValidName(text)) {
|
||||
edit->setText(oldText);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newItem) {
|
||||
/* if edited existing item, update it*/
|
||||
switch (col) {
|
||||
case (int)Column::Title:
|
||||
model->items[row].title = text;
|
||||
break;
|
||||
case (int)Column::Url:
|
||||
model->items[row].url = text;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* if both new values filled out, create new one */
|
||||
switch (col) {
|
||||
case (int)Column::Title:
|
||||
model->newTitle = text;
|
||||
break;
|
||||
case (int)Column::Url:
|
||||
model->newURL = text;
|
||||
break;
|
||||
}
|
||||
|
||||
model->CheckToAdd();
|
||||
}
|
||||
|
||||
emit commitData(edit);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent)
|
||||
: QDialog(parent), ui(new Ui::OBSExtraBrowsers)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
model = new ExtraBrowsersModel(ui->table);
|
||||
|
||||
ui->table->setModel(model);
|
||||
ui->table->setItemDelegateForColumn((int)Column::Title,
|
||||
new ExtraBrowsersDelegate(model));
|
||||
ui->table->setItemDelegateForColumn((int)Column::Url,
|
||||
new ExtraBrowsersDelegate(model));
|
||||
ui->table->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::ResizeMode::Stretch);
|
||||
ui->table->horizontalHeader()->setSectionResizeMode(
|
||||
(int)Column::Delete, QHeaderView::ResizeMode::Fixed);
|
||||
ui->table->setEditTriggers(
|
||||
QAbstractItemView::EditTrigger::CurrentChanged);
|
||||
}
|
||||
|
||||
OBSExtraBrowsers::~OBSExtraBrowsers()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OBSExtraBrowsers::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
QDialog::closeEvent(event);
|
||||
model->Apply();
|
||||
}
|
||||
|
||||
void OBSExtraBrowsers::on_apply_clicked()
|
||||
{
|
||||
model->Apply();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void OBSBasic::ClearExtraBrowserDocks()
|
||||
{
|
||||
extraBrowserDockTargets.clear();
|
||||
extraBrowserDockActions.clear();
|
||||
extraBrowserDocks.clear();
|
||||
}
|
||||
|
||||
void OBSBasic::LoadExtraBrowserDocks()
|
||||
{
|
||||
const char *jsonStr = config_get_string(
|
||||
App()->GlobalConfig(), "BasicWindow", "ExtraBrowserDocks");
|
||||
|
||||
std::string err;
|
||||
Json json = Json::parse(jsonStr, err);
|
||||
if (!err.empty())
|
||||
return;
|
||||
|
||||
Json::array array = json.array_items();
|
||||
for (Json &item : array) {
|
||||
std::string title = item["title"].string_value();
|
||||
std::string url = item["url"].string_value();
|
||||
|
||||
AddExtraBrowserDock(title.c_str(), url.c_str(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::SaveExtraBrowserDocks()
|
||||
{
|
||||
Json::array array;
|
||||
for (int i = 0; i < extraBrowserDocks.size(); i++) {
|
||||
QAction *action = extraBrowserDockActions[i].data();
|
||||
QString url = extraBrowserDockTargets[i];
|
||||
Json::object obj{
|
||||
{"title", QT_TO_UTF8(action->text())},
|
||||
{"url", QT_TO_UTF8(url)},
|
||||
};
|
||||
array.push_back(obj);
|
||||
}
|
||||
|
||||
std::string output = Json(array).dump();
|
||||
config_set_string(App()->GlobalConfig(), "BasicWindow",
|
||||
"ExtraBrowserDocks", output.c_str());
|
||||
}
|
||||
|
||||
void OBSBasic::ManageExtraBrowserDocks()
|
||||
{
|
||||
if (!extraBrowsers.isNull()) {
|
||||
extraBrowsers->show();
|
||||
extraBrowsers->raise();
|
||||
return;
|
||||
}
|
||||
|
||||
extraBrowsers = new OBSExtraBrowsers(this);
|
||||
extraBrowsers->show();
|
||||
}
|
||||
|
||||
void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url,
|
||||
bool firstCreate)
|
||||
{
|
||||
static int panel_version = -1;
|
||||
if (panel_version == -1) {
|
||||
panel_version = obs_browser_qcef_version();
|
||||
}
|
||||
|
||||
ExtraBrowser *dock = new ExtraBrowser();
|
||||
dock->setObjectName(title + OBJ_NAME_SUFFIX);
|
||||
dock->resize(460, 600);
|
||||
dock->setMinimumSize(150, 150);
|
||||
dock->setWindowTitle(title);
|
||||
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
QCefWidget *browser =
|
||||
cef->create_widget(nullptr, QT_TO_UTF8(url), nullptr);
|
||||
if (browser && panel_version >= 1)
|
||||
browser->allowAllPopups(true);
|
||||
|
||||
dock->SetWidget(browser);
|
||||
|
||||
addDockWidget(Qt::RightDockWidgetArea, dock);
|
||||
|
||||
if (firstCreate) {
|
||||
dock->setFloating(true);
|
||||
|
||||
QPoint curPos = pos();
|
||||
QSize wSizeD2 = size() / 2;
|
||||
QSize dSizeD2 = dock->size() / 2;
|
||||
|
||||
curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width());
|
||||
curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height());
|
||||
|
||||
dock->move(curPos);
|
||||
dock->setVisible(true);
|
||||
}
|
||||
|
||||
extraBrowserDocks.push_back(QSharedPointer<QDockWidget>(dock));
|
||||
extraBrowserDockActions.push_back(
|
||||
QSharedPointer<QAction>(AddDockWidget(dock)));
|
||||
extraBrowserDockTargets.push_back(url);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
class Ui_OBSExtraBrowsers;
|
||||
class ExtraBrowsersModel;
|
||||
|
||||
class QCefWidget;
|
||||
|
||||
class OBSExtraBrowsers : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
Ui_OBSExtraBrowsers *ui;
|
||||
ExtraBrowsersModel *model;
|
||||
|
||||
public:
|
||||
OBSExtraBrowsers(QWidget *parent);
|
||||
~OBSExtraBrowsers();
|
||||
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void on_apply_clicked();
|
||||
};
|
||||
|
||||
class ExtraBrowsersModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
inline ExtraBrowsersModel(QObject *parent = nullptr)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
Reset();
|
||||
QMetaObject::invokeMethod(this, "Init", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int
|
||||
columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
struct Item {
|
||||
int prevIdx;
|
||||
QString title;
|
||||
QString url;
|
||||
};
|
||||
|
||||
void TabSelection(bool forward);
|
||||
|
||||
void AddDeleteButton(int idx);
|
||||
void Reset();
|
||||
void CheckToAdd();
|
||||
void UpdateItem(Item &item);
|
||||
void DeleteItem();
|
||||
void Apply();
|
||||
|
||||
QVector<Item> items;
|
||||
QVector<int> deleted;
|
||||
|
||||
QString newTitle;
|
||||
QString newURL;
|
||||
|
||||
public slots:
|
||||
void Init();
|
||||
};
|
||||
|
||||
class ExtraBrowsersDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
inline ExtraBrowsersDelegate(ExtraBrowsersModel *model_)
|
||||
: QStyledItemDelegate(nullptr), model(model_)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget *createEditor(QWidget *parent,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
void setEditorData(QWidget *editor,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
void RevertText(QLineEdit *edit);
|
||||
bool UpdateText(QLineEdit *edit);
|
||||
bool ValidName(const QString &text) const;
|
||||
|
||||
ExtraBrowsersModel *model;
|
||||
};
|
||||
Reference in New Issue
Block a user