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.

931 lines
27KB

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