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.

551 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. {
  128. owner.resized();
  129. resized();
  130. }
  131. }
  132. void TabBarButton::resized()
  133. {
  134. if (extraComponent != nullptr)
  135. {
  136. Rectangle<int> extraComp, text;
  137. calcAreas (extraComp, text);
  138. if (! extraComp.isEmpty())
  139. extraComponent->setBounds (extraComp);
  140. }
  141. }
  142. //==============================================================================
  143. class TabbedButtonBar::BehindFrontTabComp : public Component,
  144. public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
  145. {
  146. public:
  147. BehindFrontTabComp (TabbedButtonBar& owner_)
  148. : owner (owner_)
  149. {
  150. setInterceptsMouseClicks (false, false);
  151. }
  152. void paint (Graphics& g)
  153. {
  154. getLookAndFeel().drawTabAreaBehindFrontButton (owner, g, getWidth(), getHeight());
  155. }
  156. void enablementChanged()
  157. {
  158. repaint();
  159. }
  160. void buttonClicked (Button*)
  161. {
  162. owner.showExtraItemsMenu();
  163. }
  164. private:
  165. TabbedButtonBar& owner;
  166. JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp);
  167. };
  168. //==============================================================================
  169. TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
  170. : orientation (orientation_),
  171. minimumScale (0.7),
  172. currentTabIndex (-1)
  173. {
  174. setInterceptsMouseClicks (false, true);
  175. addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
  176. setFocusContainer (true);
  177. }
  178. TabbedButtonBar::~TabbedButtonBar()
  179. {
  180. tabs.clear();
  181. extraTabsButton = nullptr;
  182. }
  183. //==============================================================================
  184. void TabbedButtonBar::setOrientation (const Orientation newOrientation)
  185. {
  186. orientation = newOrientation;
  187. for (int i = getNumChildComponents(); --i >= 0;)
  188. getChildComponent (i)->resized();
  189. resized();
  190. }
  191. TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
  192. {
  193. return new TabBarButton (name, *this);
  194. }
  195. void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
  196. {
  197. minimumScale = newMinimumScale;
  198. resized();
  199. }
  200. //==============================================================================
  201. void TabbedButtonBar::clearTabs()
  202. {
  203. tabs.clear();
  204. extraTabsButton = nullptr;
  205. setCurrentTabIndex (-1);
  206. }
  207. void TabbedButtonBar::addTab (const String& tabName,
  208. const Colour& tabBackgroundColour,
  209. int insertIndex)
  210. {
  211. jassert (tabName.isNotEmpty()); // you have to give them all a name..
  212. if (tabName.isNotEmpty())
  213. {
  214. if (! isPositiveAndBelow (insertIndex, tabs.size()))
  215. insertIndex = tabs.size();
  216. TabInfo* const currentTab = tabs [currentTabIndex];
  217. TabInfo* newTab = new TabInfo();
  218. newTab->name = tabName;
  219. newTab->colour = tabBackgroundColour;
  220. newTab->button = createTabButton (tabName, insertIndex);
  221. jassert (newTab->button != nullptr);
  222. tabs.insert (insertIndex, newTab);
  223. currentTabIndex = tabs.indexOf (currentTab);
  224. addAndMakeVisible (newTab->button, insertIndex);
  225. resized();
  226. if (currentTabIndex < 0)
  227. setCurrentTabIndex (0);
  228. }
  229. }
  230. void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
  231. {
  232. TabInfo* const tab = tabs [tabIndex];
  233. if (tab != nullptr && tab->name != newName)
  234. {
  235. tab->name = newName;
  236. tab->button->setButtonText (newName);
  237. resized();
  238. }
  239. }
  240. void TabbedButtonBar::removeTab (const int tabIndex)
  241. {
  242. if (tabIndex == currentTabIndex)
  243. setCurrentTabIndex (-1);
  244. TabInfo* const currentTab = tabs [currentTabIndex];
  245. tabs.remove (tabIndex);
  246. currentTabIndex = tabs.indexOf (currentTab);
  247. resized();
  248. }
  249. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
  250. {
  251. TabInfo* const currentTab = tabs [currentTabIndex];
  252. tabs.move (currentIndex, newIndex);
  253. currentTabIndex = tabs.indexOf (currentTab);
  254. resized();
  255. }
  256. int TabbedButtonBar::getNumTabs() const
  257. {
  258. return tabs.size();
  259. }
  260. String TabbedButtonBar::getCurrentTabName() const
  261. {
  262. TabInfo* tab = tabs [currentTabIndex];
  263. return tab == nullptr ? String::empty : tab->name;
  264. }
  265. StringArray TabbedButtonBar::getTabNames() const
  266. {
  267. StringArray names;
  268. for (int i = 0; i < tabs.size(); ++i)
  269. names.add (tabs.getUnchecked(i)->name);
  270. return names;
  271. }
  272. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  273. {
  274. if (currentTabIndex != newIndex)
  275. {
  276. if (! isPositiveAndBelow (newIndex, tabs.size()))
  277. newIndex = -1;
  278. currentTabIndex = newIndex;
  279. for (int i = 0; i < tabs.size(); ++i)
  280. {
  281. TabBarButton* tb = tabs.getUnchecked(i)->button;
  282. tb->setToggleState (i == newIndex, false);
  283. }
  284. resized();
  285. if (sendChangeMessage_)
  286. sendChangeMessage();
  287. currentTabChanged (newIndex, getCurrentTabName());
  288. }
  289. }
  290. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  291. {
  292. TabInfo* const tab = tabs[index];
  293. return tab == nullptr ? nullptr : static_cast <TabBarButton*> (tab->button);
  294. }
  295. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  296. {
  297. for (int i = tabs.size(); --i >= 0;)
  298. if (tabs.getUnchecked(i)->button == button)
  299. return i;
  300. return -1;
  301. }
  302. void TabbedButtonBar::lookAndFeelChanged()
  303. {
  304. extraTabsButton = nullptr;
  305. resized();
  306. }
  307. void TabbedButtonBar::resized()
  308. {
  309. LookAndFeel& lf = getLookAndFeel();
  310. int depth = getWidth();
  311. int length = getHeight();
  312. if (! isVertical())
  313. std::swap (depth, length);
  314. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  315. int totalLength = overlap;
  316. int numVisibleButtons = tabs.size();
  317. for (int i = 0; i < tabs.size(); ++i)
  318. {
  319. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  320. totalLength += tb->getBestTabLength (depth) - overlap;
  321. tb->overlapPixels = overlap / 2;
  322. }
  323. double scale = 1.0;
  324. if (totalLength > length)
  325. scale = jmax (minimumScale, length / (double) totalLength);
  326. const bool isTooBig = totalLength * scale > length;
  327. int tabsButtonPos = 0;
  328. if (isTooBig)
  329. {
  330. if (extraTabsButton == nullptr)
  331. {
  332. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  333. extraTabsButton->addListener (behindFrontTab);
  334. extraTabsButton->setAlwaysOnTop (true);
  335. extraTabsButton->setTriggeredOnMouseDown (true);
  336. }
  337. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  338. extraTabsButton->setSize (buttonSize, buttonSize);
  339. if (isVertical())
  340. {
  341. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  342. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  343. }
  344. else
  345. {
  346. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  347. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  348. }
  349. totalLength = 0;
  350. for (int i = 0; i < tabs.size(); ++i)
  351. {
  352. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  353. const int newLength = totalLength + tb->getBestTabLength (depth);
  354. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  355. {
  356. totalLength += overlap;
  357. break;
  358. }
  359. numVisibleButtons = i + 1;
  360. totalLength = newLength - overlap;
  361. }
  362. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  363. }
  364. else
  365. {
  366. extraTabsButton = nullptr;
  367. }
  368. int pos = 0;
  369. TabBarButton* frontTab = nullptr;
  370. for (int i = 0; i < tabs.size(); ++i)
  371. {
  372. TabBarButton* const tb = getTabButton (i);
  373. if (tb != nullptr)
  374. {
  375. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  376. if (i < numVisibleButtons)
  377. {
  378. if (isVertical())
  379. tb->setBounds (0, pos, getWidth(), bestLength);
  380. else
  381. tb->setBounds (pos, 0, bestLength, getHeight());
  382. tb->toBack();
  383. if (i == currentTabIndex)
  384. frontTab = tb;
  385. tb->setVisible (true);
  386. }
  387. else
  388. {
  389. tb->setVisible (false);
  390. }
  391. pos += bestLength - overlap;
  392. }
  393. }
  394. behindFrontTab->setBounds (getLocalBounds());
  395. if (frontTab != nullptr)
  396. {
  397. frontTab->toFront (false);
  398. behindFrontTab->toBehind (frontTab);
  399. }
  400. }
  401. //==============================================================================
  402. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  403. {
  404. TabInfo* const tab = tabs [tabIndex];
  405. return tab == nullptr ? Colours::white : tab->colour;
  406. }
  407. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour)
  408. {
  409. TabInfo* const tab = tabs [tabIndex];
  410. if (tab != nullptr && tab->colour != newColour)
  411. {
  412. tab->colour = newColour;
  413. repaint();
  414. }
  415. }
  416. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  417. {
  418. if (bar != nullptr && result > 0)
  419. bar->setCurrentTabIndex (result - 1);
  420. }
  421. void TabbedButtonBar::showExtraItemsMenu()
  422. {
  423. PopupMenu m;
  424. for (int i = 0; i < tabs.size(); ++i)
  425. {
  426. const TabInfo* const tab = tabs.getUnchecked(i);
  427. if (! tab->button->isVisible())
  428. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  429. }
  430. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  431. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  432. }
  433. //==============================================================================
  434. void TabbedButtonBar::currentTabChanged (const int, const String&)
  435. {
  436. }
  437. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  438. {
  439. }