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.

933 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. BEGIN_JUCE_NAMESPACE
  19. //==============================================================================
  20. class DragOverlayComp : public Component
  21. {
  22. public:
  23. DragOverlayComp (const Image& image_)
  24. : image (image_)
  25. {
  26. image.duplicateIfShared();
  27. image.multiplyAllAlphas (0.8f);
  28. setAlwaysOnTop (true);
  29. }
  30. void paint (Graphics& g)
  31. {
  32. g.drawImageAt (image, 0, 0);
  33. }
  34. private:
  35. Image image;
  36. JUCE_DECLARE_NON_COPYABLE (DragOverlayComp);
  37. };
  38. //==============================================================================
  39. TableHeaderComponent::TableHeaderComponent()
  40. : columnsChanged (false),
  41. columnsResized (false),
  42. sortChanged (false),
  43. menuActive (true),
  44. stretchToFit (false),
  45. columnIdBeingResized (0),
  46. columnIdBeingDragged (0),
  47. columnIdUnderMouse (0),
  48. lastDeliberateWidth (0)
  49. {
  50. }
  51. TableHeaderComponent::~TableHeaderComponent()
  52. {
  53. dragOverlayComp = nullptr;
  54. }
  55. //==============================================================================
  56. void TableHeaderComponent::setPopupMenuActive (const bool hasMenu)
  57. {
  58. menuActive = hasMenu;
  59. }
  60. bool TableHeaderComponent::isPopupMenuActive() const { return menuActive; }
  61. //==============================================================================
  62. int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) const
  63. {
  64. if (onlyCountVisibleColumns)
  65. {
  66. int num = 0;
  67. for (int i = columns.size(); --i >= 0;)
  68. if (columns.getUnchecked(i)->isVisible())
  69. ++num;
  70. return num;
  71. }
  72. else
  73. {
  74. return columns.size();
  75. }
  76. }
  77. String TableHeaderComponent::getColumnName (const int columnId) const
  78. {
  79. const ColumnInfo* const ci = getInfoForId (columnId);
  80. return ci != nullptr ? ci->name : String::empty;
  81. }
  82. void TableHeaderComponent::setColumnName (const int columnId, const String& newName)
  83. {
  84. ColumnInfo* const ci = getInfoForId (columnId);
  85. if (ci != nullptr && ci->name != newName)
  86. {
  87. ci->name = newName;
  88. sendColumnsChanged();
  89. }
  90. }
  91. void TableHeaderComponent::addColumn (const String& columnName,
  92. const int columnId,
  93. const int width,
  94. const int minimumWidth,
  95. const int maximumWidth,
  96. const int propertyFlags,
  97. const int insertIndex)
  98. {
  99. // can't have a duplicate or null ID!
  100. jassert (columnId != 0 && getIndexOfColumnId (columnId, false) < 0);
  101. jassert (width > 0);
  102. ColumnInfo* const ci = new ColumnInfo();
  103. ci->name = columnName;
  104. ci->id = columnId;
  105. ci->width = width;
  106. ci->lastDeliberateWidth = width;
  107. ci->minimumWidth = minimumWidth;
  108. ci->maximumWidth = maximumWidth;
  109. if (ci->maximumWidth < 0)
  110. ci->maximumWidth = std::numeric_limits<int>::max();
  111. jassert (ci->maximumWidth >= ci->minimumWidth);
  112. ci->propertyFlags = propertyFlags;
  113. columns.insert (insertIndex, ci);
  114. sendColumnsChanged();
  115. }
  116. void TableHeaderComponent::removeColumn (const int columnIdToRemove)
  117. {
  118. const int index = getIndexOfColumnId (columnIdToRemove, false);
  119. if (index >= 0)
  120. {
  121. columns.remove (index);
  122. sortChanged = true;
  123. sendColumnsChanged();
  124. }
  125. }
  126. void TableHeaderComponent::removeAllColumns()
  127. {
  128. if (columns.size() > 0)
  129. {
  130. columns.clear();
  131. sendColumnsChanged();
  132. }
  133. }
  134. void TableHeaderComponent::moveColumn (const int columnId, int newIndex)
  135. {
  136. const int currentIndex = getIndexOfColumnId (columnId, false);
  137. newIndex = visibleIndexToTotalIndex (newIndex);
  138. if (columns [currentIndex] != 0 && currentIndex != newIndex)
  139. {
  140. columns.move (currentIndex, newIndex);
  141. sendColumnsChanged();
  142. }
  143. }
  144. int TableHeaderComponent::getColumnWidth (const int columnId) const
  145. {
  146. const ColumnInfo* const ci = getInfoForId (columnId);
  147. return ci != nullptr ? ci->width : 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. const ColumnInfo* const ci = columns [index];
  193. return (ci != nullptr) ? ci->id : 0;
  194. }
  195. const Rectangle<int> TableHeaderComponent::getColumnPosition (const int index) const
  196. {
  197. int x = 0, width = 0, n = 0;
  198. for (int i = 0; i < columns.size(); ++i)
  199. {
  200. x += width;
  201. if (columns.getUnchecked(i)->isVisible())
  202. {
  203. width = columns.getUnchecked(i)->width;
  204. if (n++ == index)
  205. break;
  206. }
  207. else
  208. {
  209. width = 0;
  210. }
  211. }
  212. return Rectangle<int> (x, 0, width, getHeight());
  213. }
  214. int TableHeaderComponent::getColumnIdAtX (const int xToFind) const
  215. {
  216. if (xToFind >= 0)
  217. {
  218. int x = 0;
  219. for (int i = 0; i < columns.size(); ++i)
  220. {
  221. const ColumnInfo* const ci = columns.getUnchecked(i);
  222. if (ci->isVisible())
  223. {
  224. x += ci->width;
  225. if (xToFind < x)
  226. return ci->id;
  227. }
  228. }
  229. }
  230. return 0;
  231. }
  232. int TableHeaderComponent::getTotalWidth() const
  233. {
  234. int w = 0;
  235. for (int i = columns.size(); --i >= 0;)
  236. if (columns.getUnchecked(i)->isVisible())
  237. w += columns.getUnchecked(i)->width;
  238. return w;
  239. }
  240. void TableHeaderComponent::setStretchToFitActive (const bool shouldStretchToFit)
  241. {
  242. stretchToFit = shouldStretchToFit;
  243. lastDeliberateWidth = getTotalWidth();
  244. resized();
  245. }
  246. bool TableHeaderComponent::isStretchToFitActive() const
  247. {
  248. return stretchToFit;
  249. }
  250. void TableHeaderComponent::resizeAllColumnsToFit (int targetTotalWidth)
  251. {
  252. if (stretchToFit && getWidth() > 0
  253. && columnIdBeingResized == 0 && columnIdBeingDragged == 0)
  254. {
  255. lastDeliberateWidth = targetTotalWidth;
  256. resizeColumnsToFit (0, targetTotalWidth);
  257. }
  258. }
  259. void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetTotalWidth)
  260. {
  261. targetTotalWidth = jmax (targetTotalWidth, 0);
  262. StretchableObjectResizer sor;
  263. int i;
  264. for (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 (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. ColumnInfo* const ci = getInfoForId (columnId);
  292. if (ci != nullptr && shouldBeVisible != ci->isVisible())
  293. {
  294. if (shouldBeVisible)
  295. ci->propertyFlags |= visible;
  296. else
  297. ci->propertyFlags &= ~visible;
  298. sendColumnsChanged();
  299. resized();
  300. }
  301. }
  302. bool TableHeaderComponent::isColumnVisible (const int columnId) const
  303. {
  304. const ColumnInfo* const ci = getInfoForId (columnId);
  305. return ci != nullptr && ci->isVisible();
  306. }
  307. //==============================================================================
  308. void TableHeaderComponent::setSortColumnId (const int columnId, const bool sortForwards)
  309. {
  310. if (getSortColumnId() != columnId || isSortedForwards() != sortForwards)
  311. {
  312. for (int i = columns.size(); --i >= 0;)
  313. columns.getUnchecked(i)->propertyFlags &= ~(sortedForwards | sortedBackwards);
  314. ColumnInfo* const ci = getInfoForId (columnId);
  315. if (ci != nullptr)
  316. ci->propertyFlags |= (sortForwards ? sortedForwards : sortedBackwards);
  317. reSortTable();
  318. }
  319. }
  320. int TableHeaderComponent::getSortColumnId() const
  321. {
  322. for (int i = columns.size(); --i >= 0;)
  323. if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
  324. return columns.getUnchecked(i)->id;
  325. return 0;
  326. }
  327. bool TableHeaderComponent::isSortedForwards() const
  328. {
  329. for (int i = columns.size(); --i >= 0;)
  330. if ((columns.getUnchecked(i)->propertyFlags & (sortedForwards | sortedBackwards)) != 0)
  331. return (columns.getUnchecked(i)->propertyFlags & sortedForwards) != 0;
  332. return true;
  333. }
  334. void TableHeaderComponent::reSortTable()
  335. {
  336. sortChanged = true;
  337. repaint();
  338. triggerAsyncUpdate();
  339. }
  340. //==============================================================================
  341. String TableHeaderComponent::toString() const
  342. {
  343. String s;
  344. XmlElement doc ("TABLELAYOUT");
  345. doc.setAttribute ("sortedCol", getSortColumnId());
  346. doc.setAttribute ("sortForwards", isSortedForwards());
  347. for (int i = 0; i < columns.size(); ++i)
  348. {
  349. const ColumnInfo* const ci = columns.getUnchecked (i);
  350. XmlElement* const e = doc.createNewChildElement ("COLUMN");
  351. e->setAttribute ("id", ci->id);
  352. e->setAttribute ("visible", ci->isVisible());
  353. e->setAttribute ("width", ci->width);
  354. }
  355. return doc.createDocument (String::empty, true, false);
  356. }
  357. void TableHeaderComponent::restoreFromString (const String& storedVersion)
  358. {
  359. ScopedPointer <XmlElement> storedXml (XmlDocument::parse (storedVersion));
  360. int index = 0;
  361. if (storedXml != nullptr && storedXml->hasTagName ("TABLELAYOUT"))
  362. {
  363. forEachXmlChildElement (*storedXml, col)
  364. {
  365. const int tabId = col->getIntAttribute ("id");
  366. ColumnInfo* const ci = getInfoForId (tabId);
  367. if (ci != nullptr)
  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.removeValue (listenerToRemove);
  389. }
  390. //==============================================================================
  391. void TableHeaderComponent::columnClicked (int columnId, const ModifierKeys& mods)
  392. {
  393. const ColumnInfo* const ci = getInfoForId (columnId);
  394. if (ci != nullptr && (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, 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.x, e.y);
  449. }
  450. void TableHeaderComponent::mouseEnter (const MouseEvent& e)
  451. {
  452. updateColumnUnderMouse (e.x, e.y);
  453. }
  454. void TableHeaderComponent::mouseExit (const MouseEvent& e)
  455. {
  456. updateColumnUnderMouse (e.x, e.y);
  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.mouseWasClicked() || e.mods.isPopupMenu()))
  477. {
  478. dragOverlayComp = nullptr;
  479. columnIdBeingResized = getResizeDraggerAt (e.getMouseDownX());
  480. if (columnIdBeingResized != 0)
  481. {
  482. const ColumnInfo* const ci = getInfoForId (columnIdBeingResized);
  483. initialColumnWidth = ci->width;
  484. }
  485. else
  486. {
  487. beginDrag (e);
  488. }
  489. }
  490. if (columnIdBeingResized != 0)
  491. {
  492. const ColumnInfo* const ci = getInfoForId (columnIdBeingResized);
  493. if (ci != nullptr)
  494. {
  495. int w = jlimit (ci->minimumWidth, ci->maximumWidth,
  496. initialColumnWidth + e.getDistanceFromDragStartX());
  497. if (stretchToFit)
  498. {
  499. // prevent us dragging a column too far right if we're in stretch-to-fit mode
  500. int minWidthOnRight = 0;
  501. for (int i = getIndexOfColumnId (columnIdBeingResized, false) + 1; i < columns.size(); ++i)
  502. if (columns.getUnchecked (i)->isVisible())
  503. minWidthOnRight += columns.getUnchecked (i)->minimumWidth;
  504. const Rectangle<int> currentPos (getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true)));
  505. w = jmax (ci->minimumWidth, jmin (w, getWidth() - minWidthOnRight - currentPos.getX()));
  506. }
  507. setColumnWidth (columnIdBeingResized, w);
  508. }
  509. }
  510. else if (columnIdBeingDragged != 0)
  511. {
  512. if (e.y >= -50 && e.y < getHeight() + 50)
  513. {
  514. if (dragOverlayComp != nullptr)
  515. {
  516. dragOverlayComp->setVisible (true);
  517. dragOverlayComp->setBounds (jlimit (0,
  518. jmax (0, getTotalWidth() - dragOverlayComp->getWidth()),
  519. e.x - draggingColumnOffset),
  520. 0,
  521. dragOverlayComp->getWidth(),
  522. getHeight());
  523. for (int i = columns.size(); --i >= 0;)
  524. {
  525. const int currentIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  526. int newIndex = currentIndex;
  527. if (newIndex > 0)
  528. {
  529. // if the previous column isn't draggable, we can't move our column
  530. // past it, because that'd change the undraggable column's position..
  531. const ColumnInfo* const previous = columns.getUnchecked (newIndex - 1);
  532. if ((previous->propertyFlags & draggable) != 0)
  533. {
  534. const int leftOfPrevious = getColumnPosition (newIndex - 1).getX();
  535. const int rightOfCurrent = getColumnPosition (newIndex).getRight();
  536. if (abs (dragOverlayComp->getX() - leftOfPrevious)
  537. < abs (dragOverlayComp->getRight() - rightOfCurrent))
  538. {
  539. --newIndex;
  540. }
  541. }
  542. }
  543. if (newIndex < columns.size() - 1)
  544. {
  545. // if the next column isn't draggable, we can't move our column
  546. // past it, because that'd change the undraggable column's position..
  547. const ColumnInfo* const nextCol = columns.getUnchecked (newIndex + 1);
  548. if ((nextCol->propertyFlags & draggable) != 0)
  549. {
  550. const int leftOfCurrent = getColumnPosition (newIndex).getX();
  551. const int rightOfNext = getColumnPosition (newIndex + 1).getRight();
  552. if (abs (dragOverlayComp->getX() - leftOfCurrent)
  553. > abs (dragOverlayComp->getRight() - rightOfNext))
  554. {
  555. ++newIndex;
  556. }
  557. }
  558. }
  559. if (newIndex != currentIndex)
  560. moveColumn (columnIdBeingDragged, newIndex);
  561. else
  562. break;
  563. }
  564. }
  565. }
  566. else
  567. {
  568. endDrag (draggingColumnOriginalIndex);
  569. }
  570. }
  571. }
  572. void TableHeaderComponent::beginDrag (const MouseEvent& e)
  573. {
  574. if (columnIdBeingDragged == 0)
  575. {
  576. columnIdBeingDragged = getColumnIdAtX (e.getMouseDownX());
  577. const ColumnInfo* const ci = getInfoForId (columnIdBeingDragged);
  578. if (ci == nullptr || (ci->propertyFlags & draggable) == 0)
  579. {
  580. columnIdBeingDragged = 0;
  581. }
  582. else
  583. {
  584. draggingColumnOriginalIndex = getIndexOfColumnId (columnIdBeingDragged, true);
  585. const Rectangle<int> columnRect (getColumnPosition (draggingColumnOriginalIndex));
  586. const int temp = columnIdBeingDragged;
  587. columnIdBeingDragged = 0;
  588. addAndMakeVisible (dragOverlayComp = new DragOverlayComp (createComponentSnapshot (columnRect, false)));
  589. columnIdBeingDragged = temp;
  590. dragOverlayComp->setBounds (columnRect);
  591. for (int i = listeners.size(); --i >= 0;)
  592. {
  593. listeners.getUnchecked(i)->tableColumnDraggingChanged (this, columnIdBeingDragged);
  594. i = jmin (i, listeners.size() - 1);
  595. }
  596. }
  597. }
  598. }
  599. void TableHeaderComponent::endDrag (const int finalIndex)
  600. {
  601. if (columnIdBeingDragged != 0)
  602. {
  603. moveColumn (columnIdBeingDragged, finalIndex);
  604. columnIdBeingDragged = 0;
  605. repaint();
  606. for (int i = listeners.size(); --i >= 0;)
  607. {
  608. listeners.getUnchecked(i)->tableColumnDraggingChanged (this, 0);
  609. i = jmin (i, listeners.size() - 1);
  610. }
  611. }
  612. }
  613. void TableHeaderComponent::mouseUp (const MouseEvent& e)
  614. {
  615. mouseDrag (e);
  616. for (int i = columns.size(); --i >= 0;)
  617. if (columns.getUnchecked (i)->isVisible())
  618. columns.getUnchecked (i)->lastDeliberateWidth = columns.getUnchecked (i)->width;
  619. columnIdBeingResized = 0;
  620. repaint();
  621. endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
  622. updateColumnUnderMouse (e.x, e.y);
  623. if (columnIdUnderMouse != 0 && e.mouseWasClicked() && ! e.mods.isPopupMenu())
  624. columnClicked (columnIdUnderMouse, e.mods);
  625. dragOverlayComp = nullptr;
  626. }
  627. MouseCursor TableHeaderComponent::getMouseCursor()
  628. {
  629. if (columnIdBeingResized != 0 || (getResizeDraggerAt (getMouseXYRelative().getX()) != 0 && ! isMouseButtonDown()))
  630. return MouseCursor (MouseCursor::LeftRightResizeCursor);
  631. return Component::getMouseCursor();
  632. }
  633. //==============================================================================
  634. bool TableHeaderComponent::ColumnInfo::isVisible() const
  635. {
  636. return (propertyFlags & TableHeaderComponent::visible) != 0;
  637. }
  638. TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (const int id) const
  639. {
  640. for (int i = columns.size(); --i >= 0;)
  641. if (columns.getUnchecked(i)->id == id)
  642. return columns.getUnchecked(i);
  643. return nullptr;
  644. }
  645. int TableHeaderComponent::visibleIndexToTotalIndex (const int visibleIndex) const
  646. {
  647. int n = 0;
  648. for (int i = 0; i < columns.size(); ++i)
  649. {
  650. if (columns.getUnchecked(i)->isVisible())
  651. {
  652. if (n == visibleIndex)
  653. return i;
  654. ++n;
  655. }
  656. }
  657. return -1;
  658. }
  659. void TableHeaderComponent::sendColumnsChanged()
  660. {
  661. if (stretchToFit && lastDeliberateWidth > 0)
  662. resizeAllColumnsToFit (lastDeliberateWidth);
  663. repaint();
  664. columnsChanged = true;
  665. triggerAsyncUpdate();
  666. }
  667. void TableHeaderComponent::handleAsyncUpdate()
  668. {
  669. const bool changed = columnsChanged || sortChanged;
  670. const bool sized = columnsResized || changed;
  671. const bool sorted = sortChanged;
  672. columnsChanged = false;
  673. columnsResized = false;
  674. sortChanged = false;
  675. if (sorted)
  676. {
  677. for (int i = listeners.size(); --i >= 0;)
  678. {
  679. listeners.getUnchecked(i)->tableSortOrderChanged (this);
  680. i = jmin (i, listeners.size() - 1);
  681. }
  682. }
  683. if (changed)
  684. {
  685. for (int i = listeners.size(); --i >= 0;)
  686. {
  687. listeners.getUnchecked(i)->tableColumnsChanged (this);
  688. i = jmin (i, listeners.size() - 1);
  689. }
  690. }
  691. if (sized)
  692. {
  693. for (int i = listeners.size(); --i >= 0;)
  694. {
  695. listeners.getUnchecked(i)->tableColumnsResized (this);
  696. i = jmin (i, listeners.size() - 1);
  697. }
  698. }
  699. }
  700. int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const
  701. {
  702. if (isPositiveAndBelow (mouseX, getWidth()))
  703. {
  704. const int draggableDistance = 3;
  705. int x = 0;
  706. for (int i = 0; i < columns.size(); ++i)
  707. {
  708. const ColumnInfo* const ci = columns.getUnchecked(i);
  709. if (ci->isVisible())
  710. {
  711. if (abs (mouseX - (x + ci->width)) <= draggableDistance
  712. && (ci->propertyFlags & resizable) != 0)
  713. return ci->id;
  714. x += ci->width;
  715. }
  716. }
  717. }
  718. return 0;
  719. }
  720. void TableHeaderComponent::updateColumnUnderMouse (int x, int y)
  721. {
  722. const int newCol = (reallyContains (Point<int> (x, y), true) && getResizeDraggerAt (x) == 0)
  723. ? getColumnIdAtX (x) : 0;
  724. if (newCol != columnIdUnderMouse)
  725. {
  726. columnIdUnderMouse = newCol;
  727. repaint();
  728. }
  729. }
  730. static void tableHeaderMenuCallback (int result, TableHeaderComponent* tableHeader, int columnIdClicked)
  731. {
  732. if (tableHeader != nullptr && result != 0)
  733. tableHeader->reactToMenuItem (result, columnIdClicked);
  734. }
  735. void TableHeaderComponent::showColumnChooserMenu (const int columnIdClicked)
  736. {
  737. PopupMenu m;
  738. addMenuItems (m, columnIdClicked);
  739. if (m.getNumItems() > 0)
  740. {
  741. m.setLookAndFeel (&getLookAndFeel());
  742. m.showMenuAsync (PopupMenu::Options(),
  743. ModalCallbackFunction::forComponent (tableHeaderMenuCallback, this, columnIdClicked));
  744. }
  745. }
  746. void TableHeaderComponent::Listener::tableColumnDraggingChanged (TableHeaderComponent*, int)
  747. {
  748. }
  749. END_JUCE_NAMESPACE