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.

927 lines
27KB

  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. class TableHeaderComponent::DragOverlayComp final : public Component
  21. {
  22. public:
  23. DragOverlayComp (const Image& i) : image (i)
  24. {
  25. image.duplicateIfShared();
  26. image.multiplyAllAlphas (0.8f);
  27. setAlwaysOnTop (true);
  28. }
  29. void paint (Graphics& g) override
  30. {
  31. g.drawImage (image, getLocalBounds().toFloat());
  32. }
  33. Image image;
  34. JUCE_DECLARE_NON_COPYABLE (DragOverlayComp)
  35. };
  36. //==============================================================================
  37. TableHeaderComponent::TableHeaderComponent()
  38. {
  39. setFocusContainerType (FocusContainerType::focusContainer);
  40. }
  41. TableHeaderComponent::~TableHeaderComponent()
  42. {
  43. dragOverlayComp.reset();
  44. }
  45. //==============================================================================
  46. void TableHeaderComponent::setPopupMenuActive (bool hasMenu)
  47. {
  48. menuActive = hasMenu;
  49. }
  50. bool TableHeaderComponent::isPopupMenuActive() const { return menuActive; }
  51. //==============================================================================
  52. int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) const
  53. {
  54. if (onlyCountVisibleColumns)
  55. {
  56. int num = 0;
  57. for (auto* c : columns)
  58. if (c->isVisible())
  59. ++num;
  60. return num;
  61. }
  62. return columns.size();
  63. }
  64. String TableHeaderComponent::getColumnName (const int columnId) const
  65. {
  66. if (auto* ci = getInfoForId (columnId))
  67. return ci->getTitle();
  68. return {};
  69. }
  70. void TableHeaderComponent::setColumnName (const int columnId, const String& newName)
  71. {
  72. if (auto* ci = getInfoForId (columnId))
  73. {
  74. if (ci->getTitle() != newName)
  75. {
  76. ci->setTitle (newName);
  77. sendColumnsChanged();
  78. }
  79. }
  80. }
  81. void TableHeaderComponent::addColumn (const String& columnName,
  82. int columnId,
  83. int width,
  84. int minimumWidth,
  85. int maximumWidth,
  86. int propertyFlags,
  87. int insertIndex)
  88. {
  89. // can't have a duplicate or zero ID!
  90. jassert (columnId != 0 && getIndexOfColumnId (columnId, false) < 0);
  91. jassert (width > 0);
  92. auto ci = new ColumnInfo();
  93. ci->setTitle (columnName);
  94. ci->id = columnId;
  95. ci->width = width;
  96. ci->lastDeliberateWidth = width;
  97. ci->minimumWidth = minimumWidth;
  98. ci->maximumWidth = maximumWidth >= 0 ? maximumWidth : std::numeric_limits<int>::max();
  99. jassert (ci->maximumWidth >= ci->minimumWidth);
  100. ci->propertyFlags = propertyFlags;
  101. auto* added = columns.insert (insertIndex, ci);
  102. addChildComponent (added);
  103. added->setVisible ((propertyFlags & visible) != 0);
  104. resized();
  105. sendColumnsChanged();
  106. }
  107. void TableHeaderComponent::removeColumn (const int columnIdToRemove)
  108. {
  109. auto index = getIndexOfColumnId (columnIdToRemove, false);
  110. if (index >= 0)
  111. {
  112. columns.remove (index);
  113. sortChanged = true;
  114. sendColumnsChanged();
  115. }
  116. }
  117. void TableHeaderComponent::removeAllColumns()
  118. {
  119. if (columns.size() > 0)
  120. {
  121. columns.clear();
  122. sendColumnsChanged();
  123. }
  124. }
  125. void TableHeaderComponent::moveColumn (const int columnId, int newIndex)
  126. {
  127. auto currentIndex = getIndexOfColumnId (columnId, false);
  128. newIndex = visibleIndexToTotalIndex (newIndex);
  129. if (columns[currentIndex] != nullptr && currentIndex != newIndex)
  130. {
  131. columns.move (currentIndex, newIndex);
  132. sendColumnsChanged();
  133. }
  134. }
  135. int TableHeaderComponent::getColumnWidth (const int columnId) const
  136. {
  137. if (auto* ci = getInfoForId (columnId))
  138. return ci->width;
  139. return 0;
  140. }
  141. void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidth)
  142. {
  143. if (auto* ci = getInfoForId (columnId))
  144. {
  145. const auto newWidthToUse = jlimit (ci->minimumWidth, ci->maximumWidth, newWidth);
  146. if (ci->width != newWidthToUse)
  147. {
  148. auto numColumns = getNumColumns (true);
  149. ci->lastDeliberateWidth = ci->width = newWidthToUse;
  150. if (stretchToFit)
  151. {
  152. auto index = getIndexOfColumnId (columnId, true) + 1;
  153. if (isPositiveAndBelow (index, numColumns))
  154. {
  155. auto x = getColumnPosition (index).getX();
  156. if (lastDeliberateWidth == 0)
  157. lastDeliberateWidth = getTotalWidth();
  158. resizeColumnsToFit (visibleIndexToTotalIndex (index), lastDeliberateWidth - x);
  159. }
  160. }
  161. resized();
  162. repaint();
  163. columnsResized = true;
  164. triggerAsyncUpdate();
  165. }
  166. }
  167. }
  168. //==============================================================================
  169. int TableHeaderComponent::getIndexOfColumnId (const int columnId, const bool onlyCountVisibleColumns) const
  170. {
  171. int n = 0;
  172. for (auto* c : columns)
  173. {
  174. if ((! onlyCountVisibleColumns) || c->isVisible())
  175. {
  176. if (c->id == columnId)
  177. return n;
  178. ++n;
  179. }
  180. }
  181. return -1;
  182. }
  183. int TableHeaderComponent::getColumnIdOfIndex (int index, const bool onlyCountVisibleColumns) const
  184. {
  185. if (onlyCountVisibleColumns)
  186. index = visibleIndexToTotalIndex (index);
  187. if (auto* ci = columns [index])
  188. return ci->id;
  189. return 0;
  190. }
  191. Rectangle<int> TableHeaderComponent::getColumnPosition (const int index) const
  192. {
  193. int x = 0, width = 0, n = 0;
  194. for (auto* c : columns)
  195. {
  196. x += width;
  197. if (c->isVisible())
  198. {
  199. width = c->width;
  200. if (n++ == index)
  201. break;
  202. }
  203. else
  204. {
  205. width = 0;
  206. }
  207. }
  208. return { x, 0, width, getHeight() };
  209. }
  210. int TableHeaderComponent::getColumnIdAtX (const int xToFind) const
  211. {
  212. if (xToFind >= 0)
  213. {
  214. int x = 0;
  215. for (auto* ci : columns)
  216. {
  217. if (ci->isVisible())
  218. {
  219. x += ci->width;
  220. if (xToFind < x)
  221. return ci->id;
  222. }
  223. }
  224. }
  225. return 0;
  226. }
  227. int TableHeaderComponent::getTotalWidth() const
  228. {
  229. int w = 0;
  230. for (auto* c : columns)
  231. if (c->isVisible())
  232. w += c->width;
  233. return w;
  234. }
  235. void TableHeaderComponent::setStretchToFitActive (const bool shouldStretchToFit)
  236. {
  237. stretchToFit = shouldStretchToFit;
  238. lastDeliberateWidth = getTotalWidth();
  239. resized();
  240. }
  241. bool TableHeaderComponent::isStretchToFitActive() const
  242. {
  243. return stretchToFit;
  244. }
  245. void TableHeaderComponent::resizeAllColumnsToFit (int targetTotalWidth)
  246. {
  247. if (stretchToFit && getWidth() > 0
  248. && columnIdBeingResized == 0 && columnIdBeingDragged == 0)
  249. {
  250. lastDeliberateWidth = targetTotalWidth;
  251. resizeColumnsToFit (0, targetTotalWidth);
  252. }
  253. }
  254. void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetTotalWidth)
  255. {
  256. targetTotalWidth = jmax (targetTotalWidth, 0);
  257. StretchableObjectResizer sor;
  258. for (int i = firstColumnIndex; i < columns.size(); ++i)
  259. {
  260. auto* ci = columns.getUnchecked (i);
  261. if (ci->isVisible())
  262. sor.addItem (ci->lastDeliberateWidth, ci->minimumWidth, ci->maximumWidth);
  263. }
  264. sor.resizeToFit (targetTotalWidth);
  265. int visIndex = 0;
  266. for (int i = firstColumnIndex; i < columns.size(); ++i)
  267. {
  268. auto* ci = columns.getUnchecked (i);
  269. if (ci->isVisible())
  270. {
  271. auto newWidth = jlimit (ci->minimumWidth, ci->maximumWidth,
  272. (int) std::floor (sor.getItemSize (visIndex++)));
  273. if (newWidth != ci->width)
  274. {
  275. ci->width = newWidth;
  276. resized();
  277. repaint();
  278. columnsResized = true;
  279. triggerAsyncUpdate();
  280. }
  281. }
  282. }
  283. }
  284. void TableHeaderComponent::setColumnVisible (const int columnId, const bool shouldBeVisible)
  285. {
  286. if (auto* ci = getInfoForId (columnId))
  287. {
  288. if (shouldBeVisible != ci->isVisible())
  289. {
  290. ci->setVisible (shouldBeVisible);
  291. sendColumnsChanged();
  292. resized();
  293. }
  294. }
  295. }
  296. bool TableHeaderComponent::isColumnVisible (const int columnId) const
  297. {
  298. if (auto* ci = getInfoForId (columnId))
  299. return ci->isVisible();
  300. return false;
  301. }
  302. //==============================================================================
  303. void TableHeaderComponent::setSortColumnId (const int columnId, const bool sortForwards)
  304. {
  305. if (getSortColumnId() != columnId || isSortedForwards() != sortForwards)
  306. {
  307. for (auto* c : columns)
  308. c->propertyFlags &= ~(sortedForwards | sortedBackwards);
  309. if (auto* ci = getInfoForId (columnId))
  310. ci->propertyFlags |= (sortForwards ? sortedForwards : sortedBackwards);
  311. reSortTable();
  312. }
  313. }
  314. int TableHeaderComponent::getSortColumnId() const
  315. {
  316. for (auto* c : columns)
  317. if ((c->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
  318. return c->id;
  319. return 0;
  320. }
  321. bool TableHeaderComponent::isSortedForwards() const
  322. {
  323. for (auto* c : columns)
  324. if ((c->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
  325. return (c->propertyFlags & sortedForwards) != 0;
  326. return true;
  327. }
  328. void TableHeaderComponent::reSortTable()
  329. {
  330. sortChanged = true;
  331. resized();
  332. repaint();
  333. triggerAsyncUpdate();
  334. }
  335. //==============================================================================
  336. String TableHeaderComponent::toString() const
  337. {
  338. String s;
  339. XmlElement doc ("TABLELAYOUT");
  340. doc.setAttribute ("sortedCol", getSortColumnId());
  341. doc.setAttribute ("sortForwards", isSortedForwards());
  342. for (auto* ci : columns)
  343. {
  344. auto* e = doc.createNewChildElement ("COLUMN");
  345. e->setAttribute ("id", ci->id);
  346. e->setAttribute ("visible", ci->isVisible());
  347. e->setAttribute ("width", ci->width);
  348. }
  349. return doc.toString (XmlElement::TextFormat().singleLine().withoutHeader());
  350. }
  351. void TableHeaderComponent::restoreFromString (const String& storedVersion)
  352. {
  353. if (auto storedXML = parseXMLIfTagMatches (storedVersion, "TABLELAYOUT"))
  354. {
  355. int index = 0;
  356. for (auto* col : storedXML->getChildIterator())
  357. {
  358. auto tabId = col->getIntAttribute ("id");
  359. if (auto* ci = getInfoForId (tabId))
  360. {
  361. columns.move (columns.indexOf (ci), index);
  362. ci->width = col->getIntAttribute ("width");
  363. setColumnVisible (tabId, col->getBoolAttribute ("visible"));
  364. }
  365. ++index;
  366. }
  367. columnsResized = true;
  368. sendColumnsChanged();
  369. setSortColumnId (storedXML->getIntAttribute ("sortedCol"),
  370. storedXML->getBoolAttribute ("sortForwards", true));
  371. }
  372. }
  373. //==============================================================================
  374. void TableHeaderComponent::addListener (Listener* newListener)
  375. {
  376. listeners.addIfNotAlreadyThere (newListener);
  377. }
  378. void TableHeaderComponent::removeListener (Listener* listenerToRemove)
  379. {
  380. listeners.removeFirstMatchingValue (listenerToRemove);
  381. }
  382. //==============================================================================
  383. void TableHeaderComponent::columnClicked (int columnId, const ModifierKeys& mods)
  384. {
  385. if (auto* ci = getInfoForId (columnId))
  386. if ((ci->propertyFlags & sortable) != 0 && ! mods.isPopupMenu())
  387. setSortColumnId (columnId, (ci->propertyFlags & sortedForwards) == 0);
  388. }
  389. void TableHeaderComponent::addMenuItems (PopupMenu& menu, const int /*columnIdClicked*/)
  390. {
  391. for (auto* ci : columns)
  392. if ((ci->propertyFlags & appearsOnColumnMenu) != 0)
  393. menu.addItem (ci->id, ci->getTitle(),
  394. (ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0,
  395. isColumnVisible (ci->id));
  396. }
  397. void TableHeaderComponent::reactToMenuItem (const int menuReturnId, const int /*columnIdClicked*/)
  398. {
  399. if (getIndexOfColumnId (menuReturnId, false) >= 0)
  400. setColumnVisible (menuReturnId, ! isColumnVisible (menuReturnId));
  401. }
  402. void TableHeaderComponent::drawColumnHeader (Graphics& g, LookAndFeel& lf, const ColumnInfo& ci)
  403. {
  404. // Only paint columns that are visible
  405. if (! ci.isVisible())
  406. return;
  407. // If this column is being dragged, it shouldn't be drawn in the table header
  408. if (ci.id == columnIdBeingDragged && dragOverlayComp != nullptr && dragOverlayComp->isVisible())
  409. return;
  410. // There's no point drawing this column header if no part of it is visible
  411. if (! g.getClipBounds()
  412. .getHorizontalRange()
  413. .intersects (Range<int>::withStartAndLength (ci.getX(), ci.width)))
  414. return;
  415. Graphics::ScopedSaveState ss (g);
  416. g.setOrigin (ci.getX(), ci.getY());
  417. g.reduceClipRegion (0, 0, ci.width, ci.getHeight());
  418. lf.drawTableHeaderColumn (g, *this, ci.getTitle(), ci.id, ci.width, getHeight(),
  419. ci.id == columnIdUnderMouse,
  420. ci.id == columnIdUnderMouse && isMouseButtonDown(),
  421. ci.propertyFlags);
  422. }
  423. void TableHeaderComponent::paint (Graphics& g)
  424. {
  425. auto& lf = getLookAndFeel();
  426. lf.drawTableHeaderBackground (g, *this);
  427. for (auto* ci : columns)
  428. drawColumnHeader (g, lf, *ci);
  429. }
  430. void TableHeaderComponent::resized()
  431. {
  432. int x = 0;
  433. for (auto* ci : columns)
  434. {
  435. const auto widthToUse = ci->isVisible() ? ci->width : 0;
  436. ci->setBounds (x, 0, widthToUse, getHeight());
  437. x += widthToUse;
  438. }
  439. }
  440. void TableHeaderComponent::mouseMove (const MouseEvent& e) { updateColumnUnderMouse (e); }
  441. void TableHeaderComponent::mouseEnter (const MouseEvent& e) { updateColumnUnderMouse (e); }
  442. void TableHeaderComponent::mouseExit (const MouseEvent&) { setColumnUnderMouse (0); }
  443. void TableHeaderComponent::mouseDown (const MouseEvent& e)
  444. {
  445. resized();
  446. repaint();
  447. columnIdBeingResized = 0;
  448. columnIdBeingDragged = 0;
  449. if (columnIdUnderMouse != 0)
  450. {
  451. draggingColumnOffset = e.x - getColumnPosition (getIndexOfColumnId (columnIdUnderMouse, true)).getX();
  452. if (e.mods.isPopupMenu())
  453. columnClicked (columnIdUnderMouse, e.mods);
  454. }
  455. if (menuActive && e.mods.isPopupMenu())
  456. showColumnChooserMenu (columnIdUnderMouse);
  457. }
  458. void TableHeaderComponent::mouseDrag (const MouseEvent& e)
  459. {
  460. if (columnIdBeingResized == 0
  461. && columnIdBeingDragged == 0
  462. && e.mouseWasDraggedSinceMouseDown()
  463. && ! e.mods.isPopupMenu())
  464. {
  465. dragOverlayComp.reset();
  466. columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX());
  467. if (columnIdBeingResized != 0)
  468. {
  469. if (auto* ci = getInfoForId (columnIdBeingResized))
  470. initialColumnWidth = ci->width;
  471. else
  472. jassertfalse;
  473. }
  474. else
  475. {
  476. beginDrag (e);
  477. }
  478. }
  479. if (columnIdBeingResized != 0)
  480. {
  481. if (auto* ci = getInfoForId (columnIdBeingResized))
  482. {
  483. auto w = jlimit (ci->minimumWidth, ci->maximumWidth,
  484. initialColumnWidth + e.getDistanceFromDragStartX());
  485. if (stretchToFit)
  486. {
  487. // prevent us dragging a column too far right if we're in stretch-to-fit mode
  488. int minWidthOnRight = 0;
  489. for (int i = getIndexOfColumnId (columnIdBeingResized, false) + 1; i < columns.size(); ++i)
  490. if (columns.getUnchecked (i)->isVisible())
  491. minWidthOnRight += columns.getUnchecked (i)->minimumWidth;
  492. auto currentPos = getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true));
  493. w = jmax (ci->minimumWidth, jmin (w, lastDeliberateWidth - minWidthOnRight - currentPos.getX()));
  494. }
  495. setColumnWidth (columnIdBeingResized, w);
  496. }
  497. }
  498. else if (columnIdBeingDragged != 0)
  499. {
  500. if (e.y >= -50 && e.y < getHeight() + 50)
  501. {
  502. if (dragOverlayComp != nullptr)
  503. {
  504. dragOverlayComp->setVisible (true);
  505. dragOverlayComp->setBounds (jlimit (0,
  506. jmax (0, getTotalWidth() - dragOverlayComp->getWidth()),
  507. e.x - draggingColumnOffset),
  508. 0,
  509. dragOverlayComp->getWidth(),
  510. getHeight());
  511. for (int i = columns.size(); --i >= 0;)
  512. {
  513. const int currentIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  514. int newIndex = currentIndex;
  515. if (newIndex > 0)
  516. {
  517. // if the previous column isn't draggable, we can't move our column
  518. // past it, because that'd change the undraggable column's position..
  519. auto* previous = columns.getUnchecked (newIndex - 1);
  520. if ((previous->propertyFlags & draggable) != 0)
  521. {
  522. auto leftOfPrevious = getColumnPosition (newIndex - 1).getX();
  523. auto rightOfCurrent = getColumnPosition (newIndex).getRight();
  524. if (std::abs (dragOverlayComp->getX() - leftOfPrevious)
  525. < std::abs (dragOverlayComp->getRight() - rightOfCurrent))
  526. {
  527. --newIndex;
  528. }
  529. }
  530. }
  531. if (newIndex < columns.size() - 1)
  532. {
  533. // if the next column isn't draggable, we can't move our column
  534. // past it, because that'd change the undraggable column's position..
  535. auto* nextCol = columns.getUnchecked (newIndex + 1);
  536. if ((nextCol->propertyFlags & draggable) != 0)
  537. {
  538. auto leftOfCurrent = getColumnPosition (newIndex).getX();
  539. auto rightOfNext = getColumnPosition (newIndex + 1).getRight();
  540. if (std::abs (dragOverlayComp->getX() - leftOfCurrent)
  541. > std::abs (dragOverlayComp->getRight() - rightOfNext))
  542. {
  543. ++newIndex;
  544. }
  545. }
  546. }
  547. if (newIndex != currentIndex)
  548. moveColumn (columnIdBeingDragged, newIndex);
  549. else
  550. break;
  551. }
  552. }
  553. }
  554. else
  555. {
  556. endDrag (draggingColumnOriginalIndex);
  557. }
  558. }
  559. }
  560. void TableHeaderComponent::beginDrag (const MouseEvent& e)
  561. {
  562. if (columnIdBeingDragged == 0)
  563. {
  564. columnIdBeingDragged = getColumnIdAtX (e.getMouseDownX());
  565. auto* ci = getInfoForId (columnIdBeingDragged);
  566. if (ci == nullptr || (ci->propertyFlags & draggable) == 0)
  567. {
  568. columnIdBeingDragged = 0;
  569. }
  570. else
  571. {
  572. draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  573. auto columnRect = getColumnPosition (draggingColumnOriginalIndex);
  574. auto temp = columnIdBeingDragged;
  575. columnIdBeingDragged = 0;
  576. dragOverlayComp.reset (new DragOverlayComp (createComponentSnapshot (columnRect, false, 2.0f)));
  577. addAndMakeVisible (dragOverlayComp.get());
  578. columnIdBeingDragged = temp;
  579. dragOverlayComp->setBounds (columnRect);
  580. for (int i = listeners.size(); --i >= 0;)
  581. {
  582. listeners.getUnchecked (i)->tableColumnDraggingChanged (this, columnIdBeingDragged);
  583. i = jmin (i, listeners.size() - 1);
  584. }
  585. }
  586. }
  587. }
  588. void TableHeaderComponent::endDrag (const int finalIndex)
  589. {
  590. if (columnIdBeingDragged != 0)
  591. {
  592. moveColumn (columnIdBeingDragged, finalIndex);
  593. columnIdBeingDragged = 0;
  594. resized();
  595. repaint();
  596. for (int i = listeners.size(); --i >= 0;)
  597. {
  598. listeners.getUnchecked (i)->tableColumnDraggingChanged (this, 0);
  599. i = jmin (i, listeners.size() - 1);
  600. }
  601. }
  602. }
  603. void TableHeaderComponent::mouseUp (const MouseEvent& e)
  604. {
  605. mouseDrag (e);
  606. for (auto* c : columns)
  607. if (c->isVisible())
  608. c->lastDeliberateWidth = c->width;
  609. columnIdBeingResized = 0;
  610. resized();
  611. repaint();
  612. endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
  613. updateColumnUnderMouse (e);
  614. if (columnIdUnderMouse != 0 && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu()))
  615. columnClicked (columnIdUnderMouse, e.mods);
  616. dragOverlayComp.reset();
  617. }
  618. MouseCursor TableHeaderComponent::getMouseCursor()
  619. {
  620. if (columnIdBeingResized != 0 || (getResizeDraggerAt (getMouseXYRelative().getX()) != 0 && ! isMouseButtonDown()))
  621. return MouseCursor (MouseCursor::LeftRightResizeCursor);
  622. return Component::getMouseCursor();
  623. }
  624. //==============================================================================
  625. TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (int id) const
  626. {
  627. for (auto* c : columns)
  628. if (c->id == id)
  629. return c;
  630. return nullptr;
  631. }
  632. int TableHeaderComponent::visibleIndexToTotalIndex (const int visibleIndex) const
  633. {
  634. int n = 0;
  635. for (int i = 0; i < columns.size(); ++i)
  636. {
  637. if (columns.getUnchecked (i)->isVisible())
  638. {
  639. if (n == visibleIndex)
  640. return i;
  641. ++n;
  642. }
  643. }
  644. return -1;
  645. }
  646. void TableHeaderComponent::sendColumnsChanged()
  647. {
  648. if (stretchToFit && lastDeliberateWidth > 0)
  649. resizeAllColumnsToFit (lastDeliberateWidth);
  650. resized();
  651. repaint();
  652. columnsChanged = true;
  653. triggerAsyncUpdate();
  654. }
  655. void TableHeaderComponent::handleAsyncUpdate()
  656. {
  657. const bool changed = columnsChanged || sortChanged;
  658. const bool sized = columnsResized || changed;
  659. const bool sorted = sortChanged;
  660. columnsChanged = false;
  661. columnsResized = false;
  662. sortChanged = false;
  663. if (sorted)
  664. {
  665. for (int i = listeners.size(); --i >= 0;)
  666. {
  667. listeners.getUnchecked (i)->tableSortOrderChanged (this);
  668. i = jmin (i, listeners.size() - 1);
  669. }
  670. }
  671. if (changed)
  672. {
  673. for (int i = listeners.size(); --i >= 0;)
  674. {
  675. listeners.getUnchecked (i)->tableColumnsChanged (this);
  676. i = jmin (i, listeners.size() - 1);
  677. }
  678. }
  679. if (sized)
  680. {
  681. for (int i = listeners.size(); --i >= 0;)
  682. {
  683. listeners.getUnchecked (i)->tableColumnsResized (this);
  684. i = jmin (i, listeners.size() - 1);
  685. }
  686. }
  687. }
  688. int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const
  689. {
  690. if (isPositiveAndBelow (mouseX, getWidth()))
  691. {
  692. const int draggableDistance = 3;
  693. int x = 0;
  694. for (auto* ci : columns)
  695. {
  696. if (ci->isVisible())
  697. {
  698. if (std::abs (mouseX - (x + ci->width)) <= draggableDistance
  699. && (ci->propertyFlags & resizable) != 0)
  700. return ci->id;
  701. x += ci->width;
  702. }
  703. }
  704. }
  705. return 0;
  706. }
  707. void TableHeaderComponent::setColumnUnderMouse (const int newCol)
  708. {
  709. if (newCol != columnIdUnderMouse)
  710. {
  711. columnIdUnderMouse = newCol;
  712. repaint();
  713. }
  714. }
  715. void TableHeaderComponent::updateColumnUnderMouse (const MouseEvent& e)
  716. {
  717. setColumnUnderMouse (reallyContains (e.getPosition(), true) && getResizeDraggerAt (e.x) == 0
  718. ? getColumnIdAtX (e.x) : 0);
  719. }
  720. static void tableHeaderMenuCallback (int result, TableHeaderComponent* tableHeader, int columnIdClicked)
  721. {
  722. if (tableHeader != nullptr && result != 0)
  723. tableHeader->reactToMenuItem (result, columnIdClicked);
  724. }
  725. void TableHeaderComponent::showColumnChooserMenu (const int columnIdClicked)
  726. {
  727. PopupMenu m;
  728. addMenuItems (m, columnIdClicked);
  729. if (m.getNumItems() > 0)
  730. {
  731. m.setLookAndFeel (&getLookAndFeel());
  732. m.showMenuAsync (PopupMenu::Options(),
  733. ModalCallbackFunction::forComponent (tableHeaderMenuCallback, this, columnIdClicked));
  734. }
  735. }
  736. void TableHeaderComponent::Listener::tableColumnDraggingChanged (TableHeaderComponent*, int)
  737. {
  738. }
  739. //==============================================================================
  740. std::unique_ptr<AccessibilityHandler> TableHeaderComponent::createAccessibilityHandler()
  741. {
  742. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
  743. }
  744. std::unique_ptr<AccessibilityHandler> TableHeaderComponent::ColumnInfo::createAccessibilityHandler()
  745. {
  746. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
  747. }
  748. } // namespace juce