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.

612 lines
19KB

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