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.

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