node_menu.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /* node_menu.cpp - node context menu
  2. * Copyright (C) 2017-2018 caryoscelus
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <QItemSelectionModel>
  18. #include <QInputDialog>
  19. #include <QWidgetAction>
  20. #include <util/strings.h>
  21. #include <models/node_model.h>
  22. #include "node_menu.h"
  23. namespace rainynite::studio {
  24. NodeContextMenu::NodeContextMenu(NodeModel* model_, QItemSelectionModel* selection_model, core::Time time_) :
  25. model(model_),
  26. time(time_)
  27. {
  28. index = selection_model->currentIndex();
  29. auto parent_index = index.parent();
  30. if (auto parent_node = dynamic_pointer_cast<core::AbstractListLinked>(model->get_node(parent_index))) {
  31. size_t node_index = model->get_node_index(index);
  32. auto type_constraint = parent_node->get_link_type(node_index);
  33. auto t = core::all_node_infos();
  34. node_infos = {};
  35. std::copy_if(
  36. t.begin(),
  37. t.end(),
  38. std::inserter(node_infos, node_infos.end()),
  39. [type_constraint](auto info) {
  40. return type_constraint.accept(info->type());
  41. }
  42. );
  43. selection = selection_model->selectedIndexes();
  44. // selection may contain other columns, which should be ignored
  45. selection.erase(std::remove_if(
  46. std::begin(selection),
  47. std::end(selection),
  48. [](auto const& index) {
  49. return index.column() != 0;
  50. }
  51. ));
  52. if (selection.size() > 1) {
  53. addAction(
  54. QIcon::fromTheme("insert-link"),
  55. "Connect",
  56. [this]() {
  57. model->connect_nodes(selection, index);
  58. }
  59. );
  60. }
  61. if (selection.size() == 2) {
  62. auto a = selection[0];
  63. auto b = selection[1];
  64. addAction(
  65. QIcon::fromTheme("exchange-positions"),
  66. "Swap",
  67. [this, a, b]() {
  68. model->swap_nodes(a, b);
  69. }
  70. );
  71. }
  72. if (model->node_is_connected(index)) {
  73. addAction(
  74. QIcon::fromTheme("remove-link"),
  75. "Disconnect",
  76. [this]() {
  77. model->disconnect_node(index);
  78. // NOTE: this won't return active node back on undo!
  79. // TODO: replace this quick-fix with proper notification-based system
  80. model->get_context()->set_active_node(model->get_inner_index(index));
  81. }
  82. );
  83. }
  84. if (model->can_remove_node(index)) {
  85. addAction(
  86. QIcon::fromTheme("list-remove"), // TODO
  87. "Remove",
  88. [this]() {
  89. model->remove_node(index);
  90. }
  91. );
  92. }
  93. auto on_off_action = addAction("Enabled");
  94. on_off_action->setCheckable(true);
  95. on_off_action->setChecked(model->node_enabled(index));
  96. connect(on_off_action, &QAction::toggled, [this](bool value) {
  97. model->node_set_enabled(index, value);
  98. });
  99. if (model->can_clear_list(index)) {
  100. addAction(
  101. QIcon::fromTheme("edit-clear"),
  102. "Clear list",
  103. [this]() {
  104. model->clear_list(index);
  105. }
  106. );
  107. }
  108. if (model->can_add_element(index)) {
  109. addAction(
  110. QIcon::fromTheme("list-add"),
  111. "Add element",
  112. [this]() {
  113. model->add_empty_element(index);
  114. }
  115. );
  116. addSeparator();
  117. }
  118. if (model->can_add_custom_property(index)) {
  119. add_custom_property();
  120. }
  121. if (model->is_custom_property(index)) {
  122. addAction(
  123. QIcon::fromTheme("list-remove"), // TODO
  124. "Remove custom property",
  125. [this]() {
  126. model->remove_custom_property(index);
  127. }
  128. );
  129. addSeparator();
  130. }
  131. if (parent_node->is_editable_list()) {
  132. addAction(
  133. QIcon::fromTheme("list-remove"),
  134. "Remove list item",
  135. [this, node_index, parent_index]() {
  136. model->remove_list_item(parent_index, node_index);
  137. }
  138. );
  139. auto up_action = addAction(
  140. QIcon::fromTheme("go-up"),
  141. "Move up",
  142. [this, node_index, parent_index]() {
  143. model->move_up(node_index, parent_index);
  144. }
  145. );
  146. auto down_action = addAction(
  147. QIcon::fromTheme("go-down"),
  148. "Move down",
  149. [this, node_index, parent_index]() {
  150. model->move_down(node_index, parent_index);
  151. }
  152. );
  153. up_action->setEnabled(model->can_move_up(node_index, parent_index));
  154. down_action->setEnabled(model->can_move_down(node_index, parent_index));
  155. addSeparator();
  156. }
  157. if (node_infos.size() == 0)
  158. addAction("No node types available!");
  159. else {
  160. auto search_action = new QWidgetAction(this);
  161. search_widget = new QLineEdit();
  162. connect(this, SIGNAL(aboutToShow()), search_widget, SLOT(setFocus()));
  163. connect(search_widget, &QLineEdit::textEdited, this, &NodeContextMenu::update_node_list);
  164. search_action->setDefaultWidget(search_widget);
  165. addAction(search_action);
  166. update_node_list();
  167. }
  168. } else {
  169. add_custom_property();
  170. }
  171. }
  172. void NodeContextMenu::update_node_list() {
  173. for (auto action : convert_actions) {
  174. removeAction(action);
  175. }
  176. convert_actions.clear();
  177. auto search_string = search_widget->text().toLower();
  178. bool first_action = true;
  179. for (auto node_info : node_infos) {
  180. auto name = util::str(node_info->name());
  181. if (name.toLower().indexOf(search_string) != -1) {
  182. auto action = addAction(name, [this, node_info]() {
  183. model->convert_node(index, node_info);
  184. });
  185. convert_actions.push_back(action);
  186. if (first_action) {
  187. setActiveAction(action);
  188. first_action = false;
  189. }
  190. }
  191. }
  192. }
  193. void NodeContextMenu::add_custom_property() {
  194. addAction(
  195. QIcon::fromTheme("list-add"), // TODO
  196. "Add custom property",
  197. [this]() {
  198. auto text = QInputDialog::getText(
  199. nullptr,
  200. "Add new custom property",
  201. "Property name:"
  202. );
  203. if (!text.isEmpty())
  204. model->add_empty_custom_property(index, util::str(text));
  205. }
  206. );
  207. addSeparator();
  208. }
  209. } // namespace rainynite::studio