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.

689 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.isEqual(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 event::Hover& e) {
  92. // Set before calling children's onHover()
  93. mousePos = e.pos;
  94. OpaqueWidget::onHover(e);
  95. }
  96. void RackWidget::onHoverKey(const event::HoverKey& 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 event::DragHover& e) {
  108. // Set before calling children's onDragHover()
  109. mousePos = e.pos;
  110. OpaqueWidget::onDragHover(e);
  111. }
  112. void RackWidget::onButton(const event::Button& 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 with ID %ld", 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 with ID %ld", 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. // modules
  192. json_t* modulesJ = json_object_get(rootJ, "modules");
  193. assert(modulesJ);
  194. size_t moduleIndex;
  195. json_t* moduleJ;
  196. json_array_foreach(modulesJ, moduleIndex, moduleJ) {
  197. // module
  198. // Create ModuleWidget and attach it to existing Module from Engine.
  199. json_t* idJ = json_object_get(moduleJ, "id");
  200. if (!idJ)
  201. continue;
  202. int64_t id = json_integer_value(idJ);
  203. engine::Module* module = APP->engine->getModule(id);
  204. if (!module) {
  205. WARN("Cannot find module with ID %ld", id);
  206. continue;
  207. }
  208. ModuleWidget* moduleWidget = module->model->createModuleWidget(module);
  209. // Before 1.0, the module ID was the index in the "modules" array
  210. if (APP->patch->isLegacy(2)) {
  211. module->id = moduleIndex;
  212. }
  213. // pos
  214. json_t* posJ = json_object_get(moduleJ, "pos");
  215. double x, y;
  216. json_unpack(posJ, "[F, F]", &x, &y);
  217. math::Vec pos = math::Vec(x, y);
  218. if (APP->patch->isLegacy(1)) {
  219. // In <=v0.5, positions were in pixel units
  220. moduleWidget->box.pos = pos;
  221. }
  222. else {
  223. moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE);
  224. }
  225. moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET);
  226. addModule(moduleWidget);
  227. }
  228. // cables
  229. json_t* cablesJ = json_object_get(rootJ, "cables");
  230. // In <=v0.6, cables were called wires
  231. if (!cablesJ)
  232. cablesJ = json_object_get(rootJ, "wires");
  233. assert(cablesJ);
  234. size_t cableIndex;
  235. json_t* cableJ;
  236. json_array_foreach(cablesJ, cableIndex, cableJ) {
  237. // cable
  238. // Get Cable from Engine
  239. json_t* idJ = json_object_get(cableJ, "id");
  240. if (!idJ)
  241. continue;
  242. int64_t id = json_integer_value(idJ);
  243. engine::Cable* cable = APP->engine->getCable(id);
  244. if (!cable) {
  245. WARN("Cannot find cable with ID %ld", id);
  246. continue;
  247. }
  248. CableWidget* cw = new CableWidget;
  249. cw->setCable(cable);
  250. cw->fromJson(cableJ);
  251. // In <=v1, cable colors were not serialized, so choose one from the available colors.
  252. if (cw->color.a == 0.f) {
  253. cw->setNextCableColor();
  254. }
  255. addCable(cw);
  256. }
  257. }
  258. void RackWidget::pastePresetClipboardAction() {
  259. const char* moduleJson = glfwGetClipboardString(APP->window->win);
  260. if (!moduleJson) {
  261. WARN("Could not get text from clipboard.");
  262. return;
  263. }
  264. json_error_t error;
  265. json_t* moduleJ = json_loads(moduleJson, 0, &error);
  266. if (!moduleJ) {
  267. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  268. return;
  269. }
  270. DEFER({json_decref(moduleJ);});
  271. // Because we are creating a new module, we don't want to use the IDs from the JSON.
  272. json_object_del(moduleJ, "id");
  273. json_object_del(moduleJ, "leftModuleId");
  274. json_object_del(moduleJ, "rightModuleId");
  275. try {
  276. ModuleWidget* mw = moduleWidgetFromJson(moduleJ);
  277. assert(mw);
  278. assert(mw->module);
  279. APP->engine->addModule(mw->module);
  280. addModuleAtMouse(mw);
  281. // history::ModuleAdd
  282. history::ModuleAdd* h = new history::ModuleAdd;
  283. h->setModule(mw);
  284. APP->history->push(h);
  285. }
  286. catch (Exception& e) {
  287. WARN("%s", e.what());
  288. return;
  289. }
  290. }
  291. static void RackWidget_updateExpanders(RackWidget* that) {
  292. for (widget::Widget* w : that->moduleContainer->children) {
  293. math::Vec pLeft = w->box.pos.div(RACK_GRID_SIZE).round();
  294. math::Vec pRight = w->box.getTopRight().div(RACK_GRID_SIZE).round();
  295. ModuleWidget* mwLeft = NULL;
  296. ModuleWidget* mwRight = NULL;
  297. // Find adjacent modules
  298. for (widget::Widget* w2 : that->moduleContainer->children) {
  299. if (w2 == w)
  300. continue;
  301. math::Vec p2Left = w2->box.pos.div(RACK_GRID_SIZE).round();
  302. math::Vec p2Right = w2->box.getTopRight().div(RACK_GRID_SIZE).round();
  303. // Check if this is a left module
  304. if (p2Right.isEqual(pLeft)) {
  305. mwLeft = dynamic_cast<ModuleWidget*>(w2);
  306. }
  307. // Check if this is a right module
  308. if (p2Left.isEqual(pRight)) {
  309. mwRight = dynamic_cast<ModuleWidget*>(w2);
  310. }
  311. }
  312. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  313. mw->module->leftExpander.moduleId = mwLeft ? mwLeft->module->id : -1;
  314. mw->module->rightExpander.moduleId = mwRight ? mwRight->module->id : -1;
  315. }
  316. }
  317. void RackWidget::addModule(ModuleWidget* m) {
  318. // Add module to ModuleContainer
  319. assert(m);
  320. // Module must be 3U high and at least 1HP wide
  321. assert(m->box.size.x >= RACK_GRID_WIDTH);
  322. assert(m->box.size.y == RACK_GRID_HEIGHT);
  323. moduleContainer->addChild(m);
  324. RackWidget_updateExpanders(this);
  325. }
  326. void RackWidget::addModuleAtMouse(ModuleWidget* mw) {
  327. assert(mw);
  328. // Move module nearest to the mouse position
  329. math::Vec pos = mousePos.minus(mw->box.size.div(2));
  330. setModulePosNearest(mw, pos);
  331. addModule(mw);
  332. }
  333. void RackWidget::removeModule(ModuleWidget* m) {
  334. // Unset touchedParamWidget
  335. if (touchedParam) {
  336. ModuleWidget* touchedModule = touchedParam->getAncestorOfType<ModuleWidget>();
  337. if (touchedModule == m)
  338. touchedParam = NULL;
  339. }
  340. // Disconnect cables
  341. m->disconnect();
  342. // Remove module from ModuleContainer
  343. moduleContainer->removeChild(m);
  344. }
  345. bool RackWidget::requestModulePos(ModuleWidget* mw, math::Vec pos) {
  346. // Check intersection with other modules
  347. math::Rect mwBox = math::Rect(pos, mw->box.size);
  348. for (widget::Widget* w2 : moduleContainer->children) {
  349. // Don't intersect with self
  350. if (mw == w2)
  351. continue;
  352. // Don't intersect with invisible modules
  353. if (!w2->visible)
  354. continue;
  355. // Check intersection
  356. if (mwBox.isIntersecting(w2->box))
  357. return false;
  358. }
  359. // Accept requested position
  360. mw->setPosition(mwBox.pos);
  361. RackWidget_updateExpanders(this);
  362. return true;
  363. }
  364. void RackWidget::setModulePosNearest(ModuleWidget* mw, math::Vec pos) {
  365. // Dijkstra's algorithm to generate a sorted list of Vecs closest to `pos`.
  366. // Comparison of distance of Vecs to `pos`
  367. auto cmpNearest = [&](const math::Vec & a, const math::Vec & b) {
  368. return a.minus(pos).square() > b.minus(pos).square();
  369. };
  370. // Comparison of dictionary order of Vecs
  371. auto cmp = [&](const math::Vec & a, const math::Vec & b) {
  372. if (a.x != b.x)
  373. return a.x < b.x;
  374. return a.y < b.y;
  375. };
  376. // Priority queue sorted by distance from `pos`
  377. std::priority_queue<math::Vec, std::vector<math::Vec>, decltype(cmpNearest)> queue(cmpNearest);
  378. // Set of already-tested Vecs
  379. std::set<math::Vec, decltype(cmp)> visited(cmp);
  380. // Seed priority queue with closest Vec
  381. math::Vec closestPos = pos.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE);
  382. queue.push(closestPos);
  383. while (!queue.empty()) {
  384. math::Vec testPos = queue.top();
  385. // Check testPos
  386. if (requestModulePos(mw, testPos))
  387. return;
  388. // Move testPos to visited set
  389. queue.pop();
  390. visited.insert(testPos);
  391. // Add adjacent Vecs
  392. static const std::vector<math::Vec> deltas = {
  393. math::Vec(-1, 0).mult(RACK_GRID_SIZE),
  394. math::Vec(1, 0).mult(RACK_GRID_SIZE),
  395. math::Vec(0, -1).mult(RACK_GRID_SIZE),
  396. math::Vec(0, 1).mult(RACK_GRID_SIZE),
  397. };
  398. for (math::Vec delta : deltas) {
  399. math::Vec newPos = testPos.plus(delta);
  400. if (visited.find(newPos) == visited.end()) {
  401. queue.push(newPos);
  402. }
  403. }
  404. }
  405. // We failed to find a box. This shouldn't happen on an infinite rack.
  406. assert(false);
  407. }
  408. void RackWidget::setModulePosForce(ModuleWidget* mw, math::Vec pos) {
  409. mw->setPosition(pos.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE));
  410. // Comparison of center X coordinates
  411. auto cmp = [&](const widget::Widget * a, const widget::Widget * b) {
  412. return a->box.pos.x + a->box.size.x / 2 < b->box.pos.x + b->box.size.x / 2;
  413. };
  414. // Collect modules to the left and right of `mw`
  415. std::set<widget::Widget*, decltype(cmp)> leftModules(cmp);
  416. std::set<widget::Widget*, decltype(cmp)> rightModules(cmp);
  417. for (widget::Widget* w2 : moduleContainer->children) {
  418. if (w2 == mw)
  419. continue;
  420. // Modules must be on the same row as `mw`
  421. if (w2->box.pos.y != mw->box.pos.y)
  422. continue;
  423. if (cmp(w2, mw))
  424. leftModules.insert(w2);
  425. else
  426. rightModules.insert(w2);
  427. }
  428. // Shove left modules
  429. float xLimit = mw->box.pos.x;
  430. for (auto it = leftModules.rbegin(); it != leftModules.rend(); it++) {
  431. widget::Widget* w = *it;
  432. math::Vec newPos = w->box.pos;
  433. newPos.x = xLimit - w->box.size.x;
  434. newPos.x = std::round(newPos.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  435. if (w->box.pos.x < newPos.x)
  436. break;
  437. w->setPosition(newPos);
  438. xLimit = newPos.x;
  439. }
  440. // Shove right modules
  441. xLimit = mw->box.pos.x + mw->box.size.x;
  442. for (auto it = rightModules.begin(); it != rightModules.end(); it++) {
  443. widget::Widget* w = *it;
  444. math::Vec newPos = w->box.pos;
  445. newPos.x = xLimit;
  446. newPos.x = std::round(newPos.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  447. if (w->box.pos.x > newPos.x)
  448. break;
  449. w->setPosition(newPos);
  450. xLimit = newPos.x + w->box.size.x;
  451. }
  452. RackWidget_updateExpanders(this);
  453. }
  454. ModuleWidget* RackWidget::getModule(int64_t moduleId) {
  455. for (widget::Widget* w : moduleContainer->children) {
  456. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  457. assert(mw);
  458. if (mw->module->id == moduleId)
  459. return mw;
  460. }
  461. return NULL;
  462. }
  463. bool RackWidget::isEmpty() {
  464. return moduleContainer->children.empty();
  465. }
  466. void RackWidget::updateModuleOldPositions() {
  467. // Set all modules' oldPos field from their current position.
  468. for (widget::Widget* w : moduleContainer->children) {
  469. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  470. assert(mw);
  471. mw->oldPos() = mw->box.pos;
  472. }
  473. }
  474. history::ComplexAction* RackWidget::getModuleDragAction() {
  475. history::ComplexAction* h = new history::ComplexAction;
  476. for (widget::Widget* w : moduleContainer->children) {
  477. ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w);
  478. assert(mw);
  479. // Create ModuleMove action if the module was moved.
  480. math::Vec oldPos = mw->oldPos();
  481. if (!oldPos.isEqual(mw->box.pos)) {
  482. history::ModuleMove* mmh = new history::ModuleMove;
  483. mmh->moduleId = mw->module->id;
  484. mmh->oldPos = oldPos;
  485. mmh->newPos = mw->box.pos;
  486. h->push(mmh);
  487. }
  488. }
  489. if (h->isEmpty()) {
  490. delete h;
  491. return NULL;
  492. }
  493. return h;
  494. }
  495. void RackWidget::clearCables() {
  496. incompleteCable = NULL;
  497. cableContainer->clearChildren();
  498. }
  499. void RackWidget::clearCablesAction() {
  500. // Add CableRemove for every cable to a ComplexAction
  501. history::ComplexAction* complexAction = new history::ComplexAction;
  502. complexAction->name = "clear cables";
  503. for (widget::Widget* w : cableContainer->children) {
  504. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  505. assert(cw);
  506. if (!cw->isComplete())
  507. continue;
  508. // history::CableRemove
  509. history::CableRemove* h = new history::CableRemove;
  510. h->setCable(cw);
  511. complexAction->push(h);
  512. }
  513. if (!complexAction->isEmpty())
  514. APP->history->push(complexAction);
  515. else
  516. delete complexAction;
  517. clearCables();
  518. }
  519. void RackWidget::clearCablesOnPort(PortWidget* port) {
  520. for (CableWidget* cw : getCablesOnPort(port)) {
  521. // Check if cable is connected to port
  522. if (cw == incompleteCable) {
  523. incompleteCable = NULL;
  524. cableContainer->removeChild(cw);
  525. }
  526. else {
  527. removeCable(cw);
  528. }
  529. delete cw;
  530. }
  531. }
  532. void RackWidget::addCable(CableWidget* cw) {
  533. assert(cw->isComplete());
  534. cableContainer->addChild(cw);
  535. }
  536. void RackWidget::removeCable(CableWidget* cw) {
  537. assert(cw->isComplete());
  538. cableContainer->removeChild(cw);
  539. }
  540. void RackWidget::setIncompleteCable(CableWidget* cw) {
  541. if (incompleteCable) {
  542. cableContainer->removeChild(incompleteCable);
  543. delete incompleteCable;
  544. incompleteCable = NULL;
  545. }
  546. if (cw) {
  547. cableContainer->addChild(cw);
  548. incompleteCable = cw;
  549. }
  550. }
  551. CableWidget* RackWidget::releaseIncompleteCable() {
  552. if (!incompleteCable)
  553. return NULL;
  554. CableWidget* cw = incompleteCable;
  555. cableContainer->removeChild(incompleteCable);
  556. incompleteCable = NULL;
  557. return cw;
  558. }
  559. CableWidget* RackWidget::getTopCable(PortWidget* port) {
  560. for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) {
  561. CableWidget* cw = dynamic_cast<CableWidget*>(*it);
  562. assert(cw);
  563. if (cw->inputPort == port || cw->outputPort == port)
  564. return cw;
  565. }
  566. return NULL;
  567. }
  568. CableWidget* RackWidget::getCable(int64_t cableId) {
  569. for (widget::Widget* w : cableContainer->children) {
  570. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  571. assert(cw);
  572. if (!cw->cable)
  573. continue;
  574. if (cw->cable->id == cableId)
  575. return cw;
  576. }
  577. return NULL;
  578. }
  579. std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
  580. assert(port);
  581. std::list<CableWidget*> cws;
  582. for (widget::Widget* w : cableContainer->children) {
  583. CableWidget* cw = dynamic_cast<CableWidget*>(w);
  584. assert(cw);
  585. if (cw->inputPort == port || cw->outputPort == port) {
  586. cws.push_back(cw);
  587. }
  588. }
  589. return cws;
  590. }
  591. } // namespace app
  592. } // namespace rack