The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

680 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. static const Identifier tableColumnProperty { "_tableColumnId" };
  21. static const Identifier tableAccessiblePlaceholderProperty { "_accessiblePlaceholder" };
  22. class TableListBox::RowComp final : public TooltipClient,
  23. public ComponentWithListRowMouseBehaviours<RowComp>
  24. {
  25. public:
  26. explicit RowComp (TableListBox& tlb)
  27. : owner (tlb)
  28. {
  29. setFocusContainerType (FocusContainerType::focusContainer);
  30. }
  31. void paint (Graphics& g) override
  32. {
  33. if (auto* tableModel = owner.getTableListBoxModel())
  34. {
  35. tableModel->paintRowBackground (g, getRow(), getWidth(), getHeight(), isSelected());
  36. auto& headerComp = owner.getHeader();
  37. const auto numColumns = jmin ((int) columnComponents.size(), headerComp.getNumColumns (true));
  38. const auto clipBounds = g.getClipBounds();
  39. for (int i = 0; i < numColumns; ++i)
  40. {
  41. if (columnComponents[(size_t) i]->getProperties().contains (tableAccessiblePlaceholderProperty))
  42. {
  43. auto columnRect = headerComp.getColumnPosition (i).withHeight (getHeight());
  44. if (columnRect.getX() >= clipBounds.getRight())
  45. break;
  46. if (columnRect.getRight() > clipBounds.getX())
  47. {
  48. Graphics::ScopedSaveState ss (g);
  49. if (g.reduceClipRegion (columnRect))
  50. {
  51. g.setOrigin (columnRect.getX(), 0);
  52. tableModel->paintCell (g, getRow(), headerComp.getColumnIdOfIndex (i, true),
  53. columnRect.getWidth(), columnRect.getHeight(), isSelected());
  54. }
  55. }
  56. }
  57. }
  58. }
  59. }
  60. void update (int newRow, bool isNowSelected)
  61. {
  62. jassert (newRow >= 0);
  63. updateRowAndSelection (newRow, isNowSelected);
  64. auto* tableModel = owner.getTableListBoxModel();
  65. if (tableModel != nullptr && getRow() < owner.getNumRows())
  66. {
  67. const ComponentDeleter deleter { columnForComponent };
  68. const auto numColumns = owner.getHeader().getNumColumns (true);
  69. while (numColumns < (int) columnComponents.size())
  70. columnComponents.pop_back();
  71. while ((int) columnComponents.size() < numColumns)
  72. columnComponents.emplace_back (nullptr, deleter);
  73. for (int i = 0; i < numColumns; ++i)
  74. {
  75. auto columnId = owner.getHeader().getColumnIdOfIndex (i, true);
  76. auto originalComp = std::move (columnComponents[(size_t) i]);
  77. auto oldCustomComp = originalComp != nullptr && ! originalComp->getProperties().contains (tableAccessiblePlaceholderProperty)
  78. ? std::move (originalComp)
  79. : std::unique_ptr<Component, ComponentDeleter> { nullptr, deleter };
  80. auto compToRefresh = oldCustomComp != nullptr && columnId == static_cast<int> (oldCustomComp->getProperties()[tableColumnProperty])
  81. ? std::move (oldCustomComp)
  82. : std::unique_ptr<Component, ComponentDeleter> { nullptr, deleter };
  83. columnForComponent.erase (compToRefresh.get());
  84. std::unique_ptr<Component, ComponentDeleter> newCustomComp { tableModel->refreshComponentForCell (getRow(),
  85. columnId,
  86. isSelected(),
  87. compToRefresh.release()),
  88. deleter };
  89. auto columnComp = [&]
  90. {
  91. // We got a result from refreshComponentForCell, so use that
  92. if (newCustomComp != nullptr)
  93. return std::move (newCustomComp);
  94. // There was already a placeholder component for this column
  95. if (originalComp != nullptr)
  96. return std::move (originalComp);
  97. // Create a new placeholder component to use
  98. std::unique_ptr<Component, ComponentDeleter> comp { new Component, deleter };
  99. comp->setInterceptsMouseClicks (false, false);
  100. comp->getProperties().set (tableAccessiblePlaceholderProperty, true);
  101. return comp;
  102. }();
  103. columnForComponent.emplace (columnComp.get(), i);
  104. // In order for navigation to work correctly on macOS, the number of child
  105. // accessibility elements on each row must match the number of header accessibility
  106. // elements.
  107. columnComp->setFocusContainerType (FocusContainerType::focusContainer);
  108. columnComp->getProperties().set (tableColumnProperty, columnId);
  109. addAndMakeVisible (*columnComp);
  110. columnComponents[(size_t) i] = std::move (columnComp);
  111. resizeCustomComp (i);
  112. }
  113. }
  114. else
  115. {
  116. columnComponents.clear();
  117. }
  118. }
  119. void resized() override
  120. {
  121. for (auto i = (int) columnComponents.size(); --i >= 0;)
  122. resizeCustomComp (i);
  123. }
  124. void resizeCustomComp (int index)
  125. {
  126. if (auto& c = columnComponents[(size_t) index])
  127. {
  128. c->setBounds (owner.getHeader()
  129. .getColumnPosition (index)
  130. .withY (0)
  131. .withHeight (getHeight()));
  132. }
  133. }
  134. void performSelection (const MouseEvent& e, bool isMouseUp)
  135. {
  136. owner.selectRowsBasedOnModifierKeys (getRow(), e.mods, isMouseUp);
  137. auto columnId = owner.getHeader().getColumnIdAtX (e.x);
  138. if (columnId != 0)
  139. if (auto* m = owner.getTableListBoxModel())
  140. m->cellClicked (getRow(), columnId, e);
  141. }
  142. void mouseDoubleClick (const MouseEvent& e) override
  143. {
  144. if (! isEnabled())
  145. return;
  146. const auto columnId = owner.getHeader().getColumnIdAtX (e.x);
  147. if (columnId != 0)
  148. if (auto* m = owner.getTableListBoxModel())
  149. m->cellDoubleClicked (getRow(), columnId, e);
  150. }
  151. String getTooltip() override
  152. {
  153. auto columnId = owner.getHeader().getColumnIdAtX (getMouseXYRelative().getX());
  154. if (columnId != 0)
  155. if (auto* m = owner.getTableListBoxModel())
  156. return m->getCellTooltip (getRow(), columnId);
  157. return {};
  158. }
  159. Component* findChildComponentForColumn (int columnId) const
  160. {
  161. const auto index = (size_t) owner.getHeader().getIndexOfColumnId (columnId, true);
  162. if (isPositiveAndBelow (index, columnComponents.size()))
  163. return columnComponents[index].get();
  164. return nullptr;
  165. }
  166. int getColumnNumberOfComponent (const Component* comp) const
  167. {
  168. const auto iter = columnForComponent.find (comp);
  169. return iter != columnForComponent.cend() ? iter->second : -1;
  170. }
  171. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  172. {
  173. return std::make_unique<RowAccessibilityHandler> (*this);
  174. }
  175. TableListBox& getOwner() const { return owner; }
  176. private:
  177. //==============================================================================
  178. class RowAccessibilityHandler final : public AccessibilityHandler
  179. {
  180. public:
  181. RowAccessibilityHandler (RowComp& rowComp)
  182. : AccessibilityHandler (rowComp,
  183. AccessibilityRole::row,
  184. getListRowAccessibilityActions (rowComp),
  185. { std::make_unique<RowComponentCellInterface> (*this) }),
  186. rowComponent (rowComp)
  187. {
  188. }
  189. String getTitle() const override
  190. {
  191. if (auto* m = rowComponent.owner.ListBox::model)
  192. return m->getNameForRow (rowComponent.getRow());
  193. return {};
  194. }
  195. String getHelp() const override { return rowComponent.getTooltip(); }
  196. AccessibleState getCurrentState() const override
  197. {
  198. if (auto* m = rowComponent.owner.getTableListBoxModel())
  199. if (rowComponent.getRow() >= m->getNumRows())
  200. return AccessibleState().withIgnored();
  201. auto state = AccessibilityHandler::getCurrentState();
  202. if (rowComponent.owner.multipleSelection)
  203. state = state.withMultiSelectable();
  204. else
  205. state = state.withSelectable();
  206. if (rowComponent.isSelected())
  207. return state.withSelected();
  208. return state;
  209. }
  210. private:
  211. class RowComponentCellInterface final : public AccessibilityCellInterface
  212. {
  213. public:
  214. RowComponentCellInterface (RowAccessibilityHandler& handler)
  215. : owner (handler)
  216. {
  217. }
  218. int getDisclosureLevel() const override { return 0; }
  219. const AccessibilityHandler* getTableHandler() const override { return owner.rowComponent.owner.getAccessibilityHandler(); }
  220. private:
  221. RowAccessibilityHandler& owner;
  222. };
  223. private:
  224. RowComp& rowComponent;
  225. };
  226. //==============================================================================
  227. class ComponentDeleter
  228. {
  229. public:
  230. explicit ComponentDeleter (std::map<const Component*, int>& locations)
  231. : columnForComponent (&locations) {}
  232. void operator() (Component* comp) const
  233. {
  234. columnForComponent->erase (comp);
  235. if (comp != nullptr)
  236. delete comp;
  237. }
  238. private:
  239. std::map<const Component*, int>* columnForComponent;
  240. };
  241. TableListBox& owner;
  242. std::map<const Component*, int> columnForComponent;
  243. std::vector<std::unique_ptr<Component, ComponentDeleter>> columnComponents;
  244. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComp)
  245. };
  246. //==============================================================================
  247. class TableListBox::Header final : public TableHeaderComponent
  248. {
  249. public:
  250. Header (TableListBox& tlb) : owner (tlb) {}
  251. void addMenuItems (PopupMenu& menu, int columnIdClicked)
  252. {
  253. if (owner.isAutoSizeMenuOptionShown())
  254. {
  255. menu.addItem (autoSizeColumnId, TRANS ("Auto-size this column"), columnIdClicked != 0);
  256. menu.addItem (autoSizeAllId, TRANS ("Auto-size all columns"), owner.getHeader().getNumColumns (true) > 0);
  257. menu.addSeparator();
  258. }
  259. TableHeaderComponent::addMenuItems (menu, columnIdClicked);
  260. }
  261. void reactToMenuItem (int menuReturnId, int columnIdClicked)
  262. {
  263. switch (menuReturnId)
  264. {
  265. case autoSizeColumnId: owner.autoSizeColumn (columnIdClicked); break;
  266. case autoSizeAllId: owner.autoSizeAllColumns(); break;
  267. default: TableHeaderComponent::reactToMenuItem (menuReturnId, columnIdClicked); break;
  268. }
  269. }
  270. private:
  271. TableListBox& owner;
  272. enum { autoSizeColumnId = 0xf836743, autoSizeAllId = 0xf836744 };
  273. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Header)
  274. };
  275. //==============================================================================
  276. TableListBox::TableListBox (const String& name, TableListBoxModel* const m)
  277. : ListBox (name, nullptr), model (m)
  278. {
  279. ListBox::assignModelPtr (this);
  280. setHeader (std::make_unique<Header> (*this));
  281. }
  282. TableListBox::~TableListBox()
  283. {
  284. }
  285. void TableListBox::setModel (TableListBoxModel* newModel)
  286. {
  287. if (model != newModel)
  288. {
  289. model = newModel;
  290. updateContent();
  291. }
  292. }
  293. void TableListBox::setHeader (std::unique_ptr<TableHeaderComponent> newHeader)
  294. {
  295. if (newHeader == nullptr)
  296. {
  297. jassertfalse; // you need to supply a real header for a table!
  298. return;
  299. }
  300. Rectangle<int> newBounds (100, 28);
  301. if (header != nullptr)
  302. newBounds = header->getBounds();
  303. header = newHeader.get();
  304. header->setBounds (newBounds);
  305. setHeaderComponent (std::move (newHeader));
  306. header->addListener (this);
  307. }
  308. int TableListBox::getHeaderHeight() const noexcept
  309. {
  310. return header->getHeight();
  311. }
  312. void TableListBox::setHeaderHeight (int newHeight)
  313. {
  314. header->setSize (header->getWidth(), newHeight);
  315. resized();
  316. }
  317. void TableListBox::autoSizeColumn (int columnId)
  318. {
  319. auto width = model != nullptr ? model->getColumnAutoSizeWidth (columnId) : 0;
  320. if (width > 0)
  321. header->setColumnWidth (columnId, width);
  322. }
  323. void TableListBox::autoSizeAllColumns()
  324. {
  325. for (int i = 0; i < header->getNumColumns (true); ++i)
  326. autoSizeColumn (header->getColumnIdOfIndex (i, true));
  327. }
  328. void TableListBox::setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept
  329. {
  330. autoSizeOptionsShown = shouldBeShown;
  331. }
  332. Rectangle<int> TableListBox::getCellPosition (int columnId, int rowNumber, bool relativeToComponentTopLeft) const
  333. {
  334. auto headerCell = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
  335. if (relativeToComponentTopLeft)
  336. headerCell.translate (header->getX(), 0);
  337. return getRowPosition (rowNumber, relativeToComponentTopLeft)
  338. .withX (headerCell.getX())
  339. .withWidth (headerCell.getWidth());
  340. }
  341. Component* TableListBox::getCellComponent (int columnId, int rowNumber) const
  342. {
  343. if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (rowNumber)))
  344. return rowComp->findChildComponentForColumn (columnId);
  345. return nullptr;
  346. }
  347. void TableListBox::scrollToEnsureColumnIsOnscreen (int columnId)
  348. {
  349. auto& scrollbar = getHorizontalScrollBar();
  350. auto pos = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
  351. auto x = scrollbar.getCurrentRangeStart();
  352. auto w = scrollbar.getCurrentRangeSize();
  353. if (pos.getX() < x)
  354. x = pos.getX();
  355. else if (pos.getRight() > x + w)
  356. x += jmax (0.0, pos.getRight() - (x + w));
  357. scrollbar.setCurrentRangeStart (x);
  358. }
  359. int TableListBox::getNumRows()
  360. {
  361. return model != nullptr ? model->getNumRows() : 0;
  362. }
  363. void TableListBox::paintListBoxItem (int, Graphics&, int, int, bool)
  364. {
  365. }
  366. Component* TableListBox::refreshComponentForRow (int rowNumber, bool rowSelected, Component* existingComponentToUpdate)
  367. {
  368. if (existingComponentToUpdate == nullptr)
  369. existingComponentToUpdate = new RowComp (*this);
  370. static_cast<RowComp*> (existingComponentToUpdate)->update (rowNumber, rowSelected);
  371. return existingComponentToUpdate;
  372. }
  373. void TableListBox::selectedRowsChanged (int row)
  374. {
  375. if (model != nullptr)
  376. model->selectedRowsChanged (row);
  377. }
  378. void TableListBox::deleteKeyPressed (int row)
  379. {
  380. if (model != nullptr)
  381. model->deleteKeyPressed (row);
  382. }
  383. void TableListBox::returnKeyPressed (int row)
  384. {
  385. if (model != nullptr)
  386. model->returnKeyPressed (row);
  387. }
  388. void TableListBox::backgroundClicked (const MouseEvent& e)
  389. {
  390. if (model != nullptr)
  391. model->backgroundClicked (e);
  392. }
  393. void TableListBox::listWasScrolled()
  394. {
  395. if (model != nullptr)
  396. model->listWasScrolled();
  397. }
  398. void TableListBox::tableColumnsChanged (TableHeaderComponent*)
  399. {
  400. setMinimumContentWidth (header->getTotalWidth());
  401. repaint();
  402. updateColumnComponents();
  403. }
  404. void TableListBox::tableColumnsResized (TableHeaderComponent*)
  405. {
  406. setMinimumContentWidth (header->getTotalWidth());
  407. repaint();
  408. updateColumnComponents();
  409. }
  410. void TableListBox::tableSortOrderChanged (TableHeaderComponent*)
  411. {
  412. if (model != nullptr)
  413. model->sortOrderChanged (header->getSortColumnId(),
  414. header->isSortedForwards());
  415. }
  416. void TableListBox::tableColumnDraggingChanged (TableHeaderComponent*, int columnIdNowBeingDragged_)
  417. {
  418. columnIdNowBeingDragged = columnIdNowBeingDragged_;
  419. repaint();
  420. }
  421. void TableListBox::resized()
  422. {
  423. ListBox::resized();
  424. header->resizeAllColumnsToFit (getVisibleContentWidth());
  425. setMinimumContentWidth (header->getTotalWidth());
  426. }
  427. void TableListBox::updateColumnComponents() const
  428. {
  429. auto firstRow = getRowContainingPosition (0, 0);
  430. for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;)
  431. if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (i)))
  432. rowComp->resized();
  433. }
  434. template <typename FindIndex>
  435. Optional<AccessibilityTableInterface::Span> findRecursively (const AccessibilityHandler& handler,
  436. Component* outermost,
  437. FindIndex&& findIndexOfComponent)
  438. {
  439. for (auto* comp = &handler.getComponent(); comp != outermost; comp = comp->getParentComponent())
  440. {
  441. const auto result = findIndexOfComponent (comp);
  442. if (result != -1)
  443. return AccessibilityTableInterface::Span { result, 1 };
  444. }
  445. return nullopt;
  446. }
  447. std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
  448. {
  449. class TableInterface final : public AccessibilityTableInterface
  450. {
  451. public:
  452. explicit TableInterface (TableListBox& tableListBoxToWrap)
  453. : tableListBox (tableListBoxToWrap)
  454. {
  455. }
  456. int getNumRows() const override
  457. {
  458. if (auto* tableModel = tableListBox.getTableListBoxModel())
  459. return tableModel->getNumRows();
  460. return 0;
  461. }
  462. int getNumColumns() const override
  463. {
  464. return tableListBox.getHeader().getNumColumns (true);
  465. }
  466. const AccessibilityHandler* getRowHandler (int row) const override
  467. {
  468. if (isPositiveAndBelow (row, getNumRows()))
  469. if (auto* rowComp = tableListBox.getComponentForRowNumber (row))
  470. return rowComp->getAccessibilityHandler();
  471. return nullptr;
  472. }
  473. const AccessibilityHandler* getCellHandler (int row, int column) const override
  474. {
  475. if (isPositiveAndBelow (row, getNumRows()) && isPositiveAndBelow (column, getNumColumns()))
  476. if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, true), row))
  477. return cellComponent->getAccessibilityHandler();
  478. return nullptr;
  479. }
  480. const AccessibilityHandler* getHeaderHandler() const override
  481. {
  482. if (tableListBox.hasAccessibleHeaderComponent())
  483. return tableListBox.headerComponent->getAccessibilityHandler();
  484. return nullptr;
  485. }
  486. Optional<Span> getRowSpan (const AccessibilityHandler& handler) const override
  487. {
  488. if (tableListBox.isParentOf (&handler.getComponent()))
  489. return findRecursively (handler, &tableListBox, [&] (auto* c) { return tableListBox.getRowNumberOfComponent (c); });
  490. return nullopt;
  491. }
  492. Optional<Span> getColumnSpan (const AccessibilityHandler& handler) const override
  493. {
  494. if (const auto rowSpan = getRowSpan (handler))
  495. if (auto* rowComponent = dynamic_cast<RowComp*> (tableListBox.getComponentForRowNumber (rowSpan->begin)))
  496. return findRecursively (handler, &tableListBox, [&] (auto* c) { return rowComponent->getColumnNumberOfComponent (c); });
  497. return nullopt;
  498. }
  499. void showCell (const AccessibilityHandler& handler) const override
  500. {
  501. const auto row = getRowSpan (handler);
  502. const auto col = getColumnSpan (handler);
  503. if (row.hasValue() && col.hasValue())
  504. {
  505. tableListBox.scrollToEnsureRowIsOnscreen (row->begin);
  506. tableListBox.scrollToEnsureColumnIsOnscreen (col->begin);
  507. }
  508. }
  509. private:
  510. TableListBox& tableListBox;
  511. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
  512. };
  513. return std::make_unique<AccessibilityHandler> (*this,
  514. AccessibilityRole::table,
  515. AccessibilityActions{},
  516. AccessibilityHandler::Interfaces { std::make_unique<TableInterface> (*this) });
  517. }
  518. //==============================================================================
  519. void TableListBoxModel::cellClicked (int, int, const MouseEvent&) {}
  520. void TableListBoxModel::cellDoubleClicked (int, int, const MouseEvent&) {}
  521. void TableListBoxModel::backgroundClicked (const MouseEvent&) {}
  522. void TableListBoxModel::sortOrderChanged (int, bool) {}
  523. int TableListBoxModel::getColumnAutoSizeWidth (int) { return 0; }
  524. void TableListBoxModel::selectedRowsChanged (int) {}
  525. void TableListBoxModel::deleteKeyPressed (int) {}
  526. void TableListBoxModel::returnKeyPressed (int) {}
  527. void TableListBoxModel::listWasScrolled() {}
  528. String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return {}; }
  529. var TableListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return {}; }
  530. Component* TableListBoxModel::refreshComponentForCell (int, int, bool, [[maybe_unused]] Component* existingComponentToUpdate)
  531. {
  532. jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
  533. return nullptr;
  534. }
  535. } // namespace juce