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.

492 lines
15KB

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