properties-view.cpp 31 KB

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