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.

554 lines
16KB

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