properties-view.cpp 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. #include <QFormLayout>
  2. #include <QScrollBar>
  3. #include <QLabel>
  4. #include <QCheckBox>
  5. #include <QFont>
  6. #include <QFontDialog>
  7. #include <QLineEdit>
  8. #include <QSpinBox>
  9. #include <QSlider>
  10. #include <QDoubleSpinBox>
  11. #include <QComboBox>
  12. #include <QListWidget>
  13. #include <QPushButton>
  14. #include <QStandardItem>
  15. #include <QFileDialog>
  16. #include <QColorDialog>
  17. #include <QPlainTextEdit>
  18. #include <QDialogButtonBox>
  19. #include <QMenu>
  20. #include "double-slider.hpp"
  21. #include "qt-wrappers.hpp"
  22. #include "properties-view.hpp"
  23. #include "obs-app.hpp"
  24. #include <string>
  25. using namespace std;
  26. static inline QColor color_from_int(long long val)
  27. {
  28. return QColor( val & 0xff,
  29. (val >> 8) & 0xff,
  30. (val >> 16) & 0xff,
  31. (val >> 24) & 0xff);
  32. }
  33. static inline long long color_to_int(QColor color)
  34. {
  35. auto shift = [&](unsigned val, int shift)
  36. {
  37. return ((val & 0xff) << shift);
  38. };
  39. return shift(color.red(), 0) |
  40. shift(color.green(), 8) |
  41. shift(color.blue(), 16) |
  42. shift(color.alpha(), 24);
  43. }
  44. void OBSPropertiesView::ReloadProperties()
  45. {
  46. if (obj) {
  47. properties.reset(reloadCallback(obj));
  48. } else {
  49. properties.reset(reloadCallback((void*)type.c_str()));
  50. obs_properties_apply_settings(properties.get(), settings);
  51. }
  52. uint32_t flags = obs_properties_get_flags(properties.get());
  53. deferUpdate = (flags & OBS_PROPERTIES_DEFER_UPDATE) != 0;
  54. RefreshProperties();
  55. }
  56. #define NO_PROPERTIES_STRING QTStr("Basic.PropertiesWindow.NoProperties")
  57. void OBSPropertiesView::RefreshProperties()
  58. {
  59. int h, v;
  60. GetScrollPos(h, v);
  61. children.clear();
  62. if (widget)
  63. widget->deleteLater();
  64. widget = new QWidget();
  65. QFormLayout *layout = new QFormLayout;
  66. layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
  67. widget->setLayout(layout);
  68. QSizePolicy mainPolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  69. QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  70. //widget->setSizePolicy(policy);
  71. layout->setLabelAlignment(Qt::AlignRight);
  72. obs_property_t *property = obs_properties_first(properties.get());
  73. bool hasNoProperties = !property;
  74. while (property) {
  75. AddProperty(property, layout);
  76. obs_property_next(&property);
  77. }
  78. setWidgetResizable(true);
  79. setWidget(widget);
  80. SetScrollPos(h, v);
  81. setSizePolicy(mainPolicy);
  82. lastFocused.clear();
  83. if (lastWidget) {
  84. lastWidget->setFocus(Qt::OtherFocusReason);
  85. lastWidget = nullptr;
  86. }
  87. if (hasNoProperties) {
  88. QLabel *noPropertiesLabel = new QLabel(NO_PROPERTIES_STRING);
  89. layout->addWidget(noPropertiesLabel);
  90. }
  91. }
  92. void OBSPropertiesView::SetScrollPos(int h, int v)
  93. {
  94. QScrollBar *scroll = horizontalScrollBar();
  95. if (scroll)
  96. scroll->setValue(h);
  97. scroll = verticalScrollBar();
  98. if (scroll)
  99. scroll->setValue(v);
  100. }
  101. void OBSPropertiesView::GetScrollPos(int &h, int &v)
  102. {
  103. h = v = 0;
  104. QScrollBar *scroll = horizontalScrollBar();
  105. if (scroll)
  106. h = scroll->value();
  107. scroll = verticalScrollBar();
  108. if (scroll)
  109. v = scroll->value();
  110. }
  111. OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj_,
  112. PropertiesReloadCallback reloadCallback,
  113. PropertiesUpdateCallback callback_, int minSize_)
  114. : VScrollArea (nullptr),
  115. properties (nullptr, obs_properties_destroy),
  116. settings (settings_),
  117. obj (obj_),
  118. reloadCallback (reloadCallback),
  119. callback (callback_),
  120. minSize (minSize_)
  121. {
  122. setFrameShape(QFrame::NoFrame);
  123. ReloadProperties();
  124. }
  125. OBSPropertiesView::OBSPropertiesView(OBSData settings_, const char *type_,
  126. PropertiesReloadCallback reloadCallback_, int minSize_)
  127. : VScrollArea (nullptr),
  128. properties (nullptr, obs_properties_destroy),
  129. settings (settings_),
  130. type (type_),
  131. reloadCallback (reloadCallback_),
  132. minSize (minSize_)
  133. {
  134. setFrameShape(QFrame::NoFrame);
  135. ReloadProperties();
  136. }
  137. void OBSPropertiesView::resizeEvent(QResizeEvent *event)
  138. {
  139. emit PropertiesResized();
  140. VScrollArea::resizeEvent(event);
  141. }
  142. QWidget *OBSPropertiesView::NewWidget(obs_property_t *prop, QWidget *widget,
  143. const char *signal)
  144. {
  145. WidgetInfo *info = new WidgetInfo(this, prop, widget);
  146. connect(widget, signal, info, SLOT(ControlChanged()));
  147. children.emplace_back(info);
  148. return widget;
  149. }
  150. QWidget *OBSPropertiesView::AddCheckbox(obs_property_t *prop)
  151. {
  152. const char *name = obs_property_name(prop);
  153. const char *desc = obs_property_description(prop);
  154. bool val = obs_data_get_bool(settings, name);
  155. QCheckBox *checkbox = new QCheckBox(QT_UTF8(desc));
  156. checkbox->setCheckState(val ? Qt::Checked : Qt::Unchecked);
  157. return NewWidget(prop, checkbox, SIGNAL(stateChanged(int)));
  158. }
  159. QWidget *OBSPropertiesView::AddText(obs_property_t *prop, QFormLayout *layout,
  160. QLabel *&label)
  161. {
  162. const char *name = obs_property_name(prop);
  163. const char *val = obs_data_get_string(settings, name);
  164. obs_text_type type = obs_proprety_text_type(prop);
  165. if (type == OBS_TEXT_MULTILINE) {
  166. QPlainTextEdit *edit = new QPlainTextEdit(QT_UTF8(val));
  167. return NewWidget(prop, edit, SIGNAL(textChanged()));
  168. } else if (type == OBS_TEXT_PASSWORD) {
  169. QLayout *subLayout = new QHBoxLayout();
  170. QLineEdit *edit = new QLineEdit();
  171. QPushButton *show = new QPushButton();
  172. show->setText(QTStr("Show"));
  173. show->setCheckable(true);
  174. edit->setText(QT_UTF8(val));
  175. edit->setEchoMode(QLineEdit::Password);
  176. subLayout->addWidget(edit);
  177. subLayout->addWidget(show);
  178. WidgetInfo *info = new WidgetInfo(this, prop, edit);
  179. connect(show, &QAbstractButton::toggled,
  180. info, &WidgetInfo::TogglePasswordText);
  181. children.emplace_back(info);
  182. label = new QLabel(QT_UTF8(obs_property_description(prop)));
  183. layout->addRow(label, subLayout);
  184. connect(edit, SIGNAL(textEdited(const QString &)),
  185. info, SLOT(ControlChanged()));
  186. return nullptr;
  187. }
  188. QLineEdit *edit = new QLineEdit();
  189. edit->setText(QT_UTF8(val));
  190. return NewWidget(prop, edit, SIGNAL(textEdited(const QString &)));
  191. }
  192. void OBSPropertiesView::AddPath(obs_property_t *prop, QFormLayout *layout,
  193. QLabel **label)
  194. {
  195. const char *name = obs_property_name(prop);
  196. const char *val = obs_data_get_string(settings, name);
  197. QLayout *subLayout = new QHBoxLayout();
  198. QLineEdit *edit = new QLineEdit();
  199. QPushButton *button = new QPushButton(QTStr("Browse"));
  200. edit->setText(QT_UTF8(val));
  201. edit->setReadOnly(true);
  202. subLayout->addWidget(edit);
  203. subLayout->addWidget(button);
  204. WidgetInfo *info = new WidgetInfo(this, prop, edit);
  205. connect(button, SIGNAL(clicked()), info, SLOT(ControlChanged()));
  206. children.emplace_back(info);
  207. *label = new QLabel(QT_UTF8(obs_property_description(prop)));
  208. layout->addRow(*label, subLayout);
  209. }
  210. void OBSPropertiesView::AddInt(obs_property_t *prop, QFormLayout *layout,
  211. QLabel **label)
  212. {
  213. obs_number_type type = obs_property_int_type(prop);
  214. QLayout *subLayout = new QHBoxLayout();
  215. const char *name = obs_property_name(prop);
  216. int val = (int)obs_data_get_int(settings, name);
  217. QSpinBox *spin = new QSpinBox();
  218. int minVal = obs_property_int_min(prop);
  219. int maxVal = obs_property_int_max(prop);
  220. int stepVal = obs_property_int_step(prop);
  221. spin->setMinimum(minVal);
  222. spin->setMaximum(maxVal);
  223. spin->setSingleStep(stepVal);
  224. spin->setValue(val);
  225. WidgetInfo *info = new WidgetInfo(this, prop, spin);
  226. children.emplace_back(info);
  227. if (type == OBS_NUMBER_SLIDER) {
  228. QSlider *slider = new QSlider();
  229. slider->setMinimum(minVal);
  230. slider->setMaximum(maxVal);
  231. slider->setPageStep(stepVal);
  232. slider->setValue(val);
  233. slider->setOrientation(Qt::Horizontal);
  234. subLayout->addWidget(slider);
  235. connect(slider, SIGNAL(valueChanged(int)),
  236. spin, SLOT(setValue(int)));
  237. connect(spin, SIGNAL(valueChanged(int)),
  238. slider, SLOT(setValue(int)));
  239. }
  240. connect(spin, SIGNAL(valueChanged(int)), info, SLOT(ControlChanged()));
  241. subLayout->addWidget(spin);
  242. *label = new QLabel(QT_UTF8(obs_property_description(prop)));
  243. layout->addRow(*label, subLayout);
  244. }
  245. void OBSPropertiesView::AddFloat(obs_property_t *prop, QFormLayout *layout,
  246. QLabel **label)
  247. {
  248. obs_number_type type = obs_property_float_type(prop);
  249. QLayout *subLayout = new QHBoxLayout();
  250. const char *name = obs_property_name(prop);
  251. double val = obs_data_get_double(settings, name);
  252. QDoubleSpinBox *spin = new QDoubleSpinBox();
  253. double minVal = obs_property_float_min(prop);
  254. double maxVal = obs_property_float_max(prop);
  255. double stepVal = obs_property_float_step(prop);
  256. spin->setMinimum(minVal);
  257. spin->setMaximum(maxVal);
  258. spin->setSingleStep(stepVal);
  259. spin->setValue(val);
  260. WidgetInfo *info = new WidgetInfo(this, prop, spin);
  261. children.emplace_back(info);
  262. if (type == OBS_NUMBER_SLIDER) {
  263. DoubleSlider *slider = new DoubleSlider();
  264. slider->setDoubleConstraints(minVal, maxVal, stepVal, val);
  265. slider->setOrientation(Qt::Horizontal);
  266. subLayout->addWidget(slider);
  267. connect(slider, SIGNAL(doubleValChanged(double)),
  268. spin, SLOT(setValue(double)));
  269. connect(spin, SIGNAL(valueChanged(double)),
  270. slider, SLOT(setDoubleVal(double)));
  271. }
  272. connect(spin, SIGNAL(valueChanged(double)), info,
  273. SLOT(ControlChanged()));
  274. subLayout->addWidget(spin);
  275. *label = new QLabel(QT_UTF8(obs_property_description(prop)));
  276. layout->addRow(*label, subLayout);
  277. }
  278. static void AddComboItem(QComboBox *combo, obs_property_t *prop,
  279. obs_combo_format format, size_t idx)
  280. {
  281. const char *name = obs_property_list_item_name(prop, idx);
  282. QVariant var;
  283. if (format == OBS_COMBO_FORMAT_INT) {
  284. long long val = obs_property_list_item_int(prop, idx);
  285. var = QVariant::fromValue<long long>(val);
  286. } else if (format == OBS_COMBO_FORMAT_FLOAT) {
  287. double val = obs_property_list_item_float(prop, idx);
  288. var = QVariant::fromValue<double>(val);
  289. } else if (format == OBS_COMBO_FORMAT_STRING) {
  290. var = obs_property_list_item_string(prop, idx);
  291. }
  292. combo->addItem(QT_UTF8(name), var);
  293. if (!obs_property_list_item_disabled(prop, idx))
  294. return;
  295. int index = combo->findText(QT_UTF8(name));
  296. if (index < 0)
  297. return;
  298. QStandardItemModel *model =
  299. dynamic_cast<QStandardItemModel*>(combo->model());
  300. if (!model)
  301. return;
  302. QStandardItem *item = model->item(index);
  303. item->setFlags(Qt::NoItemFlags);
  304. }
  305. template <long long get_int(obs_data_t*, const char*),
  306. double get_double(obs_data_t*, const char*),
  307. const char *get_string(obs_data_t*, const char*)>
  308. static string from_obs_data(obs_data_t *data, const char *name,
  309. obs_combo_format format)
  310. {
  311. switch (format) {
  312. case OBS_COMBO_FORMAT_INT:
  313. return to_string(get_int(data, name));
  314. case OBS_COMBO_FORMAT_FLOAT:
  315. return to_string(get_double(data, name));
  316. case OBS_COMBO_FORMAT_STRING:
  317. return get_string(data, name);
  318. default:
  319. return "";
  320. }
  321. }
  322. static string from_obs_data(obs_data_t *data, const char *name,
  323. obs_combo_format format)
  324. {
  325. return from_obs_data<obs_data_get_int, obs_data_get_double,
  326. obs_data_get_string>(data, name, format);
  327. }
  328. static string from_obs_data_autoselect(obs_data_t *data, const char *name,
  329. obs_combo_format format)
  330. {
  331. return from_obs_data<obs_data_get_autoselect_int,
  332. obs_data_get_autoselect_double,
  333. obs_data_get_autoselect_string>(data, name, format);
  334. }
  335. QWidget *OBSPropertiesView::AddList(obs_property_t *prop, bool &warning)
  336. {
  337. const char *name = obs_property_name(prop);
  338. QComboBox *combo = new QComboBox();
  339. obs_combo_type type = obs_property_list_type(prop);
  340. obs_combo_format format = obs_property_list_format(prop);
  341. size_t count = obs_property_list_item_count(prop);
  342. int idx = -1;
  343. for (size_t i = 0; i < count; i++)
  344. AddComboItem(combo, prop, format, i);
  345. if (type == OBS_COMBO_TYPE_EDITABLE)
  346. combo->setEditable(true);
  347. string value = from_obs_data(settings, name, format);
  348. if (format == OBS_COMBO_FORMAT_STRING &&
  349. type == OBS_COMBO_TYPE_EDITABLE)
  350. combo->lineEdit()->setText(QT_UTF8(value.c_str()));
  351. else
  352. idx = combo->findData(QT_UTF8(value.c_str()));
  353. if (type == OBS_COMBO_TYPE_EDITABLE)
  354. return NewWidget(prop, combo,
  355. SIGNAL(editTextChanged(const QString &)));
  356. if (idx != -1)
  357. combo->setCurrentIndex(idx);
  358. if (obs_data_has_autoselect_value(settings, name)) {
  359. string autoselect =
  360. from_obs_data_autoselect(settings, name, format);
  361. int id = combo->findData(QT_UTF8(autoselect.c_str()));
  362. if (id != -1 && id != idx) {
  363. QString actual = combo->itemText(id);
  364. QString selected = combo->itemText(idx);
  365. QString combined = QTStr(
  366. "Basic.PropertiesWindow.AutoSelectFormat");
  367. combo->setItemText(idx,
  368. combined.arg(selected).arg(actual));
  369. }
  370. }
  371. QAbstractItemModel *model = combo->model();
  372. warning = idx != -1 &&
  373. model->flags(model->index(idx, 0)) == Qt::NoItemFlags;
  374. WidgetInfo *info = new WidgetInfo(this, prop, combo);
  375. connect(combo, SIGNAL(currentIndexChanged(int)), info,
  376. SLOT(ControlChanged()));
  377. children.emplace_back(info);
  378. /* trigger a settings update if the index was not found */
  379. if (idx == -1)
  380. info->ControlChanged();
  381. return combo;
  382. }
  383. static void NewButton(QLayout *layout, WidgetInfo *info,
  384. const char *themeIcon,
  385. void (WidgetInfo::*method)())
  386. {
  387. QPushButton *button = new QPushButton();
  388. button->setProperty("themeID", themeIcon);
  389. button->setFlat(true);
  390. button->setMaximumSize(22, 22);
  391. button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
  392. QObject::connect(button, &QPushButton::clicked, info, method);
  393. layout->addWidget(button);
  394. }
  395. void OBSPropertiesView::AddEditableList(obs_property_t *prop,
  396. QFormLayout *layout, QLabel *&label)
  397. {
  398. const char *name = obs_property_name(prop);
  399. obs_data_array_t *array = obs_data_get_array(settings, name);
  400. QListWidget *list = new QListWidget();
  401. size_t count = obs_data_array_count(array);
  402. list->setSortingEnabled(false);
  403. list->setSelectionMode(QAbstractItemView::ExtendedSelection);
  404. for (size_t i = 0; i < count; i++) {
  405. obs_data_t *item = obs_data_array_item(array, i);
  406. list->addItem(QT_UTF8(obs_data_get_string(item, "value")));
  407. obs_data_release(item);
  408. }
  409. WidgetInfo *info = new WidgetInfo(this, prop, list);
  410. QVBoxLayout *sideLayout = new QVBoxLayout();
  411. NewButton(sideLayout, info, "addIconSmall",
  412. &WidgetInfo::EditListAdd);
  413. NewButton(sideLayout, info, "removeIconSmall",
  414. &WidgetInfo::EditListRemove);
  415. NewButton(sideLayout, info, "configIconSmall",
  416. &WidgetInfo::EditListEdit);
  417. NewButton(sideLayout, info, "upArrowIconSmall",
  418. &WidgetInfo::EditListUp);
  419. NewButton(sideLayout, info, "downArrowIconSmall",
  420. &WidgetInfo::EditListDown);
  421. sideLayout->addStretch(0);
  422. QHBoxLayout *subLayout = new QHBoxLayout();
  423. subLayout->addWidget(list);
  424. subLayout->addLayout(sideLayout);
  425. children.emplace_back(info);
  426. label = new QLabel(QT_UTF8(obs_property_description(prop)));
  427. layout->addRow(label, subLayout);
  428. obs_data_array_release(array);
  429. }
  430. QWidget *OBSPropertiesView::AddButton(obs_property_t *prop)
  431. {
  432. const char *desc = obs_property_description(prop);
  433. QPushButton *button = new QPushButton(QT_UTF8(desc));
  434. button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
  435. return NewWidget(prop, button, SIGNAL(clicked()));
  436. }
  437. void OBSPropertiesView::AddColor(obs_property_t *prop, QFormLayout *layout,
  438. QLabel *&label)
  439. {
  440. QPushButton *button = new QPushButton;
  441. QLabel *colorLabel = new QLabel;
  442. const char *name = obs_property_name(prop);
  443. long long val = obs_data_get_int(settings, name);
  444. QColor color = color_from_int(val);
  445. button->setText(QTStr("Basic.PropertiesWindow.SelectColor"));
  446. colorLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel);
  447. colorLabel->setText(color.name(QColor::HexArgb));
  448. colorLabel->setPalette(QPalette(color));
  449. colorLabel->setAutoFillBackground(true);
  450. colorLabel->setAlignment(Qt::AlignCenter);
  451. QHBoxLayout *subLayout = new QHBoxLayout;
  452. subLayout->setContentsMargins(0, 0, 0, 0);
  453. subLayout->addWidget(colorLabel);
  454. subLayout->addWidget(button);
  455. WidgetInfo *info = new WidgetInfo(this, prop, colorLabel);
  456. connect(button, SIGNAL(clicked()), info, SLOT(ControlChanged()));
  457. children.emplace_back(info);
  458. label = new QLabel(QT_UTF8(obs_property_description(prop)));
  459. layout->addRow(label, subLayout);
  460. }
  461. static void MakeQFont(obs_data_t *font_obj, QFont &font)
  462. {
  463. const char *face = obs_data_get_string(font_obj, "face");
  464. const char *style = obs_data_get_string(font_obj, "style");
  465. int size = (int)obs_data_get_int(font_obj, "size");
  466. uint32_t flags = (uint32_t)obs_data_get_int(font_obj, "flags");
  467. if (face) {
  468. font.setFamily(face);
  469. font.setStyleName(style);
  470. }
  471. if (size)
  472. font.setPointSize(size);
  473. if (flags & OBS_FONT_BOLD) font.setBold(true);
  474. if (flags & OBS_FONT_ITALIC) font.setItalic(true);
  475. if (flags & OBS_FONT_UNDERLINE) font.setUnderline(true);
  476. if (flags & OBS_FONT_STRIKEOUT) font.setStrikeOut(true);
  477. }
  478. void OBSPropertiesView::AddFont(obs_property_t *prop, QFormLayout *layout,
  479. QLabel *&label)
  480. {
  481. const char *name = obs_property_name(prop);
  482. obs_data_t *font_obj = obs_data_get_obj(settings, name);
  483. const char *face = obs_data_get_string(font_obj, "face");
  484. const char *style = obs_data_get_string(font_obj, "style");
  485. QPushButton *button = new QPushButton;
  486. QLabel *fontLabel = new QLabel;
  487. QFont font;
  488. font = fontLabel->font();
  489. MakeQFont(font_obj, font);
  490. button->setText(QTStr("Basic.PropertiesWindow.SelectFont"));
  491. fontLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel);
  492. fontLabel->setFont(font);
  493. fontLabel->setText(QString("%1 %2").arg(face, style));
  494. fontLabel->setAlignment(Qt::AlignCenter);
  495. QHBoxLayout *subLayout = new QHBoxLayout;
  496. subLayout->setContentsMargins(0, 0, 0, 0);
  497. subLayout->addWidget(fontLabel);
  498. subLayout->addWidget(button);
  499. WidgetInfo *info = new WidgetInfo(this, prop, fontLabel);
  500. connect(button, SIGNAL(clicked()), info, SLOT(ControlChanged()));
  501. children.emplace_back(info);
  502. label = new QLabel(QT_UTF8(obs_property_description(prop)));
  503. layout->addRow(label, subLayout);
  504. obs_data_release(font_obj);
  505. }
  506. void OBSPropertiesView::AddProperty(obs_property_t *property,
  507. QFormLayout *layout)
  508. {
  509. const char *name = obs_property_name(property);
  510. obs_property_type type = obs_property_get_type(property);
  511. if (!obs_property_visible(property))
  512. return;
  513. QLabel *label = nullptr;
  514. QWidget *widget = nullptr;
  515. bool warning = false;
  516. switch (type) {
  517. case OBS_PROPERTY_INVALID:
  518. return;
  519. case OBS_PROPERTY_BOOL:
  520. widget = AddCheckbox(property);
  521. break;
  522. case OBS_PROPERTY_INT:
  523. AddInt(property, layout, &label);
  524. break;
  525. case OBS_PROPERTY_FLOAT:
  526. AddFloat(property, layout, &label);
  527. break;
  528. case OBS_PROPERTY_TEXT:
  529. widget = AddText(property, layout, label);
  530. break;
  531. case OBS_PROPERTY_PATH:
  532. AddPath(property, layout, &label);
  533. break;
  534. case OBS_PROPERTY_LIST:
  535. widget = AddList(property, warning);
  536. break;
  537. case OBS_PROPERTY_COLOR:
  538. AddColor(property, layout, label);
  539. break;
  540. case OBS_PROPERTY_FONT:
  541. AddFont(property, layout, label);
  542. break;
  543. case OBS_PROPERTY_BUTTON:
  544. widget = AddButton(property);
  545. break;
  546. case OBS_PROPERTY_EDITABLE_LIST:
  547. AddEditableList(property, layout, label);
  548. break;
  549. }
  550. if (widget && !obs_property_enabled(property))
  551. widget->setEnabled(false);
  552. if (!label &&
  553. type != OBS_PROPERTY_BOOL &&
  554. type != OBS_PROPERTY_BUTTON)
  555. label = new QLabel(QT_UTF8(obs_property_description(property)));
  556. if (warning && label) //TODO: select color based on background color
  557. label->setStyleSheet("QLabel { color: red; }");
  558. if (label && minSize) {
  559. label->setMinimumWidth(minSize);
  560. label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
  561. }
  562. if (!widget)
  563. return;
  564. layout->addRow(label, widget);
  565. if (!lastFocused.empty())
  566. if (lastFocused.compare(name) == 0)
  567. lastWidget = widget;
  568. }
  569. void OBSPropertiesView::SignalChanged()
  570. {
  571. emit Changed();
  572. }
  573. void WidgetInfo::BoolChanged(const char *setting)
  574. {
  575. QCheckBox *checkbox = static_cast<QCheckBox*>(widget);
  576. obs_data_set_bool(view->settings, setting,
  577. checkbox->checkState() == Qt::Checked);
  578. }
  579. void WidgetInfo::IntChanged(const char *setting)
  580. {
  581. QSpinBox *spin = static_cast<QSpinBox*>(widget);
  582. obs_data_set_int(view->settings, setting, spin->value());
  583. }
  584. void WidgetInfo::FloatChanged(const char *setting)
  585. {
  586. QDoubleSpinBox *spin = static_cast<QDoubleSpinBox*>(widget);
  587. obs_data_set_double(view->settings, setting, spin->value());
  588. }
  589. void WidgetInfo::TextChanged(const char *setting)
  590. {
  591. obs_text_type type = obs_proprety_text_type(property);
  592. if (type == OBS_TEXT_MULTILINE) {
  593. QPlainTextEdit *edit = static_cast<QPlainTextEdit*>(widget);
  594. obs_data_set_string(view->settings, setting,
  595. QT_TO_UTF8(edit->toPlainText()));
  596. return;
  597. }
  598. QLineEdit *edit = static_cast<QLineEdit*>(widget);
  599. obs_data_set_string(view->settings, setting, QT_TO_UTF8(edit->text()));
  600. }
  601. bool WidgetInfo::PathChanged(const char *setting)
  602. {
  603. const char *desc = obs_property_description(property);
  604. obs_path_type type = obs_property_path_type(property);
  605. const char *filter = obs_property_path_filter(property);
  606. const char *default_path = obs_property_path_default_path(property);
  607. QString path;
  608. if (type == OBS_PATH_DIRECTORY)
  609. path = QFileDialog::getExistingDirectory(view,
  610. QT_UTF8(desc), QT_UTF8(default_path),
  611. QFileDialog::ShowDirsOnly |
  612. QFileDialog::DontResolveSymlinks);
  613. else if (type == OBS_PATH_FILE)
  614. path = QFileDialog::getOpenFileName(view,
  615. QT_UTF8(desc), QT_UTF8(default_path),
  616. QT_UTF8(filter));
  617. if (path.isEmpty())
  618. return false;
  619. QLineEdit *edit = static_cast<QLineEdit*>(widget);
  620. edit->setText(path);
  621. obs_data_set_string(view->settings, setting, QT_TO_UTF8(path));
  622. return true;
  623. }
  624. void WidgetInfo::ListChanged(const char *setting)
  625. {
  626. QComboBox *combo = static_cast<QComboBox*>(widget);
  627. obs_combo_format format = obs_property_list_format(property);
  628. obs_combo_type type = obs_property_list_type(property);
  629. QVariant data;
  630. if (type == OBS_COMBO_TYPE_EDITABLE) {
  631. data = combo->currentText();
  632. } else {
  633. int index = combo->currentIndex();
  634. if (index != -1)
  635. data = combo->itemData(index);
  636. else
  637. return;
  638. }
  639. switch (format) {
  640. case OBS_COMBO_FORMAT_INVALID:
  641. return;
  642. case OBS_COMBO_FORMAT_INT:
  643. obs_data_set_int(view->settings, setting,
  644. data.value<long long>());
  645. break;
  646. case OBS_COMBO_FORMAT_FLOAT:
  647. obs_data_set_double(view->settings, setting,
  648. data.value<double>());
  649. break;
  650. case OBS_COMBO_FORMAT_STRING:
  651. obs_data_set_string(view->settings, setting,
  652. QT_TO_UTF8(data.toString()));
  653. break;
  654. }
  655. }
  656. bool WidgetInfo::ColorChanged(const char *setting)
  657. {
  658. const char *desc = obs_property_description(property);
  659. long long val = obs_data_get_int(view->settings, setting);
  660. QColor color = color_from_int(val);
  661. QColorDialog::ColorDialogOptions options =
  662. QColorDialog::ShowAlphaChannel;
  663. /* The native dialog on OSX has all kinds of problems, like closing
  664. * other open QDialogs on exit, and
  665. * https://bugreports.qt-project.org/browse/QTBUG-34532
  666. */
  667. #ifdef __APPLE__
  668. options |= QColorDialog::DontUseNativeDialog;
  669. #endif
  670. color = QColorDialog::getColor(color, view, QT_UTF8(desc), options);
  671. if (!color.isValid())
  672. return false;
  673. QLabel *label = static_cast<QLabel*>(widget);
  674. label->setText(color.name(QColor::HexArgb));
  675. label->setPalette(QPalette(color));
  676. obs_data_set_int(view->settings, setting, color_to_int(color));
  677. return true;
  678. }
  679. bool WidgetInfo::FontChanged(const char *setting)
  680. {
  681. obs_data_t *font_obj = obs_data_get_obj(view->settings, setting);
  682. bool success;
  683. uint32_t flags;
  684. QFont font;
  685. if (!font_obj) {
  686. font = QFontDialog::getFont(&success, view);
  687. } else {
  688. MakeQFont(font_obj, font);
  689. font = QFontDialog::getFont(&success, font, view);
  690. obs_data_release(font_obj);
  691. }
  692. if (!success)
  693. return false;
  694. font_obj = obs_data_create();
  695. obs_data_set_string(font_obj, "face", QT_TO_UTF8(font.family()));
  696. obs_data_set_string(font_obj, "style", QT_TO_UTF8(font.styleName()));
  697. obs_data_set_int(font_obj, "size", font.pointSize());
  698. flags = font.bold() ? OBS_FONT_BOLD : 0;
  699. flags |= font.italic() ? OBS_FONT_ITALIC : 0;
  700. flags |= font.underline() ? OBS_FONT_UNDERLINE : 0;
  701. flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0;
  702. obs_data_set_int(font_obj, "flags", flags);
  703. QLabel *label = static_cast<QLabel*>(widget);
  704. label->setFont(font);
  705. label->setText(QString("%1 %2").arg(font.family(), font.styleName()));
  706. obs_data_set_obj(view->settings, setting, font_obj);
  707. obs_data_release(font_obj);
  708. return true;
  709. }
  710. void WidgetInfo::EditableListChanged()
  711. {
  712. const char *setting = obs_property_name(property);
  713. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  714. obs_data_array *array = obs_data_array_create();
  715. for (int i = 0; i < list->count(); i++) {
  716. QListWidgetItem *item = list->item(i);
  717. obs_data_t *arrayItem = obs_data_create();
  718. obs_data_set_string(arrayItem, "value",
  719. QT_TO_UTF8(item->text()));
  720. obs_data_array_push_back(array, arrayItem);
  721. obs_data_release(arrayItem);
  722. }
  723. obs_data_set_array(view->settings, setting, array);
  724. obs_data_array_release(array);
  725. }
  726. void WidgetInfo::ButtonClicked()
  727. {
  728. if (obs_property_button_clicked(property, view->obj)) {
  729. QMetaObject::invokeMethod(view, "RefreshProperties",
  730. Qt::QueuedConnection);
  731. }
  732. }
  733. void WidgetInfo::TogglePasswordText(bool show)
  734. {
  735. reinterpret_cast<QLineEdit*>(widget)->setEchoMode(
  736. show ? QLineEdit::Normal : QLineEdit::Password);
  737. }
  738. void WidgetInfo::ControlChanged()
  739. {
  740. const char *setting = obs_property_name(property);
  741. obs_property_type type = obs_property_get_type(property);
  742. switch (type) {
  743. case OBS_PROPERTY_INVALID: return;
  744. case OBS_PROPERTY_BOOL: BoolChanged(setting); break;
  745. case OBS_PROPERTY_INT: IntChanged(setting); break;
  746. case OBS_PROPERTY_FLOAT: FloatChanged(setting); break;
  747. case OBS_PROPERTY_TEXT: TextChanged(setting); break;
  748. case OBS_PROPERTY_LIST: ListChanged(setting); break;
  749. case OBS_PROPERTY_BUTTON: ButtonClicked(); return;
  750. case OBS_PROPERTY_COLOR:
  751. if (!ColorChanged(setting))
  752. return;
  753. break;
  754. case OBS_PROPERTY_FONT:
  755. if (!FontChanged(setting))
  756. return;
  757. break;
  758. case OBS_PROPERTY_PATH:
  759. if (!PathChanged(setting))
  760. return;
  761. break;
  762. case OBS_PROPERTY_EDITABLE_LIST: return;
  763. }
  764. if (view->callback && !view->deferUpdate)
  765. view->callback(view->obj, view->settings);
  766. view->SignalChanged();
  767. if (obs_property_modified(property, view->settings)) {
  768. view->lastFocused = setting;
  769. QMetaObject::invokeMethod(view, "RefreshProperties",
  770. Qt::QueuedConnection);
  771. }
  772. }
  773. class EditableItemDialog : public QDialog {
  774. QLineEdit *edit;
  775. QString filter;
  776. QString default_path;
  777. void BrowseClicked()
  778. {
  779. QString curPath = QFileInfo(edit->text()).absoluteDir().path();
  780. if (curPath.isEmpty())
  781. curPath = default_path;
  782. QString path = QFileDialog::getOpenFileName(
  783. App()->GetMainWindow(), QTStr("Browse"),
  784. curPath, filter);
  785. if (path.isEmpty())
  786. return;
  787. edit->setText(path);
  788. }
  789. public:
  790. EditableItemDialog(QWidget *parent, const QString &text,
  791. bool browse, const char *filter_ = nullptr,
  792. const char *default_path_ = nullptr)
  793. : QDialog (parent),
  794. filter (QT_UTF8(filter_)),
  795. default_path (QT_UTF8(default_path_))
  796. {
  797. QHBoxLayout *topLayout = new QHBoxLayout();
  798. QVBoxLayout *mainLayout = new QVBoxLayout();
  799. edit = new QLineEdit();
  800. edit->setText(text);
  801. topLayout->addWidget(edit);
  802. topLayout->setAlignment(edit, Qt::AlignVCenter);
  803. if (browse) {
  804. QPushButton *browseButton =
  805. new QPushButton(QTStr("Browse"));
  806. topLayout->addWidget(browseButton);
  807. topLayout->setAlignment(browseButton, Qt::AlignVCenter);
  808. connect(browseButton, &QPushButton::clicked, this,
  809. &EditableItemDialog::BrowseClicked);
  810. }
  811. QDialogButtonBox::StandardButtons buttons =
  812. QDialogButtonBox::Ok |
  813. QDialogButtonBox::Cancel;
  814. QDialogButtonBox *buttonBox = new QDialogButtonBox(buttons);
  815. buttonBox->setCenterButtons(true);
  816. mainLayout->addLayout(topLayout);
  817. mainLayout->addWidget(buttonBox);
  818. setLayout(mainLayout);
  819. resize(QSize(400, 80));
  820. connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
  821. connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
  822. }
  823. inline QString GetText() const {return edit->text();}
  824. };
  825. void WidgetInfo::EditListAdd()
  826. {
  827. bool allow_files = obs_property_editable_list_allow_files(property);
  828. if (!allow_files) {
  829. EditListAddText();
  830. return;
  831. }
  832. QMenu popup(view->window());
  833. QAction *action;
  834. action = new QAction(QTStr("Basic.PropertiesWindow.AddFiles"), this);
  835. connect(action, &QAction::triggered,
  836. this, &WidgetInfo::EditListAddFiles);
  837. popup.addAction(action);
  838. action = new QAction(QTStr("Basic.PropertiesWindow.AddURL"), this);
  839. connect(action, &QAction::triggered,
  840. this, &WidgetInfo::EditListAddText);
  841. popup.addAction(action);
  842. popup.exec(QCursor::pos());
  843. }
  844. void WidgetInfo::EditListAddText()
  845. {
  846. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  847. const char *desc = obs_property_description(property);
  848. EditableItemDialog dialog(widget->window(), QString(), false);
  849. auto title = QTStr("Basic.PropertiesWindow.AddEditableListEntry").arg(
  850. QT_UTF8(desc));
  851. dialog.setWindowTitle(title);
  852. if (dialog.exec() == QDialog::Rejected)
  853. return;
  854. QString text = dialog.GetText();
  855. if (text.isEmpty())
  856. return;
  857. list->addItem(text);
  858. EditableListChanged();
  859. }
  860. void WidgetInfo::EditListAddFiles()
  861. {
  862. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  863. const char *desc = obs_property_description(property);
  864. const char *filter = obs_property_editable_list_filter(property);
  865. const char *default_path =
  866. obs_property_editable_list_default_path(property);
  867. QString title = QTStr("Basic.PropertiesWindow.AddEditableListFiles")
  868. .arg(QT_UTF8(desc));
  869. QStringList files = QFileDialog::getOpenFileNames(
  870. App()->GetMainWindow(), title, QT_UTF8(default_path),
  871. QT_UTF8(filter));
  872. if (files.count() == 0)
  873. return;
  874. list->addItems(files);
  875. EditableListChanged();
  876. }
  877. void WidgetInfo::EditListRemove()
  878. {
  879. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  880. QList<QListWidgetItem*> items = list->selectedItems();
  881. for (QListWidgetItem *item : items)
  882. delete item;
  883. EditableListChanged();
  884. }
  885. void WidgetInfo::EditListEdit()
  886. {
  887. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  888. bool allow_files = obs_property_editable_list_allow_files(property);
  889. const char *desc = obs_property_description(property);
  890. const char *filter = obs_property_editable_list_filter(property);
  891. QList<QListWidgetItem*> selectedItems = list->selectedItems();
  892. if (!selectedItems.count())
  893. return;
  894. QListWidgetItem *item = selectedItems[0];
  895. EditableItemDialog dialog(widget->window(), item->text(), allow_files,
  896. filter);
  897. auto title = QTStr("Basic.PropertiesWindow.EditEditableListEntry").arg(
  898. QT_UTF8(desc));
  899. dialog.setWindowTitle(title);
  900. if (dialog.exec() == QDialog::Rejected)
  901. return;
  902. QString text = dialog.GetText();
  903. if (text.isEmpty())
  904. return;
  905. item->setText(text);
  906. EditableListChanged();
  907. }
  908. void WidgetInfo::EditListUp()
  909. {
  910. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  911. int lastItemRow = -1;
  912. for (int i = 0; i < list->count(); i++) {
  913. QListWidgetItem *item = list->item(i);
  914. if (!item->isSelected())
  915. continue;
  916. int row = list->row(item);
  917. if ((row - 1) != lastItemRow) {
  918. lastItemRow = row - 1;
  919. list->takeItem(row);
  920. list->insertItem(lastItemRow, item);
  921. item->setSelected(true);
  922. } else {
  923. lastItemRow = row;
  924. }
  925. }
  926. EditableListChanged();
  927. }
  928. void WidgetInfo::EditListDown()
  929. {
  930. QListWidget *list = reinterpret_cast<QListWidget*>(widget);
  931. int lastItemRow = list->count();
  932. for (int i = list->count() - 1; i >= 0; i--) {
  933. QListWidgetItem *item = list->item(i);
  934. if (!item->isSelected())
  935. continue;
  936. int row = list->row(item);
  937. if ((row + 1) != lastItemRow) {
  938. lastItemRow = row + 1;
  939. list->takeItem(row);
  940. list->insertItem(lastItemRow, item);
  941. item->setSelected(true);
  942. } else {
  943. lastItemRow = row;
  944. }
  945. }
  946. EditableListChanged();
  947. }