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.

542 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_TabbedComponent.h"
  21. #include "../menus/juce_PopupMenu.h"
  22. #include "../lookandfeel/juce_LookAndFeel.h"
  23. //==============================================================================
  24. TabBarButton::TabBarButton (const String& name,
  25. TabbedButtonBar* const owner_,
  26. const int index)
  27. : Button (name),
  28. owner (owner_),
  29. tabIndex (index),
  30. overlapPixels (0)
  31. {
  32. shadow.setShadowProperties (2.2f, 0.7f, 0, 0);
  33. setComponentEffect (&shadow);
  34. setWantsKeyboardFocus (false);
  35. }
  36. TabBarButton::~TabBarButton()
  37. {
  38. }
  39. void TabBarButton::paintButton (Graphics& g,
  40. bool isMouseOverButton,
  41. bool isButtonDown)
  42. {
  43. int x, y, w, h;
  44. getActiveArea (x, y, w, h);
  45. g.setOrigin (x, y);
  46. getLookAndFeel()
  47. .drawTabButton (g, w, h,
  48. owner->getTabBackgroundColour (tabIndex),
  49. tabIndex, getButtonText(), *this,
  50. owner->getOrientation(),
  51. isMouseOverButton, isButtonDown,
  52. getToggleState());
  53. }
  54. void TabBarButton::clicked (const ModifierKeys& mods)
  55. {
  56. if (mods.isPopupMenu())
  57. owner->popupMenuClickOnTab (tabIndex, getButtonText());
  58. else
  59. owner->setCurrentTabIndex (tabIndex);
  60. }
  61. bool TabBarButton::hitTest (int mx, int my)
  62. {
  63. int x, y, w, h;
  64. getActiveArea (x, y, w, h);
  65. if (owner->getOrientation() == TabbedButtonBar::TabsAtLeft
  66. || owner->getOrientation() == TabbedButtonBar::TabsAtRight)
  67. {
  68. if (((unsigned int) mx) < (unsigned int) getWidth()
  69. && my >= y + overlapPixels
  70. && my < y + h - overlapPixels)
  71. return true;
  72. }
  73. else
  74. {
  75. if (mx >= x + overlapPixels && mx < x + w - overlapPixels
  76. && ((unsigned int) my) < (unsigned int) getHeight())
  77. return true;
  78. }
  79. Path p;
  80. getLookAndFeel()
  81. .createTabButtonShape (p, w, h, tabIndex, getButtonText(), *this,
  82. owner->getOrientation(),
  83. false, false, getToggleState());
  84. return p.contains ((float) (mx - x),
  85. (float) (my - y));
  86. }
  87. int TabBarButton::getBestTabLength (const int depth)
  88. {
  89. return jlimit (depth * 2,
  90. depth * 7,
  91. getLookAndFeel().getTabButtonBestWidth (tabIndex, getButtonText(), depth, *this));
  92. }
  93. void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h)
  94. {
  95. x = 0;
  96. y = 0;
  97. int r = getWidth();
  98. int b = getHeight();
  99. const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
  100. if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft)
  101. r -= spaceAroundImage;
  102. if (owner->getOrientation() != TabbedButtonBar::TabsAtRight)
  103. x += spaceAroundImage;
  104. if (owner->getOrientation() != TabbedButtonBar::TabsAtBottom)
  105. y += spaceAroundImage;
  106. if (owner->getOrientation() != TabbedButtonBar::TabsAtTop)
  107. b -= spaceAroundImage;
  108. w = r - x;
  109. h = b - y;
  110. }
  111. //==============================================================================
  112. class TabAreaBehindFrontButtonComponent : public Component
  113. {
  114. public:
  115. TabAreaBehindFrontButtonComponent (TabbedButtonBar* const owner_)
  116. : owner (owner_)
  117. {
  118. setInterceptsMouseClicks (false, false);
  119. }
  120. ~TabAreaBehindFrontButtonComponent()
  121. {
  122. }
  123. void paint (Graphics& g)
  124. {
  125. getLookAndFeel()
  126. .drawTabAreaBehindFrontButton (g, getWidth(), getHeight(),
  127. *owner, owner->getOrientation());
  128. }
  129. void enablementChanged()
  130. {
  131. repaint();
  132. }
  133. private:
  134. TabbedButtonBar* const owner;
  135. TabAreaBehindFrontButtonComponent (const TabAreaBehindFrontButtonComponent&);
  136. const TabAreaBehindFrontButtonComponent& operator= (const TabAreaBehindFrontButtonComponent&);
  137. };
  138. //==============================================================================
  139. TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
  140. : orientation (orientation_),
  141. currentTabIndex (-1),
  142. extraTabsButton (0)
  143. {
  144. setInterceptsMouseClicks (false, true);
  145. addAndMakeVisible (behindFrontTab = new TabAreaBehindFrontButtonComponent (this));
  146. setFocusContainer (true);
  147. }
  148. TabbedButtonBar::~TabbedButtonBar()
  149. {
  150. deleteAllChildren();
  151. }
  152. //==============================================================================
  153. void TabbedButtonBar::setOrientation (const Orientation newOrientation)
  154. {
  155. orientation = newOrientation;
  156. for (int i = getNumChildComponents(); --i >= 0;)
  157. getChildComponent (i)->resized();
  158. resized();
  159. }
  160. TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int index)
  161. {
  162. return new TabBarButton (name, this, index);
  163. }
  164. //==============================================================================
  165. void TabbedButtonBar::clearTabs()
  166. {
  167. tabs.clear();
  168. tabColours.clear();
  169. currentTabIndex = -1;
  170. deleteAndZero (extraTabsButton);
  171. removeChildComponent (behindFrontTab);
  172. deleteAllChildren();
  173. addChildComponent (behindFrontTab);
  174. setCurrentTabIndex (-1);
  175. }
  176. void TabbedButtonBar::addTab (const String& tabName,
  177. const Colour& tabBackgroundColour,
  178. int insertIndex)
  179. {
  180. jassert (tabName.isNotEmpty()); // you have to give them all a name..
  181. if (tabName.isNotEmpty())
  182. {
  183. if (((unsigned int) insertIndex) > (unsigned int) tabs.size())
  184. insertIndex = tabs.size();
  185. for (int i = tabs.size(); --i >= insertIndex;)
  186. {
  187. TabBarButton* const tb = getTabButton (i);
  188. if (tb != 0)
  189. tb->tabIndex++;
  190. }
  191. tabs.insert (insertIndex, tabName);
  192. tabColours.insert (insertIndex, tabBackgroundColour);
  193. TabBarButton* const tb = createTabButton (tabName, insertIndex);
  194. jassert (tb != 0); // your createTabButton() mustn't return zero!
  195. addAndMakeVisible (tb, insertIndex);
  196. resized();
  197. if (currentTabIndex < 0)
  198. setCurrentTabIndex (0);
  199. }
  200. }
  201. void TabbedButtonBar::setTabName (const int tabIndex,
  202. const String& newName)
  203. {
  204. if (((unsigned int) tabIndex) < (unsigned int) tabs.size()
  205. && tabs[tabIndex] != newName)
  206. {
  207. tabs.set (tabIndex, newName);
  208. TabBarButton* const tb = getTabButton (tabIndex);
  209. if (tb != 0)
  210. tb->setButtonText (newName);
  211. resized();
  212. }
  213. }
  214. void TabbedButtonBar::removeTab (const int tabIndex)
  215. {
  216. if (((unsigned int) tabIndex) < (unsigned int) tabs.size())
  217. {
  218. const int oldTabIndex = currentTabIndex;
  219. if (currentTabIndex == tabIndex)
  220. currentTabIndex = -1;
  221. tabs.remove (tabIndex);
  222. tabColours.remove (tabIndex);
  223. delete getTabButton (tabIndex);
  224. for (int i = tabIndex + 1; i <= tabs.size(); ++i)
  225. {
  226. TabBarButton* const tb = getTabButton (i);
  227. if (tb != 0)
  228. tb->tabIndex--;
  229. }
  230. resized();
  231. setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex));
  232. }
  233. }
  234. void TabbedButtonBar::moveTab (const int currentIndex,
  235. const int newIndex)
  236. {
  237. tabs.move (currentIndex, newIndex);
  238. tabColours.move (currentIndex, newIndex);
  239. resized();
  240. }
  241. int TabbedButtonBar::getNumTabs() const
  242. {
  243. return tabs.size();
  244. }
  245. const StringArray TabbedButtonBar::getTabNames() const
  246. {
  247. return tabs;
  248. }
  249. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  250. {
  251. if (currentTabIndex != newIndex)
  252. {
  253. if (((unsigned int) newIndex) >= (unsigned int) tabs.size())
  254. newIndex = -1;
  255. currentTabIndex = newIndex;
  256. for (int i = 0; i < getNumChildComponents(); ++i)
  257. {
  258. TabBarButton* const tb = dynamic_cast <TabBarButton*> (getChildComponent (i));
  259. if (tb != 0)
  260. tb->setToggleState (tb->tabIndex == newIndex, false);
  261. }
  262. resized();
  263. if (sendChangeMessage_)
  264. sendChangeMessage (this);
  265. currentTabChanged (newIndex, newIndex >= 0 ? tabs [newIndex] : String::empty);
  266. }
  267. }
  268. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  269. {
  270. for (int i = getNumChildComponents(); --i >= 0;)
  271. {
  272. TabBarButton* const tb = dynamic_cast <TabBarButton*> (getChildComponent (i));
  273. if (tb != 0 && tb->tabIndex == index)
  274. return tb;
  275. }
  276. return 0;
  277. }
  278. void TabbedButtonBar::lookAndFeelChanged()
  279. {
  280. deleteAndZero (extraTabsButton);
  281. resized();
  282. }
  283. void TabbedButtonBar::resized()
  284. {
  285. const double minimumScale = 0.7;
  286. int depth = getWidth();
  287. int length = getHeight();
  288. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  289. swapVariables (depth, length);
  290. const int overlap = getLookAndFeel().getTabButtonOverlap (depth)
  291. + getLookAndFeel().getTabButtonSpaceAroundImage() * 2;
  292. int i, totalLength = overlap;
  293. int numVisibleButtons = tabs.size();
  294. for (i = 0; i < getNumChildComponents(); ++i)
  295. {
  296. TabBarButton* const tb = dynamic_cast <TabBarButton*> (getChildComponent (i));
  297. if (tb != 0)
  298. {
  299. totalLength += tb->getBestTabLength (depth) - overlap;
  300. tb->overlapPixels = overlap / 2;
  301. }
  302. }
  303. double scale = 1.0;
  304. if (totalLength > length)
  305. scale = jmax (minimumScale, length / (double) totalLength);
  306. const bool isTooBig = totalLength * scale > length;
  307. int tabsButtonPos = 0;
  308. if (isTooBig)
  309. {
  310. if (extraTabsButton == 0)
  311. {
  312. addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton());
  313. extraTabsButton->addButtonListener (this);
  314. extraTabsButton->setAlwaysOnTop (true);
  315. extraTabsButton->setTriggeredOnMouseDown (true);
  316. }
  317. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  318. extraTabsButton->setSize (buttonSize, buttonSize);
  319. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  320. {
  321. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  322. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  323. }
  324. else
  325. {
  326. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  327. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  328. }
  329. totalLength = 0;
  330. for (i = 0; i < tabs.size(); ++i)
  331. {
  332. TabBarButton* const tb = getTabButton (i);
  333. if (tb != 0)
  334. {
  335. const int newLength = totalLength + tb->getBestTabLength (depth);
  336. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  337. {
  338. totalLength += overlap;
  339. break;
  340. }
  341. numVisibleButtons = i + 1;
  342. totalLength = newLength - overlap;
  343. }
  344. }
  345. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  346. }
  347. else
  348. {
  349. deleteAndZero (extraTabsButton);
  350. }
  351. int pos = 0;
  352. TabBarButton* frontTab = 0;
  353. for (i = 0; i < tabs.size(); ++i)
  354. {
  355. TabBarButton* const tb = getTabButton (i);
  356. if (tb != 0)
  357. {
  358. const int bestLength = roundDoubleToInt (scale * tb->getBestTabLength (depth));
  359. if (i < numVisibleButtons)
  360. {
  361. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  362. tb->setBounds (pos, 0, bestLength, getHeight());
  363. else
  364. tb->setBounds (0, pos, getWidth(), bestLength);
  365. tb->toBack();
  366. if (tb->tabIndex == currentTabIndex)
  367. frontTab = tb;
  368. tb->setVisible (true);
  369. }
  370. else
  371. {
  372. tb->setVisible (false);
  373. }
  374. pos += bestLength - overlap;
  375. }
  376. }
  377. behindFrontTab->setBounds (0, 0, getWidth(), getHeight());
  378. if (frontTab != 0)
  379. {
  380. frontTab->toFront (false);
  381. behindFrontTab->toBehind (frontTab);
  382. }
  383. }
  384. //==============================================================================
  385. const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  386. {
  387. return tabColours [tabIndex];
  388. }
  389. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour)
  390. {
  391. if (((unsigned int) tabIndex) < (unsigned int) tabColours.size()
  392. && tabColours [tabIndex] != newColour)
  393. {
  394. tabColours.set (tabIndex, newColour);
  395. repaint();
  396. }
  397. }
  398. void TabbedButtonBar::buttonClicked (Button* button)
  399. {
  400. if (extraTabsButton == button)
  401. {
  402. PopupMenu m;
  403. for (int i = 0; i < tabs.size(); ++i)
  404. {
  405. TabBarButton* const tb = getTabButton (i);
  406. if (tb != 0 && ! tb->isVisible())
  407. m.addItem (tb->tabIndex + 1, tabs[i], true, i == currentTabIndex);
  408. }
  409. const int res = m.showAt (extraTabsButton);
  410. if (res != 0)
  411. setCurrentTabIndex (res - 1);
  412. }
  413. }
  414. //==============================================================================
  415. void TabbedButtonBar::currentTabChanged (const int, const String&)
  416. {
  417. }
  418. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  419. {
  420. }
  421. END_JUCE_NAMESPACE