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.

509 lines
15KB

  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. #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, TabbedButtonBar& owner_)
  25. : Button (name),
  26. owner (owner_),
  27. overlapPixels (0)
  28. {
  29. shadow.setShadowProperties (2.2f, 0.7f, 0, 0);
  30. setComponentEffect (&shadow);
  31. setWantsKeyboardFocus (false);
  32. }
  33. TabBarButton::~TabBarButton()
  34. {
  35. }
  36. int TabBarButton::getIndex() const
  37. {
  38. return owner.indexOfTabButton (this);
  39. }
  40. void TabBarButton::paintButton (Graphics& g,
  41. bool isMouseOverButton,
  42. bool isButtonDown)
  43. {
  44. const Rectangle<int> area (getActiveArea());
  45. g.setOrigin (area.getX(), area.getY());
  46. getLookAndFeel()
  47. .drawTabButton (g, area.getWidth(), area.getHeight(),
  48. owner.getTabBackgroundColour (getIndex()),
  49. getIndex(), 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 (getIndex(), getButtonText());
  58. else
  59. owner.setCurrentTabIndex (getIndex());
  60. }
  61. bool TabBarButton::hitTest (int mx, int my)
  62. {
  63. const Rectangle<int> area (getActiveArea());
  64. if (owner.getOrientation() == TabbedButtonBar::TabsAtLeft
  65. || owner.getOrientation() == TabbedButtonBar::TabsAtRight)
  66. {
  67. if (isPositiveAndBelow (mx, getWidth())
  68. && my >= area.getY() + overlapPixels
  69. && my < area.getBottom() - overlapPixels)
  70. return true;
  71. }
  72. else
  73. {
  74. if (mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels
  75. && isPositiveAndBelow (my, getHeight()))
  76. return true;
  77. }
  78. Path p;
  79. getLookAndFeel()
  80. .createTabButtonShape (p, area.getWidth(), area.getHeight(), getIndex(), getButtonText(), *this,
  81. owner.getOrientation(), false, false, getToggleState());
  82. return p.contains ((float) (mx - area.getX()),
  83. (float) (my - area.getY()));
  84. }
  85. int TabBarButton::getBestTabLength (const int depth)
  86. {
  87. return jlimit (depth * 2,
  88. depth * 7,
  89. getLookAndFeel().getTabButtonBestWidth (getIndex(), getButtonText(), depth, *this));
  90. }
  91. const Rectangle<int> TabBarButton::getActiveArea()
  92. {
  93. Rectangle<int> r (getLocalBounds());
  94. const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
  95. if (owner.getOrientation() != TabbedButtonBar::TabsAtLeft) r.removeFromRight (spaceAroundImage);
  96. if (owner.getOrientation() != TabbedButtonBar::TabsAtRight) r.removeFromLeft (spaceAroundImage);
  97. if (owner.getOrientation() != TabbedButtonBar::TabsAtBottom) r.removeFromTop (spaceAroundImage);
  98. if (owner.getOrientation() != TabbedButtonBar::TabsAtTop) r.removeFromBottom (spaceAroundImage);
  99. return r;
  100. }
  101. //==============================================================================
  102. class TabbedButtonBar::BehindFrontTabComp : public Component,
  103. public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
  104. {
  105. public:
  106. BehindFrontTabComp (TabbedButtonBar& owner_)
  107. : owner (owner_)
  108. {
  109. setInterceptsMouseClicks (false, false);
  110. }
  111. void paint (Graphics& g)
  112. {
  113. getLookAndFeel().drawTabAreaBehindFrontButton (g, getWidth(), getHeight(),
  114. owner, owner.getOrientation());
  115. }
  116. void enablementChanged()
  117. {
  118. repaint();
  119. }
  120. void buttonClicked (Button*)
  121. {
  122. owner.showExtraItemsMenu();
  123. }
  124. private:
  125. TabbedButtonBar& owner;
  126. JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp);
  127. };
  128. //==============================================================================
  129. TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
  130. : orientation (orientation_),
  131. minimumScale (0.7),
  132. currentTabIndex (-1)
  133. {
  134. setInterceptsMouseClicks (false, true);
  135. addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
  136. setFocusContainer (true);
  137. }
  138. TabbedButtonBar::~TabbedButtonBar()
  139. {
  140. tabs.clear();
  141. extraTabsButton = nullptr;
  142. }
  143. //==============================================================================
  144. void TabbedButtonBar::setOrientation (const Orientation newOrientation)
  145. {
  146. orientation = newOrientation;
  147. for (int i = getNumChildComponents(); --i >= 0;)
  148. getChildComponent (i)->resized();
  149. resized();
  150. }
  151. TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
  152. {
  153. return new TabBarButton (name, *this);
  154. }
  155. void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
  156. {
  157. minimumScale = newMinimumScale;
  158. resized();
  159. }
  160. //==============================================================================
  161. void TabbedButtonBar::clearTabs()
  162. {
  163. tabs.clear();
  164. extraTabsButton = nullptr;
  165. setCurrentTabIndex (-1);
  166. }
  167. void TabbedButtonBar::addTab (const String& tabName,
  168. const Colour& tabBackgroundColour,
  169. int insertIndex)
  170. {
  171. jassert (tabName.isNotEmpty()); // you have to give them all a name..
  172. if (tabName.isNotEmpty())
  173. {
  174. if (! isPositiveAndBelow (insertIndex, tabs.size()))
  175. insertIndex = tabs.size();
  176. TabInfo* newTab = new TabInfo();
  177. newTab->name = tabName;
  178. newTab->colour = tabBackgroundColour;
  179. newTab->component = createTabButton (tabName, insertIndex);
  180. jassert (newTab->component != nullptr);
  181. tabs.insert (insertIndex, newTab);
  182. addAndMakeVisible (newTab->component, insertIndex);
  183. resized();
  184. if (currentTabIndex < 0)
  185. setCurrentTabIndex (0);
  186. }
  187. }
  188. void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
  189. {
  190. TabInfo* const tab = tabs [tabIndex];
  191. if (tab != nullptr && tab->name != newName)
  192. {
  193. tab->name = newName;
  194. tab->component->setButtonText (newName);
  195. resized();
  196. }
  197. }
  198. void TabbedButtonBar::removeTab (const int tabIndex)
  199. {
  200. if (tabs [tabIndex] != nullptr)
  201. {
  202. const int oldTabIndex = currentTabIndex;
  203. if (currentTabIndex == tabIndex)
  204. currentTabIndex = -1;
  205. tabs.remove (tabIndex);
  206. resized();
  207. setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex));
  208. }
  209. }
  210. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
  211. {
  212. tabs.move (currentIndex, newIndex);
  213. resized();
  214. }
  215. int TabbedButtonBar::getNumTabs() const
  216. {
  217. return tabs.size();
  218. }
  219. const String TabbedButtonBar::getCurrentTabName() const
  220. {
  221. TabInfo* tab = tabs [currentTabIndex];
  222. return tab == nullptr ? String::empty : tab->name;
  223. }
  224. StringArray TabbedButtonBar::getTabNames() const
  225. {
  226. StringArray names;
  227. for (int i = 0; i < tabs.size(); ++i)
  228. names.add (tabs.getUnchecked(i)->name);
  229. return names;
  230. }
  231. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  232. {
  233. if (currentTabIndex != newIndex)
  234. {
  235. if (! isPositiveAndBelow (newIndex, tabs.size()))
  236. newIndex = -1;
  237. currentTabIndex = newIndex;
  238. for (int i = 0; i < tabs.size(); ++i)
  239. {
  240. TabBarButton* tb = tabs.getUnchecked(i)->component;
  241. tb->setToggleState (i == newIndex, false);
  242. }
  243. resized();
  244. if (sendChangeMessage_)
  245. sendChangeMessage();
  246. currentTabChanged (newIndex, getCurrentTabName());
  247. }
  248. }
  249. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  250. {
  251. TabInfo* const tab = tabs[index];
  252. return tab == nullptr ? nullptr : static_cast <TabBarButton*> (tab->component);
  253. }
  254. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  255. {
  256. for (int i = tabs.size(); --i >= 0;)
  257. if (tabs.getUnchecked(i)->component == button)
  258. return i;
  259. return -1;
  260. }
  261. void TabbedButtonBar::lookAndFeelChanged()
  262. {
  263. extraTabsButton = nullptr;
  264. resized();
  265. }
  266. void TabbedButtonBar::resized()
  267. {
  268. int depth = getWidth();
  269. int length = getHeight();
  270. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  271. std::swap (depth, length);
  272. const int overlap = getLookAndFeel().getTabButtonOverlap (depth)
  273. + getLookAndFeel().getTabButtonSpaceAroundImage() * 2;
  274. int i, totalLength = overlap;
  275. int numVisibleButtons = tabs.size();
  276. for (i = 0; i < tabs.size(); ++i)
  277. {
  278. TabBarButton* const tb = tabs.getUnchecked(i)->component;
  279. totalLength += tb->getBestTabLength (depth) - overlap;
  280. tb->overlapPixels = overlap / 2;
  281. }
  282. double scale = 1.0;
  283. if (totalLength > length)
  284. scale = jmax (minimumScale, length / (double) totalLength);
  285. const bool isTooBig = totalLength * scale > length;
  286. int tabsButtonPos = 0;
  287. if (isTooBig)
  288. {
  289. if (extraTabsButton == nullptr)
  290. {
  291. addAndMakeVisible (extraTabsButton = getLookAndFeel().createTabBarExtrasButton());
  292. extraTabsButton->addListener (behindFrontTab);
  293. extraTabsButton->setAlwaysOnTop (true);
  294. extraTabsButton->setTriggeredOnMouseDown (true);
  295. }
  296. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  297. extraTabsButton->setSize (buttonSize, buttonSize);
  298. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  299. {
  300. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  301. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  302. }
  303. else
  304. {
  305. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  306. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  307. }
  308. totalLength = 0;
  309. for (i = 0; i < tabs.size(); ++i)
  310. {
  311. TabBarButton* const tb = tabs.getUnchecked(i)->component;
  312. const int newLength = totalLength + tb->getBestTabLength (depth);
  313. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  314. {
  315. totalLength += overlap;
  316. break;
  317. }
  318. numVisibleButtons = i + 1;
  319. totalLength = newLength - overlap;
  320. }
  321. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  322. }
  323. else
  324. {
  325. extraTabsButton = nullptr;
  326. }
  327. int pos = 0;
  328. TabBarButton* frontTab = nullptr;
  329. for (i = 0; i < tabs.size(); ++i)
  330. {
  331. TabBarButton* const tb = getTabButton (i);
  332. if (tb != nullptr)
  333. {
  334. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  335. if (i < numVisibleButtons)
  336. {
  337. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  338. tb->setBounds (pos, 0, bestLength, getHeight());
  339. else
  340. tb->setBounds (0, pos, getWidth(), bestLength);
  341. tb->toBack();
  342. if (i == currentTabIndex)
  343. frontTab = tb;
  344. tb->setVisible (true);
  345. }
  346. else
  347. {
  348. tb->setVisible (false);
  349. }
  350. pos += bestLength - overlap;
  351. }
  352. }
  353. behindFrontTab->setBounds (getLocalBounds());
  354. if (frontTab != nullptr)
  355. {
  356. frontTab->toFront (false);
  357. behindFrontTab->toBehind (frontTab);
  358. }
  359. }
  360. //==============================================================================
  361. const Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  362. {
  363. TabInfo* const tab = tabs [tabIndex];
  364. return tab == nullptr ? Colours::white : tab->colour;
  365. }
  366. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour)
  367. {
  368. TabInfo* const tab = tabs [tabIndex];
  369. if (tab != nullptr && tab->colour != newColour)
  370. {
  371. tab->colour = newColour;
  372. repaint();
  373. }
  374. }
  375. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  376. {
  377. if (bar != nullptr && result > 0)
  378. bar->setCurrentTabIndex (result - 1);
  379. }
  380. void TabbedButtonBar::showExtraItemsMenu()
  381. {
  382. PopupMenu m;
  383. for (int i = 0; i < tabs.size(); ++i)
  384. {
  385. const TabInfo* const tab = tabs.getUnchecked(i);
  386. if (! tab->component->isVisible())
  387. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  388. }
  389. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  390. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  391. }
  392. //==============================================================================
  393. void TabbedButtonBar::currentTabChanged (const int, const String&)
  394. {
  395. }
  396. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  397. {
  398. }
  399. END_JUCE_NAMESPACE