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.

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