window-basic-transform.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #include <QMessageBox>
  2. #include "window-basic-transform.hpp"
  3. #include "window-basic-main.hpp"
  4. Q_DECLARE_METATYPE(OBSScene);
  5. Q_DECLARE_METATYPE(OBSSceneItem);
  6. static bool find_sel(obs_scene_t *, obs_sceneitem_t *item, void *param)
  7. {
  8. OBSSceneItem &dst = *reinterpret_cast<OBSSceneItem *>(param);
  9. if (obs_sceneitem_selected(item)) {
  10. dst = item;
  11. return false;
  12. }
  13. if (obs_sceneitem_is_group(item)) {
  14. obs_sceneitem_group_enum_items(item, find_sel, param);
  15. if (!!dst) {
  16. return false;
  17. }
  18. }
  19. return true;
  20. };
  21. static OBSSceneItem FindASelectedItem(obs_scene_t *scene)
  22. {
  23. OBSSceneItem item;
  24. obs_scene_enum_items(scene, find_sel, &item);
  25. return item;
  26. }
  27. #define COMBO_CHANGED &QComboBox::currentIndexChanged
  28. #define ISCROLL_CHANGED &QSpinBox::valueChanged
  29. #define DSCROLL_CHANGED &QDoubleSpinBox::valueChanged
  30. OBSBasicTransform::OBSBasicTransform(OBSSceneItem item, OBSBasic *parent)
  31. : QDialog(parent),
  32. ui(new Ui::OBSBasicTransform),
  33. main(parent)
  34. {
  35. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  36. ui->setupUi(this);
  37. HookWidget(ui->positionX, DSCROLL_CHANGED,
  38. &OBSBasicTransform::OnControlChanged);
  39. HookWidget(ui->positionY, DSCROLL_CHANGED,
  40. &OBSBasicTransform::OnControlChanged);
  41. HookWidget(ui->rotation, DSCROLL_CHANGED,
  42. &OBSBasicTransform::OnControlChanged);
  43. HookWidget(ui->sizeX, DSCROLL_CHANGED,
  44. &OBSBasicTransform::OnControlChanged);
  45. HookWidget(ui->sizeY, DSCROLL_CHANGED,
  46. &OBSBasicTransform::OnControlChanged);
  47. HookWidget(ui->align, COMBO_CHANGED,
  48. &OBSBasicTransform::OnControlChanged);
  49. HookWidget(ui->boundsType, COMBO_CHANGED,
  50. &OBSBasicTransform::OnBoundsType);
  51. HookWidget(ui->boundsAlign, COMBO_CHANGED,
  52. &OBSBasicTransform::OnControlChanged);
  53. HookWidget(ui->boundsWidth, DSCROLL_CHANGED,
  54. &OBSBasicTransform::OnControlChanged);
  55. HookWidget(ui->boundsHeight, DSCROLL_CHANGED,
  56. &OBSBasicTransform::OnControlChanged);
  57. HookWidget(ui->cropLeft, ISCROLL_CHANGED,
  58. &OBSBasicTransform::OnCropChanged);
  59. HookWidget(ui->cropRight, ISCROLL_CHANGED,
  60. &OBSBasicTransform::OnCropChanged);
  61. HookWidget(ui->cropTop, ISCROLL_CHANGED,
  62. &OBSBasicTransform::OnCropChanged);
  63. HookWidget(ui->cropBottom, ISCROLL_CHANGED,
  64. &OBSBasicTransform::OnCropChanged);
  65. HookWidget(ui->cropToBounds, &QCheckBox::stateChanged,
  66. &OBSBasicTransform::OnControlChanged);
  67. ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
  68. connect(ui->buttonBox->button(QDialogButtonBox::Reset),
  69. &QPushButton::clicked, main,
  70. &OBSBasic::on_actionResetTransform_triggered);
  71. installEventFilter(CreateShortcutFilter());
  72. OBSScene scene = obs_sceneitem_get_scene(item);
  73. SetScene(scene);
  74. SetItem(item);
  75. std::string name = obs_source_get_name(obs_sceneitem_get_source(item));
  76. setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
  77. OBSDataAutoRelease wrapper =
  78. obs_scene_save_transform_states(main->GetCurrentScene(), false);
  79. undo_data = std::string(obs_data_get_json(wrapper));
  80. channelChangedSignal.Connect(obs_get_signal_handler(), "channel_change",
  81. OBSChannelChanged, this);
  82. }
  83. OBSBasicTransform::~OBSBasicTransform()
  84. {
  85. OBSDataAutoRelease wrapper =
  86. obs_scene_save_transform_states(main->GetCurrentScene(), false);
  87. auto undo_redo = [](const std::string &data) {
  88. OBSDataAutoRelease dat =
  89. obs_data_create_from_json(data.c_str());
  90. OBSSourceAutoRelease source = obs_get_source_by_uuid(
  91. obs_data_get_string(dat, "scene_uuid"));
  92. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  93. ->SetCurrentScene(source.Get(), true);
  94. obs_scene_load_transform_states(data.c_str());
  95. };
  96. std::string redo_data(obs_data_get_json(wrapper));
  97. if (undo_data.compare(redo_data) != 0)
  98. main->undo_s.add_action(
  99. QTStr("Undo.Transform")
  100. .arg(obs_source_get_name(obs_scene_get_source(
  101. main->GetCurrentScene()))),
  102. undo_redo, undo_redo, undo_data, redo_data);
  103. }
  104. void OBSBasicTransform::SetScene(OBSScene scene)
  105. {
  106. sigs.clear();
  107. if (scene) {
  108. OBSSource source = obs_scene_get_source(scene);
  109. signal_handler_t *signal =
  110. obs_source_get_signal_handler(source);
  111. sigs.emplace_back(signal, "item_transform",
  112. OBSSceneItemTransform, this);
  113. sigs.emplace_back(signal, "item_remove", OBSSceneItemRemoved,
  114. this);
  115. sigs.emplace_back(signal, "item_select", OBSSceneItemSelect,
  116. this);
  117. sigs.emplace_back(signal, "item_deselect", OBSSceneItemDeselect,
  118. this);
  119. sigs.emplace_back(signal, "item_locked", OBSSceneItemLocked,
  120. this);
  121. }
  122. }
  123. void OBSBasicTransform::SetItem(OBSSceneItem newItem)
  124. {
  125. QMetaObject::invokeMethod(this, "SetItemQt",
  126. Q_ARG(OBSSceneItem, OBSSceneItem(newItem)));
  127. }
  128. void OBSBasicTransform::SetEnabled(bool enable)
  129. {
  130. ui->container->setEnabled(enable);
  131. ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(enable);
  132. }
  133. void OBSBasicTransform::SetItemQt(OBSSceneItem newItem)
  134. {
  135. item = newItem;
  136. if (item)
  137. RefreshControls();
  138. bool enable = !!item && !obs_sceneitem_locked(item);
  139. SetEnabled(enable);
  140. }
  141. void OBSBasicTransform::OBSChannelChanged(void *param, calldata_t *data)
  142. {
  143. OBSBasicTransform *window =
  144. reinterpret_cast<OBSBasicTransform *>(param);
  145. uint32_t channel = (uint32_t)calldata_int(data, "channel");
  146. OBSSource source = (obs_source_t *)calldata_ptr(data, "source");
  147. if (channel == 0) {
  148. OBSScene scene = obs_scene_from_source(source);
  149. window->SetScene(scene);
  150. if (!scene)
  151. window->SetItem(nullptr);
  152. else
  153. window->SetItem(FindASelectedItem(scene));
  154. }
  155. }
  156. void OBSBasicTransform::OBSSceneItemTransform(void *param, calldata_t *data)
  157. {
  158. OBSBasicTransform *window =
  159. reinterpret_cast<OBSBasicTransform *>(param);
  160. OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  161. if (item == window->item && !window->ignoreTransformSignal)
  162. QMetaObject::invokeMethod(window, "RefreshControls");
  163. }
  164. void OBSBasicTransform::OBSSceneItemRemoved(void *param, calldata_t *data)
  165. {
  166. OBSBasicTransform *window =
  167. reinterpret_cast<OBSBasicTransform *>(param);
  168. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(data, "scene");
  169. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  170. if (item == window->item)
  171. window->SetItem(FindASelectedItem(scene));
  172. }
  173. void OBSBasicTransform::OBSSceneItemSelect(void *param, calldata_t *data)
  174. {
  175. OBSBasicTransform *window =
  176. reinterpret_cast<OBSBasicTransform *>(param);
  177. OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  178. if (item != window->item)
  179. window->SetItem(item);
  180. }
  181. void OBSBasicTransform::OBSSceneItemDeselect(void *param, calldata_t *data)
  182. {
  183. OBSBasicTransform *window =
  184. reinterpret_cast<OBSBasicTransform *>(param);
  185. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(data, "scene");
  186. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  187. if (item == window->item) {
  188. window->setWindowTitle(
  189. QTStr("Basic.TransformWindow.NoSelectedSource"));
  190. window->SetItem(FindASelectedItem(scene));
  191. }
  192. }
  193. void OBSBasicTransform::OBSSceneItemLocked(void *param, calldata_t *data)
  194. {
  195. OBSBasicTransform *window =
  196. reinterpret_cast<OBSBasicTransform *>(param);
  197. bool locked = calldata_bool(data, "locked");
  198. QMetaObject::invokeMethod(window, "SetEnabled", Q_ARG(bool, !locked));
  199. }
  200. static const uint32_t listToAlign[] = {OBS_ALIGN_TOP | OBS_ALIGN_LEFT,
  201. OBS_ALIGN_TOP,
  202. OBS_ALIGN_TOP | OBS_ALIGN_RIGHT,
  203. OBS_ALIGN_LEFT,
  204. OBS_ALIGN_CENTER,
  205. OBS_ALIGN_RIGHT,
  206. OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT,
  207. OBS_ALIGN_BOTTOM,
  208. OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT};
  209. static int AlignToList(uint32_t align)
  210. {
  211. int index = 0;
  212. for (uint32_t curAlign : listToAlign) {
  213. if (curAlign == align)
  214. return index;
  215. index++;
  216. }
  217. return 0;
  218. }
  219. void OBSBasicTransform::RefreshControls()
  220. {
  221. if (!item)
  222. return;
  223. obs_transform_info osi;
  224. obs_sceneitem_crop crop;
  225. obs_sceneitem_get_info2(item, &osi);
  226. obs_sceneitem_get_crop(item, &crop);
  227. obs_source_t *source = obs_sceneitem_get_source(item);
  228. uint32_t source_cx = obs_source_get_width(source);
  229. uint32_t source_cy = obs_source_get_height(source);
  230. float width = float(source_cx);
  231. float height = float(source_cy);
  232. int alignIndex = AlignToList(osi.alignment);
  233. int boundsAlignIndex = AlignToList(osi.bounds_alignment);
  234. ignoreItemChange = true;
  235. ui->positionX->setValue(osi.pos.x);
  236. ui->positionY->setValue(osi.pos.y);
  237. ui->rotation->setValue(osi.rot);
  238. ui->sizeX->setValue(osi.scale.x * width);
  239. ui->sizeY->setValue(osi.scale.y * height);
  240. ui->align->setCurrentIndex(alignIndex);
  241. bool valid_size = source_cx != 0 && source_cy != 0;
  242. ui->sizeX->setEnabled(valid_size);
  243. ui->sizeY->setEnabled(valid_size);
  244. ui->boundsType->setCurrentIndex(int(osi.bounds_type));
  245. ui->boundsAlign->setCurrentIndex(boundsAlignIndex);
  246. ui->boundsWidth->setValue(osi.bounds.x);
  247. ui->boundsHeight->setValue(osi.bounds.y);
  248. ui->cropToBounds->setChecked(osi.crop_to_bounds);
  249. ui->cropLeft->setValue(int(crop.left));
  250. ui->cropRight->setValue(int(crop.right));
  251. ui->cropTop->setValue(int(crop.top));
  252. ui->cropBottom->setValue(int(crop.bottom));
  253. ignoreItemChange = false;
  254. std::string name = obs_source_get_name(source);
  255. setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
  256. }
  257. void OBSBasicTransform::OnBoundsType(int index)
  258. {
  259. if (index == -1)
  260. return;
  261. obs_bounds_type type = (obs_bounds_type)index;
  262. bool enable = (type != OBS_BOUNDS_NONE);
  263. ui->boundsAlign->setEnabled(enable);
  264. ui->boundsWidth->setEnabled(enable);
  265. ui->boundsHeight->setEnabled(enable);
  266. ui->cropToBounds->setEnabled(enable);
  267. if (!ignoreItemChange) {
  268. obs_bounds_type lastType = obs_sceneitem_get_bounds_type(item);
  269. if (lastType == OBS_BOUNDS_NONE) {
  270. OBSSource source = obs_sceneitem_get_source(item);
  271. int width = (int)obs_source_get_width(source);
  272. int height = (int)obs_source_get_height(source);
  273. ui->boundsWidth->setValue(width);
  274. ui->boundsHeight->setValue(height);
  275. }
  276. }
  277. OnControlChanged();
  278. }
  279. void OBSBasicTransform::OnControlChanged()
  280. {
  281. if (ignoreItemChange)
  282. return;
  283. obs_source_t *source = obs_sceneitem_get_source(item);
  284. uint32_t source_cx = obs_source_get_width(source);
  285. uint32_t source_cy = obs_source_get_height(source);
  286. double width = double(source_cx);
  287. double height = double(source_cy);
  288. obs_transform_info oti;
  289. obs_sceneitem_get_info2(item, &oti);
  290. /* do not scale a source if it has 0 width/height */
  291. if (source_cx != 0 && source_cy != 0) {
  292. oti.scale.x = float(ui->sizeX->value() / width);
  293. oti.scale.y = float(ui->sizeY->value() / height);
  294. }
  295. oti.pos.x = float(ui->positionX->value());
  296. oti.pos.y = float(ui->positionY->value());
  297. oti.rot = float(ui->rotation->value());
  298. oti.alignment = listToAlign[ui->align->currentIndex()];
  299. oti.bounds_type = (obs_bounds_type)ui->boundsType->currentIndex();
  300. oti.bounds_alignment = listToAlign[ui->boundsAlign->currentIndex()];
  301. oti.bounds.x = float(ui->boundsWidth->value());
  302. oti.bounds.y = float(ui->boundsHeight->value());
  303. oti.crop_to_bounds = ui->cropToBounds->isChecked();
  304. ignoreTransformSignal = true;
  305. obs_sceneitem_set_info2(item, &oti);
  306. ignoreTransformSignal = false;
  307. }
  308. void OBSBasicTransform::OnCropChanged()
  309. {
  310. if (ignoreItemChange)
  311. return;
  312. obs_sceneitem_crop crop;
  313. crop.left = uint32_t(ui->cropLeft->value());
  314. crop.right = uint32_t(ui->cropRight->value());
  315. crop.top = uint32_t(ui->cropTop->value());
  316. crop.bottom = uint32_t(ui->cropBottom->value());
  317. ignoreTransformSignal = true;
  318. obs_sceneitem_set_crop(item, &crop);
  319. ignoreTransformSignal = false;
  320. }
  321. template<typename T> static T GetOBSRef(QListWidgetItem *item)
  322. {
  323. return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
  324. }
  325. void OBSBasicTransform::OnSceneChanged(QListWidgetItem *current,
  326. QListWidgetItem *)
  327. {
  328. if (!current)
  329. return;
  330. OBSScene scene = GetOBSRef<OBSScene>(current);
  331. this->SetScene(scene);
  332. }