You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

708 lines
18KB

  1. #include <map>
  2. #include <algorithm>
  3. #include <queue>
  4. #include <osdialog.h>
  5. #include <app/RackWidget.hpp>
  6. #include <widget/TransparentWidget.hpp>
  7. #include <app/RackRail.hpp>
  8. #include <app/Scene.hpp>
  9. #include <app/ModuleBrowser.hpp>
  10. #include <settings.hpp>
  11. #include <plugin.hpp>
  12. #include <engine/Engine.hpp>
  13. #include <context.hpp>
  14. #include <asset.hpp>
  15. #include <patch.hpp>
  16. namespace rack {
  17. namespace app {
  18. /** Creates a new Module and ModuleWidget */
  19. static ModuleWidget* moduleWidgetFromJson(json_t* moduleJ) {
  20. plugin::Model* model = plugin::modelFromJson(moduleJ);
  21. assert(model);
  22. engine::Module* module = model->createModule();
  23. assert(module);
  24. module->fromJson(moduleJ);
  25. // Create ModuleWidget
  26. ModuleWidget* moduleWidget = module->model->createModuleWidget(module);
  27. assert(moduleWidget);
  28. return moduleWidget;
  29. }
  30. struct ModuleContainer : widget::Widget {
  31. void draw(const DrawArgs& args) override {
  32. // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front of other ModuleWidgets.
  33. for (widget::Widget* child : children) {
  34. ModuleWidget* w = dynamic_cast<ModuleWidget*>(child);
  35. assert(w);
  36. nvgSave(args.vg);
  37. nvgTranslate(args.vg, child->box.pos.x, child->box.pos.y);
  38. w->drawShadow(args);
  39. nvgRestore(args.vg);
  40. }
  41. Widget::draw(args);
  42. }
  43. };
  44. struct CableContainer : widget::TransparentWidget {
  45. void draw(const DrawArgs& args) override {
  46. // Draw cable plugs
  47. for (widget::Widget* w : children) {
  48. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  49. assert(cw);
  50. cw->drawPlugs(args);
  51. }
  52. Widget::draw(args);
  53. }
  54. };
  55. RackWidget::RackWidget() {
  56. railFb = new widget::FramebufferWidget;
  57. railFb->box.size = math::Vec();
  58. railFb->oversample = 1.0;
  59. // Don't redraw when the world offset of the rail FramebufferWidget changes its fractional value.
  60. railFb->dirtyOnSubpixelChange = false;
  61. {
  62. RackRail* rail = new RackRail;
  63. rail->box.size = math::Vec();
  64. railFb->addChild(rail);
  65. }
  66. addChild(railFb);
  67. moduleContainer = new ModuleContainer;
  68. addChild(moduleContainer);
  69. cableContainer = new CableContainer;
  70. addChild(cableContainer);
  71. }
  72. RackWidget::~RackWidget() {
  73. clear();
  74. }
  75. void RackWidget::step() {
  76. Widget::step();
  77. }
  78. void RackWidget::draw(const DrawArgs& args) {
  79. // Resize and reposition the RackRail to align on the grid.
  80. math::Rect railBox;
  81. railBox.pos = args.clipBox.pos.div(BUS_BOARD_GRID_SIZE).floor().mult(BUS_BOARD_GRID_SIZE);
  82. railBox.size = args.clipBox.size.div(BUS_BOARD_GRID_SIZE).ceil().plus(math::Vec(1, 1)).mult(BUS_BOARD_GRID_SIZE);
  83. if (!railFb->box.size.equals(railBox.size)) {
  84. railFb->dirty = true;
  85. }
  86. railFb->box = railBox;
  87. RackRail* rail = railFb->getFirstDescendantOfType<RackRail>();
  88. rail->box.size = railFb->box.size;
  89. Widget::draw(args);
  90. }
  91. void RackWidget::onHover(const HoverEvent& e) {
  92. // Set before calling children's onHover()
  93. mousePos = e.pos;
  94. OpaqueWidget::onHover(e);
  95. }
  96. void RackWidget::onHoverKey(const HoverKeyEvent& e) {
  97. OpaqueWidget::onHoverKey(e);
  98. if (e.isConsumed())
  99. return;
  100. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  101. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  102. pastePresetClipboardAction();
  103. e.consume(this);
  104. }
  105. }
  106. }
  107. void RackWidget::onDragHover(const DragHoverEvent& e) {
  108. // Set before calling children's onDragHover()
  109. mousePos = e.pos;
  110. OpaqueWidget::onDragHover(e);
  111. }
  112. void RackWidget::onButton(const ButtonEvent& e) {
  113. Widget::onButton(e);
  114. e.stopPropagating();
  115. if (e.isConsumed())
  116. return;
  117. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  118. APP->scene->moduleBrowser->show();
  119. e.consume(this);
  120. }
  121. }
  122. void RackWidget::clear() {
  123. // This isn't required because removing all ModuleWidgets should remove all cables, but do it just in case.
  124. clearCables();
  125. // Remove ModuleWidgets
  126. std::list<widget::Widget*> widgets = moduleContainer->children;
  127. for (widget::Widget* w : widgets) {
  128. ModuleWidget* moduleWidget = dynamic_cast<ModuleWidget*>(w);
  129. assert(moduleWidget);
  130. removeModule(moduleWidget);
  131. delete moduleWidget;
  132. }
  133. }
  134. void RackWidget::mergeJson(json_t* rootJ) {
  135. // Get module offset so modules are aligned to (0, 0) when the patch is loaded.
  136. math::Vec moduleOffset = math::Vec(INFINITY, INFINITY);
  137. for (widget::Widget* w : moduleContainer->children) {
  138. moduleOffset = moduleOffset.min(w->box.pos);
  139. }
  140. if (moduleContainer->children.empty()) {
  141. moduleOffset = RACK_OFFSET;
  142. }
  143. // modules
  144. json_t* modulesJ = json_object_get(rootJ, "modules");
  145. if (!modulesJ)
  146. return;
  147. size_t moduleIndex;
  148. json_t* moduleJ;
  149. json_array_foreach(modulesJ, moduleIndex, moduleJ) {
  150. // module
  151. json_t* idJ = json_object_get(moduleJ, "id");
  152. if (!idJ)
  153. continue;
  154. int64_t id = json_integer_value(idJ);
  155. // TODO Legacy v0.6?
  156. ModuleWidget* moduleWidget = getModule(id);
  157. if (!moduleWidget) {
  158. WARN("Cannot find ModuleWidget %" PRId64, id);
  159. continue;
  160. }
  161. // pos
  162. math::Vec pos = moduleWidget->box.pos.minus(moduleOffset);
  163. pos = pos.div(RACK_GRID_SIZE).round();
  164. json_t* posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y);
  165. json_object_set_new(moduleJ, "pos", posJ);
  166. }
  167. // cables
  168. json_t* cablesJ = json_object_get(rootJ, "cables");
  169. if (!cablesJ)
  170. return;
  171. size_t cableIndex;
  172. json_t* cableJ;
  173. json_array_foreach(cablesJ, cableIndex, cableJ) {
  174. // cable
  175. json_t* idJ = json_object_get(cableJ, "id");
  176. if (!idJ)
  177. continue;
  178. int64_t id = json_integer_value(idJ);
  179. CableWidget* cw = getCable(id);
  180. if (!cw) {
  181. WARN("Cannot find CableWidget %" PRId64, id);
  182. continue;
  183. }
  184. json_t* cwJ = cw->toJson();
  185. // Merge cable JSON object
  186. json_object_update(cableJ, cwJ);
  187. json_decref(cwJ);
  188. }
  189. }
  190. void RackWidget::fromJson(json_t* rootJ) {
  191. // version
  192. std::string version;
  193. json_t* versionJ = json_object_get(rootJ, "version");
  194. if (versionJ)
  195. version = json_string_value(versionJ);
  196. bool legacyV05 = false;
  197. if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "dev") {
  198. legacyV05 = true;
  199. }
  200. // modules
  201. json_t* modulesJ = json_object_get(rootJ, "modules");
  202. if (!modulesJ)
  203. return;
  204. size_t moduleIndex;
  205. json_t* moduleJ;
  206. json_array_foreach(modulesJ, moduleIndex, moduleJ) {
  207. // Get module ID
  208. json_t* idJ = json_object_get(moduleJ, "id");
  209. int64_t id;
  210. if (idJ)
  211. id = json_integer_value(idJ);
  212. else
  213. id = moduleIndex;
  214. // Get Module
  215. engine::Module* module = APP->engine->getModule(id);
  216. if (!module) {
  217. WARN("Cannot find Module %" PRId64, id);
  218. continue;
  219. }
  220. // Create ModuleWidget
  221. ModuleWidget* moduleWidget = module->model->createModuleWidget(module);
  222. // pos
  223. json_t* posJ = json_object_get(moduleJ, "pos");
  224. double x = 0.0, y = 0.0;
  225. json_unpack(posJ, "[F, F]", &x, &y);
  226. math::Vec pos = math::Vec(x, y);
  227. if (legacyV05) {
  228. // In <=v0.5, positions were in pixel units
  229. moduleWidget->box.pos = pos;
  230. }
  231. else {
  232. moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE);
  233. }
  234. moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET);
  235. addModule(moduleWidget);
  236. }
  237. // cables
  238. json_t* cablesJ = json_object_get(rootJ, "cables");
  239. // In <=v0.6, cables were called wires
  240. if (!cablesJ)
  241. cablesJ = json_object_get(rootJ, "wires");
  242. if (!cablesJ)
  243. return;
  244. size_t cableIndex;
  245. json_t* cableJ;
  246. json_array_foreach(cablesJ, cableIndex, cableJ) {
  247. // Get cable ID
  248. json_t* idJ = json_object_get(cableJ, "id");
  249. int64_t id;
  250. if (idJ)
  251. id = json_integer_value(idJ);
  252. else
  253. id = cableIndex;
  254. // Get Cable
  255. engine::Cable* cable = APP->engine->getCable(id);
  256. if (!cable) {
  257. WARN("Cannot find Cable %" PRId64, id);
  258. continue;
  259. }
  260. // Create CableWidget
  261. CableWidget* cw = new CableWidget;
  262. cw->setCable(cable);
  263. cw->fromJson(cableJ);
  264. // In <=v1, cable colors were not serialized, so choose one from the available colors.
  265. if (cw->color.a == 0.f) {
  266. cw->setNextCableColor();
  267. }
  268. addCable(cw);
  269. }
  270. }
  271. void RackWidget::pastePresetClipboardAction() {
  272. const char* moduleJson = glfwGetClipboardString(APP->window->win);
  273. if (!moduleJson) {
  274. WARN("Could not get text from clipboard.");
  275. return;
  276. }
  277. json_error_t error;
  278. json_t* moduleJ = json_loads(moduleJson, 0, &error);
  279. if (!moduleJ) {
  280. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  281. return;
  282. }
  283. DEFER({json_decref(moduleJ);});
  284. // Because we are creating a new module, we don't want to use the IDs from the JSON.
  285. json_object_del(moduleJ, "id");
  286. json_object_del(moduleJ, "leftModuleId");
  287. json_object_del(moduleJ, "rightModuleId");
  288. ModuleWidget* mw;
  289. try {
  290. mw = moduleWidgetFromJson(moduleJ);
  291. }
  292. catch (Exception& e) {
  293. WARN("%s", e.what());
  294. return;
  295. }
  296. assert(mw);
  297. assert(mw->module);
  298. APP->engine->addModule(mw->module);
  299. addModuleAtMouse(mw);
  300. // history::ModuleAdd
  301. history::ModuleAdd* h = new history::ModuleAdd;
  302. h->setModule(mw);
  303. APP->history->push(h);
  304. }
  305. static void RackWidget_updateExpanders(RackWidget* that) {
  306. for (widget::Widget* w : that->moduleContainer->children) {
  307. math::Vec pLeft = w->box.pos.div(RACK_GRID_SIZE).round();
  308. math::Vec pRight = w->box.getTopRight().div(RACK_GRID_SIZE).round();
  309. ModuleWidget* mwLeft = NULL;
  310. ModuleWidget* mwRight = NULL;
  311. // Find adjacent modules
  312. for (widget::Widget* w2 : that->moduleContainer->children) {
  313. if (w2 == w)
  314. continue;
  315. math::Vec p2Left = w2->box.pos.div(RACK_GRID_SIZE).round();
  316. math::Vec p2Right = w2->box.getTopRight().div(RACK_GRID_SIZE).round();
  317. // Check if this is a left module
  318. if (p2Right.equals(pLeft)) {
  319. mwLeft = dynamic_cast<ModuleWidget*>(w2);
  320. }
  321. // Check if this is a right module
  322. if (p2Left.equals(pRight)) {
  323. mwRight = dynamic_cast<ModuleWidget*>(w2);
  324. }
  325. }
  326. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  327. mw->module->leftExpander.moduleId = mwLeft ? mwLeft->module->id : -1;
  328. mw->module->rightExpander.moduleId = mwRight ? mwRight->module->id : -1;
  329. }
  330. }
  331. void RackWidget::addModule(ModuleWidget* m) {
  332. // Add module to ModuleContainer
  333. assert(m);
  334. // Module must be 3U high and at least 1HP wide
  335. assert(m->box.size.x >= RACK_GRID_WIDTH);
  336. assert(m->box.size.y == RACK_GRID_HEIGHT);
  337. moduleContainer->addChild(m);
  338. RackWidget_updateExpanders(this);
  339. }
  340. void RackWidget::addModuleAtMouse(ModuleWidget* mw) {
  341. assert(mw);
  342. // Move module nearest to the mouse position
  343. math::Vec pos = mousePos.minus(mw->box.size.div(2));
  344. setModulePosNearest(mw, pos);
  345. addModule(mw);
  346. }
  347. void RackWidget::removeModule(ModuleWidget* m) {
  348. // Unset touchedParamWidget
  349. if (touchedParam) {
  350. ModuleWidget* touchedModule = touchedParam->getAncestorOfType<ModuleWidget>();
  351. if (touchedModule == m)
  352. touchedParam = NULL;
  353. }
  354. // Disconnect cables
  355. m->disconnect();
  356. // Remove module from ModuleContainer
  357. moduleContainer->removeChild(m);
  358. }
  359. bool RackWidget::requestModulePos(ModuleWidget* mw, math::Vec pos) {
  360. // Check intersection with other modules
  361. math::Rect mwBox = math::Rect(pos, mw->box.size);
  362. for (widget::Widget* w2 : moduleContainer->children) {
  363. // Don't intersect with self
  364. if (mw == w2)
  365. continue;
  366. // Don't intersect with invisible modules
  367. if (!w2->visible)
  368. continue;
  369. // Check intersection
  370. if (mwBox.intersects(w2->box))
  371. return false;
  372. }
  373. // Accept requested position
  374. mw->setPosition(mwBox.pos);
  375. RackWidget_updateExpanders(this);
  376. return true;
  377. }
  378. void RackWidget::setModulePosNearest(ModuleWidget* mw, math::Vec pos) {
  379. // Dijkstra's algorithm to generate a sorted list of Vecs closest to `pos`.
  380. // Comparison of distance of Vecs to `pos`
  381. auto cmpNearest = [&](const math::Vec & a, const math::Vec & b) {
  382. return a.minus(pos).square() > b.minus(pos).square();
  383. };
  384. // Comparison of dictionary order of Vecs
  385. auto cmp = [&](const math::Vec & a, const math::Vec & b) {
  386. if (a.x != b.x)
  387. return a.x < b.x;
  388. return a.y < b.y;
  389. };
  390. // Priority queue sorted by distance from `pos`
  391. std::priority_queue<math::Vec, std::vector<math::Vec>, decltype(cmpNearest)> queue(cmpNearest);
  392. // Set of already-tested Vecs
  393. std::set<math::Vec, decltype(cmp)> visited(cmp);
  394. // Seed priority queue with closest Vec
  395. math::Vec closestPos = pos.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE);
  396. queue.push(closestPos);
  397. while (!queue.empty()) {
  398. math::Vec testPos = queue.top();
  399. // Check testPos
  400. if (requestModulePos(mw, testPos))
  401. return;
  402. // Move testPos to visited set
  403. queue.pop();
  404. visited.insert(testPos);
  405. // Add adjacent Vecs
  406. static const std::vector<math::Vec> deltas = {
  407. math::Vec(-1, 0).mult(RACK_GRID_SIZE),
  408. math::Vec(1, 0).mult(RACK_GRID_SIZE),
  409. math::Vec(0, -1).mult(RACK_GRID_SIZE),
  410. math::Vec(0, 1).mult(RACK_GRID_SIZE),
  411. };
  412. for (math::Vec delta : deltas) {
  413. math::Vec newPos = testPos.plus(delta);
  414. if (visited.find(newPos) == visited.end()) {
  415. queue.push(newPos);
  416. }
  417. }
  418. }
  419. // We failed to find a box. This shouldn't happen on an infinite rack.
  420. assert(false);
  421. }
  422. void RackWidget::setModulePosForce(ModuleWidget* mw, math::Vec pos) {
  423. mw->setPosition(pos.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE));
  424. // Comparison of center X coordinates
  425. auto cmp = [&](const widget::Widget * a, const widget::Widget * b) {
  426. return a->box.pos.x + a->box.size.x / 2 < b->box.pos.x + b->box.size.x / 2;
  427. };
  428. // Collect modules to the left and right of `mw`
  429. std::set<widget::Widget*, decltype(cmp)> leftModules(cmp);
  430. std::set<widget::Widget*, decltype(cmp)> rightModules(cmp);
  431. for (widget::Widget* w2 : moduleContainer->children) {
  432. if (w2 == mw)
  433. continue;
  434. // Modules must be on the same row as `mw`
  435. if (w2->box.pos.y != mw->box.pos.y)
  436. continue;
  437. if (cmp(w2, mw))
  438. leftModules.insert(w2);
  439. else
  440. rightModules.insert(w2);
  441. }
  442. // Shove left modules
  443. float xLimit = mw->box.pos.x;
  444. for (auto it = leftModules.rbegin(); it != leftModules.rend(); it++) {
  445. widget::Widget* w = *it;
  446. math::Vec newPos = w->box.pos;
  447. newPos.x = xLimit - w->box.size.x;
  448. newPos.x = std::round(newPos.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  449. if (w->box.pos.x < newPos.x)
  450. break;
  451. w->setPosition(newPos);
  452. xLimit = newPos.x;
  453. }
  454. // Shove right modules
  455. xLimit = mw->box.pos.x + mw->box.size.x;
  456. for (auto it = rightModules.begin(); it != rightModules.end(); it++) {
  457. widget::Widget* w = *it;
  458. math::Vec newPos = w->box.pos;
  459. newPos.x = xLimit;
  460. newPos.x = std::round(newPos.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  461. if (w->box.pos.x > newPos.x)
  462. break;
  463. w->setPosition(newPos);
  464. xLimit = newPos.x + w->box.size.x;
  465. }
  466. RackWidget_updateExpanders(this);
  467. }
  468. ModuleWidget* RackWidget::getModule(int64_t moduleId) {
  469. for (widget::Widget* w : moduleContainer->children) {
  470. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  471. assert(mw);
  472. if (mw->module->id == moduleId)
  473. return mw;
  474. }
  475. return NULL;
  476. }
  477. bool RackWidget::isEmpty() {
  478. return moduleContainer->children.empty();
  479. }
  480. void RackWidget::updateModuleOldPositions() {
  481. // Set all modules' oldPos field from their current position.
  482. for (widget::Widget* w : moduleContainer->children) {
  483. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  484. assert(mw);
  485. mw->oldPos() = mw->box.pos;
  486. }
  487. }
  488. history::ComplexAction* RackWidget::getModuleDragAction() {
  489. history::ComplexAction* h = new history::ComplexAction;
  490. for (widget::Widget* w : moduleContainer->children) {
  491. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  492. assert(mw);
  493. // Create ModuleMove action if the module was moved.
  494. math::Vec oldPos = mw->oldPos();
  495. if (!oldPos.equals(mw->box.pos)) {
  496. history::ModuleMove* mmh = new history::ModuleMove;
  497. mmh->moduleId = mw->module->id;
  498. mmh->oldPos = oldPos;
  499. mmh->newPos = mw->box.pos;
  500. h->push(mmh);
  501. }
  502. }
  503. if (h->isEmpty()) {
  504. delete h;
  505. return NULL;
  506. }
  507. return h;
  508. }
  509. void RackWidget::clearCables() {
  510. incompleteCable = NULL;
  511. cableContainer->clearChildren();
  512. }
  513. void RackWidget::clearCablesAction() {
  514. // Add CableRemove for every cable to a ComplexAction
  515. history::ComplexAction* complexAction = new history::ComplexAction;
  516. complexAction->name = "clear cables";
  517. for (widget::Widget* w : cableContainer->children) {
  518. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  519. assert(cw);
  520. if (!cw->isComplete())
  521. continue;
  522. // history::CableRemove
  523. history::CableRemove* h = new history::CableRemove;
  524. h->setCable(cw);
  525. complexAction->push(h);
  526. }
  527. if (!complexAction->isEmpty())
  528. APP->history->push(complexAction);
  529. else
  530. delete complexAction;
  531. clearCables();
  532. }
  533. void RackWidget::clearCablesOnPort(PortWidget* port) {
  534. for (CableWidget* cw : getCablesOnPort(port)) {
  535. // Check if cable is connected to port
  536. if (cw == incompleteCable) {
  537. incompleteCable = NULL;
  538. cableContainer->removeChild(cw);
  539. }
  540. else {
  541. removeCable(cw);
  542. }
  543. delete cw;
  544. }
  545. }
  546. void RackWidget::addCable(CableWidget* cw) {
  547. assert(cw->isComplete());
  548. cableContainer->addChild(cw);
  549. }
  550. void RackWidget::removeCable(CableWidget* cw) {
  551. assert(cw->isComplete());
  552. cableContainer->removeChild(cw);
  553. }
  554. void RackWidget::setIncompleteCable(CableWidget* cw) {
  555. if (incompleteCable) {
  556. cableContainer->removeChild(incompleteCable);
  557. delete incompleteCable;
  558. incompleteCable = NULL;
  559. }
  560. if (cw) {
  561. cableContainer->addChild(cw);
  562. incompleteCable = cw;
  563. }
  564. }
  565. CableWidget* RackWidget::releaseIncompleteCable() {
  566. if (!incompleteCable)
  567. return NULL;
  568. CableWidget* cw = incompleteCable;
  569. cableContainer->removeChild(incompleteCable);
  570. incompleteCable = NULL;
  571. return cw;
  572. }
  573. CableWidget* RackWidget::getTopCable(PortWidget* port) {
  574. for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) {
  575. CableWidget* cw = dynamic_cast<CableWidget*>(*it);
  576. assert(cw);
  577. if (cw->inputPort == port || cw->outputPort == port)
  578. return cw;
  579. }
  580. return NULL;
  581. }
  582. CableWidget* RackWidget::getCable(int64_t cableId) {
  583. for (widget::Widget* w : cableContainer->children) {
  584. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  585. assert(cw);
  586. if (!cw->cable)
  587. continue;
  588. if (cw->cable->id == cableId)
  589. return cw;
  590. }
  591. return NULL;
  592. }
  593. std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
  594. assert(port);
  595. std::list<CableWidget*> cws;
  596. for (widget::Widget* w : cableContainer->children) {
  597. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  598. assert(cw);
  599. if (cw->inputPort == port || cw->outputPort == port) {
  600. cws.push_back(cw);
  601. }
  602. }
  603. return cws;
  604. }
  605. } // namespace app
  606. } // namespace rack