Audio plugin host https://kx.studio/carla
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.

893 lines
25KB

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