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.

486 lines
15KB

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