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.

485 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - 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 this technical preview, this file is not subject to commercial licensing.
  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 : owner (tlb) {}
  20. void paint (Graphics& g) override
  21. {
  22. if (auto* tableModel = owner.getModel())
  23. {
  24. tableModel->paintRowBackground (g, row, getWidth(), getHeight(), isSelected);
  25. auto& headerComp = owner.getHeader();
  26. auto numColumns = headerComp.getNumColumns (true);
  27. auto clipBounds = g.getClipBounds();
  28. for (int i = 0; i < numColumns; ++i)
  29. {
  30. if (columnComponents[i] == nullptr)
  31. {
  32. auto columnRect = headerComp.getColumnPosition(i).withHeight (getHeight());
  33. if (columnRect.getX() >= clipBounds.getRight())
  34. break;
  35. if (columnRect.getRight() > clipBounds.getX())
  36. {
  37. Graphics::ScopedSaveState ss (g);
  38. if (g.reduceClipRegion (columnRect))
  39. {
  40. g.setOrigin (columnRect.getX(), 0);
  41. tableModel->paintCell (g, row, headerComp.getColumnIdOfIndex (i, true),
  42. columnRect.getWidth(), columnRect.getHeight(), isSelected);
  43. }
  44. }
  45. }
  46. }
  47. }
  48. }
  49. void update (int newRow, bool isNowSelected)
  50. {
  51. jassert (newRow >= 0);
  52. if (newRow != row || isNowSelected != isSelected)
  53. {
  54. row = newRow;
  55. isSelected = isNowSelected;
  56. repaint();
  57. }
  58. auto* tableModel = owner.getModel();
  59. if (tableModel != nullptr && row < owner.getNumRows())
  60. {
  61. const Identifier columnProperty ("_tableColumnId");
  62. auto numColumns = owner.getHeader().getNumColumns (true);
  63. for (int i = 0; i < numColumns; ++i)
  64. {
  65. auto columnId = owner.getHeader().getColumnIdOfIndex (i, true);
  66. auto* comp = columnComponents[i];
  67. if (comp != nullptr && columnId != static_cast<int> (comp->getProperties() [columnProperty]))
  68. {
  69. columnComponents.set (i, nullptr);
  70. comp = nullptr;
  71. }
  72. comp = tableModel->refreshComponentForCell (row, columnId, isSelected, comp);
  73. columnComponents.set (i, comp, false);
  74. if (comp != nullptr)
  75. {
  76. comp->getProperties().set (columnProperty, columnId);
  77. addAndMakeVisible (comp);
  78. resizeCustomComp (i);
  79. }
  80. }
  81. columnComponents.removeRange (numColumns, columnComponents.size());
  82. }
  83. else
  84. {
  85. columnComponents.clear();
  86. }
  87. }
  88. void resized() override
  89. {
  90. for (int i = columnComponents.size(); --i >= 0;)
  91. resizeCustomComp (i);
  92. }
  93. void resizeCustomComp (int index)
  94. {
  95. if (auto* c = columnComponents.getUnchecked (index))
  96. c->setBounds (owner.getHeader().getColumnPosition (index)
  97. .withY (0).withHeight (getHeight()));
  98. }
  99. void mouseDown (const MouseEvent& e) override
  100. {
  101. isDragging = false;
  102. selectRowOnMouseUp = false;
  103. if (isEnabled())
  104. {
  105. if (! isSelected)
  106. {
  107. owner.selectRowsBasedOnModifierKeys (row, e.mods, false);
  108. auto columnId = owner.getHeader().getColumnIdAtX (e.x);
  109. if (columnId != 0)
  110. if (auto* m = owner.getModel())
  111. m->cellClicked (row, columnId, e);
  112. }
  113. else
  114. {
  115. selectRowOnMouseUp = true;
  116. }
  117. }
  118. }
  119. void mouseDrag (const MouseEvent& e) override
  120. {
  121. if (isEnabled()
  122. && owner.getModel() != nullptr
  123. && e.mouseWasDraggedSinceMouseDown()
  124. && ! isDragging)
  125. {
  126. SparseSet<int> rowsToDrag;
  127. if (owner.selectOnMouseDown || owner.isRowSelected (row))
  128. rowsToDrag = owner.getSelectedRows();
  129. else
  130. rowsToDrag.addRange (Range<int>::withStartAndLength (row, 1));
  131. if (rowsToDrag.size() > 0)
  132. {
  133. auto dragDescription = owner.getModel()->getDragSourceDescription (rowsToDrag);
  134. if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
  135. {
  136. isDragging = true;
  137. owner.startDragAndDrop (e, rowsToDrag, dragDescription, true);
  138. }
  139. }
  140. }
  141. }
  142. void mouseUp (const MouseEvent& e) override
  143. {
  144. if (selectRowOnMouseUp && e.mouseWasClicked() && isEnabled())
  145. {
  146. owner.selectRowsBasedOnModifierKeys (row, e.mods, true);
  147. auto columnId = owner.getHeader().getColumnIdAtX (e.x);
  148. if (columnId != 0)
  149. if (TableListBoxModel* m = owner.getModel())
  150. m->cellClicked (row, columnId, e);
  151. }
  152. }
  153. void mouseDoubleClick (const MouseEvent& e) override
  154. {
  155. auto columnId = owner.getHeader().getColumnIdAtX (e.x);
  156. if (columnId != 0)
  157. if (auto* m = owner.getModel())
  158. m->cellDoubleClicked (row, columnId, e);
  159. }
  160. String getTooltip() override
  161. {
  162. auto columnId = owner.getHeader().getColumnIdAtX (getMouseXYRelative().getX());
  163. if (columnId != 0)
  164. if (auto* m = owner.getModel())
  165. return m->getCellTooltip (row, columnId);
  166. return {};
  167. }
  168. Component* findChildComponentForColumn (int columnId) const
  169. {
  170. return columnComponents [owner.getHeader().getIndexOfColumnId (columnId, true)];
  171. }
  172. private:
  173. TableListBox& owner;
  174. OwnedArray<Component> columnComponents;
  175. int row = -1;
  176. bool isSelected = false, isDragging = false, selectRowOnMouseUp = false;
  177. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComp)
  178. };
  179. //==============================================================================
  180. class TableListBox::Header : public TableHeaderComponent
  181. {
  182. public:
  183. Header (TableListBox& tlb) : owner (tlb) {}
  184. void addMenuItems (PopupMenu& menu, int columnIdClicked)
  185. {
  186. if (owner.isAutoSizeMenuOptionShown())
  187. {
  188. menu.addItem (autoSizeColumnId, TRANS("Auto-size this column"), columnIdClicked != 0);
  189. menu.addItem (autoSizeAllId, TRANS("Auto-size all columns"), owner.getHeader().getNumColumns (true) > 0);
  190. menu.addSeparator();
  191. }
  192. TableHeaderComponent::addMenuItems (menu, columnIdClicked);
  193. }
  194. void reactToMenuItem (int menuReturnId, int columnIdClicked)
  195. {
  196. switch (menuReturnId)
  197. {
  198. case autoSizeColumnId: owner.autoSizeColumn (columnIdClicked); break;
  199. case autoSizeAllId: owner.autoSizeAllColumns(); break;
  200. default: TableHeaderComponent::reactToMenuItem (menuReturnId, columnIdClicked); break;
  201. }
  202. }
  203. private:
  204. TableListBox& owner;
  205. enum { autoSizeColumnId = 0xf836743, autoSizeAllId = 0xf836744 };
  206. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Header)
  207. };
  208. //==============================================================================
  209. TableListBox::TableListBox (const String& name, TableListBoxModel* const m)
  210. : ListBox (name, nullptr), model (m)
  211. {
  212. ListBox::model = this;
  213. setHeader (std::make_unique<Header> (*this));
  214. }
  215. TableListBox::~TableListBox()
  216. {
  217. }
  218. void TableListBox::setModel (TableListBoxModel* newModel)
  219. {
  220. if (model != newModel)
  221. {
  222. model = newModel;
  223. updateContent();
  224. }
  225. }
  226. void TableListBox::setHeader (std::unique_ptr<TableHeaderComponent> newHeader)
  227. {
  228. if (newHeader == nullptr)
  229. {
  230. jassertfalse; // you need to supply a real header for a table!
  231. return;
  232. }
  233. Rectangle<int> newBounds (100, 28);
  234. if (header != nullptr)
  235. newBounds = header->getBounds();
  236. header = newHeader.get();
  237. header->setBounds (newBounds);
  238. setHeaderComponent (std::move (newHeader));
  239. header->addListener (this);
  240. }
  241. int TableListBox::getHeaderHeight() const noexcept
  242. {
  243. return header->getHeight();
  244. }
  245. void TableListBox::setHeaderHeight (int newHeight)
  246. {
  247. header->setSize (header->getWidth(), newHeight);
  248. resized();
  249. }
  250. void TableListBox::autoSizeColumn (int columnId)
  251. {
  252. auto width = model != nullptr ? model->getColumnAutoSizeWidth (columnId) : 0;
  253. if (width > 0)
  254. header->setColumnWidth (columnId, width);
  255. }
  256. void TableListBox::autoSizeAllColumns()
  257. {
  258. for (int i = 0; i < header->getNumColumns (true); ++i)
  259. autoSizeColumn (header->getColumnIdOfIndex (i, true));
  260. }
  261. void TableListBox::setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept
  262. {
  263. autoSizeOptionsShown = shouldBeShown;
  264. }
  265. Rectangle<int> TableListBox::getCellPosition (int columnId, int rowNumber, bool relativeToComponentTopLeft) const
  266. {
  267. auto headerCell = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
  268. if (relativeToComponentTopLeft)
  269. headerCell.translate (header->getX(), 0);
  270. return getRowPosition (rowNumber, relativeToComponentTopLeft)
  271. .withX (headerCell.getX())
  272. .withWidth (headerCell.getWidth());
  273. }
  274. Component* TableListBox::getCellComponent (int columnId, int rowNumber) const
  275. {
  276. if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (rowNumber)))
  277. return rowComp->findChildComponentForColumn (columnId);
  278. return nullptr;
  279. }
  280. void TableListBox::scrollToEnsureColumnIsOnscreen (int columnId)
  281. {
  282. auto& scrollbar = getHorizontalScrollBar();
  283. auto pos = header->getColumnPosition (header->getIndexOfColumnId (columnId, true));
  284. auto x = scrollbar.getCurrentRangeStart();
  285. auto w = scrollbar.getCurrentRangeSize();
  286. if (pos.getX() < x)
  287. x = pos.getX();
  288. else if (pos.getRight() > x + w)
  289. x += jmax (0.0, pos.getRight() - (x + w));
  290. scrollbar.setCurrentRangeStart (x);
  291. }
  292. int TableListBox::getNumRows()
  293. {
  294. return model != nullptr ? model->getNumRows() : 0;
  295. }
  296. void TableListBox::paintListBoxItem (int, Graphics&, int, int, bool)
  297. {
  298. }
  299. Component* TableListBox::refreshComponentForRow (int rowNumber, bool rowSelected, Component* existingComponentToUpdate)
  300. {
  301. if (existingComponentToUpdate == nullptr)
  302. existingComponentToUpdate = new RowComp (*this);
  303. static_cast<RowComp*> (existingComponentToUpdate)->update (rowNumber, rowSelected);
  304. return existingComponentToUpdate;
  305. }
  306. void TableListBox::selectedRowsChanged (int row)
  307. {
  308. if (model != nullptr)
  309. model->selectedRowsChanged (row);
  310. }
  311. void TableListBox::deleteKeyPressed (int row)
  312. {
  313. if (model != nullptr)
  314. model->deleteKeyPressed (row);
  315. }
  316. void TableListBox::returnKeyPressed (int row)
  317. {
  318. if (model != nullptr)
  319. model->returnKeyPressed (row);
  320. }
  321. void TableListBox::backgroundClicked (const MouseEvent& e)
  322. {
  323. if (model != nullptr)
  324. model->backgroundClicked (e);
  325. }
  326. void TableListBox::listWasScrolled()
  327. {
  328. if (model != nullptr)
  329. model->listWasScrolled();
  330. }
  331. void TableListBox::tableColumnsChanged (TableHeaderComponent*)
  332. {
  333. setMinimumContentWidth (header->getTotalWidth());
  334. repaint();
  335. updateColumnComponents();
  336. }
  337. void TableListBox::tableColumnsResized (TableHeaderComponent*)
  338. {
  339. setMinimumContentWidth (header->getTotalWidth());
  340. repaint();
  341. updateColumnComponents();
  342. }
  343. void TableListBox::tableSortOrderChanged (TableHeaderComponent*)
  344. {
  345. if (model != nullptr)
  346. model->sortOrderChanged (header->getSortColumnId(),
  347. header->isSortedForwards());
  348. }
  349. void TableListBox::tableColumnDraggingChanged (TableHeaderComponent*, int columnIdNowBeingDragged_)
  350. {
  351. columnIdNowBeingDragged = columnIdNowBeingDragged_;
  352. repaint();
  353. }
  354. void TableListBox::resized()
  355. {
  356. ListBox::resized();
  357. header->resizeAllColumnsToFit (getVisibleContentWidth());
  358. setMinimumContentWidth (header->getTotalWidth());
  359. }
  360. void TableListBox::updateColumnComponents() const
  361. {
  362. auto firstRow = getRowContainingPosition (0, 0);
  363. for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;)
  364. if (auto* rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (i)))
  365. rowComp->resized();
  366. }
  367. //==============================================================================
  368. void TableListBoxModel::cellClicked (int, int, const MouseEvent&) {}
  369. void TableListBoxModel::cellDoubleClicked (int, int, const MouseEvent&) {}
  370. void TableListBoxModel::backgroundClicked (const MouseEvent&) {}
  371. void TableListBoxModel::sortOrderChanged (int, bool) {}
  372. int TableListBoxModel::getColumnAutoSizeWidth (int) { return 0; }
  373. void TableListBoxModel::selectedRowsChanged (int) {}
  374. void TableListBoxModel::deleteKeyPressed (int) {}
  375. void TableListBoxModel::returnKeyPressed (int) {}
  376. void TableListBoxModel::listWasScrolled() {}
  377. String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return {}; }
  378. var TableListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return {}; }
  379. Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate)
  380. {
  381. ignoreUnused (existingComponentToUpdate);
  382. jassert (existingComponentToUpdate == nullptr); // indicates a failure in the code that recycles the components
  383. return nullptr;
  384. }
  385. } // namespace juce