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.

930 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 : 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::paint (Graphics& g)
  403. {
  404. auto& lf = getLookAndFeel();
  405. lf.drawTableHeaderBackground (g, *this);
  406. for (auto* ci : columns)
  407. {
  408. if (ci->isVisible() && ci->getWidth() > 0)
  409. {
  410. Graphics::ScopedSaveState ss (g);
  411. g.setOrigin (ci->getX(), ci->getY());
  412. g.reduceClipRegion (0, 0, ci->getWidth(), ci->getHeight());
  413. lf.drawTableHeaderColumn (g, *this, ci->getTitle(), ci->id, ci->width, getHeight(),
  414. ci->id == columnIdUnderMouse,
  415. ci->id == columnIdUnderMouse && isMouseButtonDown(),
  416. ci->propertyFlags);
  417. }
  418. }
  419. }
  420. void TableHeaderComponent::resized()
  421. {
  422. auto clip = getBounds();
  423. int x = 0;
  424. for (auto* ci : columns)
  425. ci->setBounds (0, 0, 0, 0);
  426. for (auto* ci : columns)
  427. {
  428. if (ci->isVisible())
  429. {
  430. if (x + ci->width > clip.getX()
  431. && (ci->id != columnIdBeingDragged
  432. || dragOverlayComp == nullptr
  433. || ! dragOverlayComp->isVisible()))
  434. {
  435. ci->setBounds (x, 0, ci->width, getHeight());
  436. }
  437. x += ci->width;
  438. if (x >= clip.getRight())
  439. break;
  440. }
  441. }
  442. }
  443. void TableHeaderComponent::mouseMove (const MouseEvent& e) { updateColumnUnderMouse (e); }
  444. void TableHeaderComponent::mouseEnter (const MouseEvent& e) { updateColumnUnderMouse (e); }
  445. void TableHeaderComponent::mouseExit (const MouseEvent&) { setColumnUnderMouse (0); }
  446. void TableHeaderComponent::mouseDown (const MouseEvent& e)
  447. {
  448. resized();
  449. repaint();
  450. columnIdBeingResized = 0;
  451. columnIdBeingDragged = 0;
  452. if (columnIdUnderMouse != 0)
  453. {
  454. draggingColumnOffset = e.x - getColumnPosition (getIndexOfColumnId (columnIdUnderMouse, true)).getX();
  455. if (e.mods.isPopupMenu())
  456. columnClicked (columnIdUnderMouse, e.mods);
  457. }
  458. if (menuActive && e.mods.isPopupMenu())
  459. showColumnChooserMenu (columnIdUnderMouse);
  460. }
  461. void TableHeaderComponent::mouseDrag (const MouseEvent& e)
  462. {
  463. if (columnIdBeingResized == 0
  464. && columnIdBeingDragged == 0
  465. && e.mouseWasDraggedSinceMouseDown()
  466. && ! e.mods.isPopupMenu())
  467. {
  468. dragOverlayComp.reset();
  469. columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX());
  470. if (columnIdBeingResized != 0)
  471. {
  472. if (auto* ci = getInfoForId (columnIdBeingResized))
  473. initialColumnWidth = ci->width;
  474. else
  475. jassertfalse;
  476. }
  477. else
  478. {
  479. beginDrag (e);
  480. }
  481. }
  482. if (columnIdBeingResized != 0)
  483. {
  484. if (auto* ci = getInfoForId (columnIdBeingResized))
  485. {
  486. auto w = jlimit (ci->minimumWidth, ci->maximumWidth,
  487. initialColumnWidth + e.getDistanceFromDragStartX());
  488. if (stretchToFit)
  489. {
  490. // prevent us dragging a column too far right if we're in stretch-to-fit mode
  491. int minWidthOnRight = 0;
  492. for (int i = getIndexOfColumnId (columnIdBeingResized, false) + 1; i < columns.size(); ++i)
  493. if (columns.getUnchecked (i)->isVisible())
  494. minWidthOnRight += columns.getUnchecked (i)->minimumWidth;
  495. auto currentPos = getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true));
  496. w = jmax (ci->minimumWidth, jmin (w, lastDeliberateWidth - minWidthOnRight - currentPos.getX()));
  497. }
  498. setColumnWidth (columnIdBeingResized, w);
  499. }
  500. }
  501. else if (columnIdBeingDragged != 0)
  502. {
  503. if (e.y >= -50 && e.y < getHeight() + 50)
  504. {
  505. if (dragOverlayComp != nullptr)
  506. {
  507. dragOverlayComp->setVisible (true);
  508. dragOverlayComp->setBounds (jlimit (0,
  509. jmax (0, getTotalWidth() - dragOverlayComp->getWidth()),
  510. e.x - draggingColumnOffset),
  511. 0,
  512. dragOverlayComp->getWidth(),
  513. getHeight());
  514. for (int i = columns.size(); --i >= 0;)
  515. {
  516. const int currentIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  517. int newIndex = currentIndex;
  518. if (newIndex > 0)
  519. {
  520. // if the previous column isn't draggable, we can't move our column
  521. // past it, because that'd change the undraggable column's position..
  522. auto* previous = columns.getUnchecked (newIndex - 1);
  523. if ((previous->propertyFlags & draggable) != 0)
  524. {
  525. auto leftOfPrevious = getColumnPosition (newIndex - 1).getX();
  526. auto rightOfCurrent = getColumnPosition (newIndex).getRight();
  527. if (std::abs (dragOverlayComp->getX() - leftOfPrevious)
  528. < std::abs (dragOverlayComp->getRight() - rightOfCurrent))
  529. {
  530. --newIndex;
  531. }
  532. }
  533. }
  534. if (newIndex < columns.size() - 1)
  535. {
  536. // if the next column isn't draggable, we can't move our column
  537. // past it, because that'd change the undraggable column's position..
  538. auto* nextCol = columns.getUnchecked (newIndex + 1);
  539. if ((nextCol->propertyFlags & draggable) != 0)
  540. {
  541. auto leftOfCurrent = getColumnPosition (newIndex).getX();
  542. auto rightOfNext = getColumnPosition (newIndex + 1).getRight();
  543. if (std::abs (dragOverlayComp->getX() - leftOfCurrent)
  544. > std::abs (dragOverlayComp->getRight() - rightOfNext))
  545. {
  546. ++newIndex;
  547. }
  548. }
  549. }
  550. if (newIndex != currentIndex)
  551. moveColumn (columnIdBeingDragged, newIndex);
  552. else
  553. break;
  554. }
  555. }
  556. }
  557. else
  558. {
  559. endDrag (draggingColumnOriginalIndex);
  560. }
  561. }
  562. }
  563. void TableHeaderComponent::beginDrag (const MouseEvent& e)
  564. {
  565. if (columnIdBeingDragged == 0)
  566. {
  567. columnIdBeingDragged = getColumnIdAtX (e.getMouseDownX());
  568. auto* ci = getInfoForId (columnIdBeingDragged);
  569. if (ci == nullptr || (ci->propertyFlags & draggable) == 0)
  570. {
  571. columnIdBeingDragged = 0;
  572. }
  573. else
  574. {
  575. draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  576. auto columnRect = getColumnPosition (draggingColumnOriginalIndex);
  577. auto temp = columnIdBeingDragged;
  578. columnIdBeingDragged = 0;
  579. dragOverlayComp.reset (new DragOverlayComp (createComponentSnapshot (columnRect, false, 2.0f)));
  580. addAndMakeVisible (dragOverlayComp.get());
  581. columnIdBeingDragged = temp;
  582. dragOverlayComp->setBounds (columnRect);
  583. for (int i = listeners.size(); --i >= 0;)
  584. {
  585. listeners.getUnchecked(i)->tableColumnDraggingChanged (this, columnIdBeingDragged);
  586. i = jmin (i, listeners.size() - 1);
  587. }
  588. }
  589. }
  590. }
  591. void TableHeaderComponent::endDrag (const int finalIndex)
  592. {
  593. if (columnIdBeingDragged != 0)
  594. {
  595. moveColumn (columnIdBeingDragged, finalIndex);
  596. columnIdBeingDragged = 0;
  597. resized();
  598. repaint();
  599. for (int i = listeners.size(); --i >= 0;)
  600. {
  601. listeners.getUnchecked(i)->tableColumnDraggingChanged (this, 0);
  602. i = jmin (i, listeners.size() - 1);
  603. }
  604. }
  605. }
  606. void TableHeaderComponent::mouseUp (const MouseEvent& e)
  607. {
  608. mouseDrag (e);
  609. for (auto* c : columns)
  610. if (c->isVisible())
  611. c->lastDeliberateWidth = c->width;
  612. columnIdBeingResized = 0;
  613. resized();
  614. repaint();
  615. endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
  616. updateColumnUnderMouse (e);
  617. if (columnIdUnderMouse != 0 && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu()))
  618. columnClicked (columnIdUnderMouse, e.mods);
  619. dragOverlayComp.reset();
  620. }
  621. MouseCursor TableHeaderComponent::getMouseCursor()
  622. {
  623. if (columnIdBeingResized != 0 || (getResizeDraggerAt (getMouseXYRelative().getX()) != 0 && ! isMouseButtonDown()))
  624. return MouseCursor (MouseCursor::LeftRightResizeCursor);
  625. return Component::getMouseCursor();
  626. }
  627. //==============================================================================
  628. TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (int id) const
  629. {
  630. for (auto* c : columns)
  631. if (c->id == id)
  632. return c;
  633. return nullptr;
  634. }
  635. int TableHeaderComponent::visibleIndexToTotalIndex (const int visibleIndex) const
  636. {
  637. int n = 0;
  638. for (int i = 0; i < columns.size(); ++i)
  639. {
  640. if (columns.getUnchecked(i)->isVisible())
  641. {
  642. if (n == visibleIndex)
  643. return i;
  644. ++n;
  645. }
  646. }
  647. return -1;
  648. }
  649. void TableHeaderComponent::sendColumnsChanged()
  650. {
  651. if (stretchToFit && lastDeliberateWidth > 0)
  652. resizeAllColumnsToFit (lastDeliberateWidth);
  653. resized();
  654. repaint();
  655. columnsChanged = true;
  656. triggerAsyncUpdate();
  657. }
  658. void TableHeaderComponent::handleAsyncUpdate()
  659. {
  660. const bool changed = columnsChanged || sortChanged;
  661. const bool sized = columnsResized || changed;
  662. const bool sorted = sortChanged;
  663. columnsChanged = false;
  664. columnsResized = false;
  665. sortChanged = false;
  666. if (sorted)
  667. {
  668. for (int i = listeners.size(); --i >= 0;)
  669. {
  670. listeners.getUnchecked(i)->tableSortOrderChanged (this);
  671. i = jmin (i, listeners.size() - 1);
  672. }
  673. }
  674. if (changed)
  675. {
  676. for (int i = listeners.size(); --i >= 0;)
  677. {
  678. listeners.getUnchecked(i)->tableColumnsChanged (this);
  679. i = jmin (i, listeners.size() - 1);
  680. }
  681. }
  682. if (sized)
  683. {
  684. for (int i = listeners.size(); --i >= 0;)
  685. {
  686. listeners.getUnchecked(i)->tableColumnsResized (this);
  687. i = jmin (i, listeners.size() - 1);
  688. }
  689. }
  690. }
  691. int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const
  692. {
  693. if (isPositiveAndBelow (mouseX, getWidth()))
  694. {
  695. const int draggableDistance = 3;
  696. int x = 0;
  697. for (auto* ci : columns)
  698. {
  699. if (ci->isVisible())
  700. {
  701. if (std::abs (mouseX - (x + ci->width)) <= draggableDistance
  702. && (ci->propertyFlags & resizable) != 0)
  703. return ci->id;
  704. x += ci->width;
  705. }
  706. }
  707. }
  708. return 0;
  709. }
  710. void TableHeaderComponent::setColumnUnderMouse (const int newCol)
  711. {
  712. if (newCol != columnIdUnderMouse)
  713. {
  714. columnIdUnderMouse = newCol;
  715. repaint();
  716. }
  717. }
  718. void TableHeaderComponent::updateColumnUnderMouse (const MouseEvent& e)
  719. {
  720. setColumnUnderMouse (reallyContains (e.getPosition(), true) && getResizeDraggerAt (e.x) == 0
  721. ? getColumnIdAtX (e.x) : 0);
  722. }
  723. static void tableHeaderMenuCallback (int result, TableHeaderComponent* tableHeader, int columnIdClicked)
  724. {
  725. if (tableHeader != nullptr && result != 0)
  726. tableHeader->reactToMenuItem (result, columnIdClicked);
  727. }
  728. void TableHeaderComponent::showColumnChooserMenu (const int columnIdClicked)
  729. {
  730. PopupMenu m;
  731. addMenuItems (m, columnIdClicked);
  732. if (m.getNumItems() > 0)
  733. {
  734. m.setLookAndFeel (&getLookAndFeel());
  735. m.showMenuAsync (PopupMenu::Options(),
  736. ModalCallbackFunction::forComponent (tableHeaderMenuCallback, this, columnIdClicked));
  737. }
  738. }
  739. void TableHeaderComponent::Listener::tableColumnDraggingChanged (TableHeaderComponent*, int)
  740. {
  741. }
  742. //==============================================================================
  743. std::unique_ptr<AccessibilityHandler> TableHeaderComponent::createAccessibilityHandler()
  744. {
  745. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
  746. }
  747. std::unique_ptr<AccessibilityHandler> TableHeaderComponent::ColumnInfo::createAccessibilityHandler()
  748. {
  749. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
  750. }
  751. } // namespace juce