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.

937 lines
27KB

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