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. TabInfo* const tab = tabs [tabIndex];
  211. if (tab != nullptr && tab->name != newName)
  212. {
  213. tab->name = newName;
  214. tab->button->setButtonText (newName);
  215. resized();
  216. }
  217. }
  218. void TabbedButtonBar::removeTab (const int tabIndex)
  219. {
  220. if (tabIndex == currentTabIndex)
  221. setCurrentTabIndex (-1);
  222. TabInfo* const currentTab = tabs [currentTabIndex];
  223. tabs.remove (tabIndex);
  224. currentTabIndex = tabs.indexOf (currentTab);
  225. resized();
  226. }
  227. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
  228. {
  229. TabInfo* const currentTab = tabs [currentTabIndex];
  230. tabs.move (currentIndex, newIndex);
  231. currentTabIndex = tabs.indexOf (currentTab);
  232. resized();
  233. }
  234. int TabbedButtonBar::getNumTabs() const
  235. {
  236. return tabs.size();
  237. }
  238. String TabbedButtonBar::getCurrentTabName() const
  239. {
  240. TabInfo* tab = tabs [currentTabIndex];
  241. return tab == nullptr ? String::empty : tab->name;
  242. }
  243. StringArray TabbedButtonBar::getTabNames() const
  244. {
  245. StringArray names;
  246. for (int i = 0; i < tabs.size(); ++i)
  247. names.add (tabs.getUnchecked(i)->name);
  248. return names;
  249. }
  250. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  251. {
  252. if (currentTabIndex != newIndex)
  253. {
  254. if (! isPositiveAndBelow (newIndex, tabs.size()))
  255. newIndex = -1;
  256. currentTabIndex = newIndex;
  257. for (int i = 0; i < tabs.size(); ++i)
  258. {
  259. TabBarButton* tb = tabs.getUnchecked(i)->button;
  260. tb->setToggleState (i == newIndex, false);
  261. }
  262. resized();
  263. if (sendChangeMessage_)
  264. sendChangeMessage();
  265. currentTabChanged (newIndex, getCurrentTabName());
  266. }
  267. }
  268. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  269. {
  270. TabInfo* const tab = tabs[index];
  271. return tab == nullptr ? nullptr : static_cast <TabBarButton*> (tab->button);
  272. }
  273. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  274. {
  275. for (int i = tabs.size(); --i >= 0;)
  276. if (tabs.getUnchecked(i)->button == button)
  277. return i;
  278. return -1;
  279. }
  280. void TabbedButtonBar::lookAndFeelChanged()
  281. {
  282. extraTabsButton = nullptr;
  283. resized();
  284. }
  285. void TabbedButtonBar::resized()
  286. {
  287. LookAndFeel& lf = getLookAndFeel();
  288. int depth = getWidth();
  289. int length = getHeight();
  290. if (! isVertical())
  291. std::swap (depth, length);
  292. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  293. int totalLength = jmax (0, overlap);
  294. int numVisibleButtons = tabs.size();
  295. for (int i = 0; i < tabs.size(); ++i)
  296. {
  297. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  298. totalLength += tb->getBestTabLength (depth) - overlap;
  299. tb->overlapPixels = jmax (0, overlap / 2);
  300. }
  301. double scale = 1.0;
  302. if (totalLength > length)
  303. scale = jmax (minimumScale, length / (double) totalLength);
  304. const bool isTooBig = (int) (totalLength * scale) > length;
  305. int tabsButtonPos = 0;
  306. if (isTooBig)
  307. {
  308. if (extraTabsButton == nullptr)
  309. {
  310. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  311. extraTabsButton->addListener (behindFrontTab);
  312. extraTabsButton->setAlwaysOnTop (true);
  313. extraTabsButton->setTriggeredOnMouseDown (true);
  314. }
  315. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  316. extraTabsButton->setSize (buttonSize, buttonSize);
  317. if (isVertical())
  318. {
  319. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  320. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  321. }
  322. else
  323. {
  324. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  325. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  326. }
  327. totalLength = 0;
  328. for (int i = 0; i < tabs.size(); ++i)
  329. {
  330. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  331. const int newLength = totalLength + tb->getBestTabLength (depth);
  332. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  333. {
  334. totalLength += overlap;
  335. break;
  336. }
  337. numVisibleButtons = i + 1;
  338. totalLength = newLength - overlap;
  339. }
  340. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  341. }
  342. else
  343. {
  344. extraTabsButton = nullptr;
  345. }
  346. int pos = 0;
  347. TabBarButton* frontTab = nullptr;
  348. for (int i = 0; i < tabs.size(); ++i)
  349. {
  350. TabBarButton* const tb = getTabButton (i);
  351. if (tb != nullptr)
  352. {
  353. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  354. if (i < numVisibleButtons)
  355. {
  356. if (isVertical())
  357. tb->setBounds (0, pos, getWidth(), bestLength);
  358. else
  359. tb->setBounds (pos, 0, bestLength, getHeight());
  360. tb->toBack();
  361. if (i == currentTabIndex)
  362. frontTab = tb;
  363. tb->setVisible (true);
  364. }
  365. else
  366. {
  367. tb->setVisible (false);
  368. }
  369. pos += bestLength - overlap;
  370. }
  371. }
  372. behindFrontTab->setBounds (getLocalBounds());
  373. if (frontTab != nullptr)
  374. {
  375. frontTab->toFront (false);
  376. behindFrontTab->toBehind (frontTab);
  377. }
  378. }
  379. //==============================================================================
  380. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  381. {
  382. TabInfo* const tab = tabs [tabIndex];
  383. return tab == nullptr ? Colours::white : tab->colour;
  384. }
  385. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, const Colour& newColour)
  386. {
  387. TabInfo* const tab = tabs [tabIndex];
  388. if (tab != nullptr && tab->colour != newColour)
  389. {
  390. tab->colour = newColour;
  391. repaint();
  392. }
  393. }
  394. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  395. {
  396. if (bar != nullptr && result > 0)
  397. bar->setCurrentTabIndex (result - 1);
  398. }
  399. void TabbedButtonBar::showExtraItemsMenu()
  400. {
  401. PopupMenu m;
  402. for (int i = 0; i < tabs.size(); ++i)
  403. {
  404. const TabInfo* const tab = tabs.getUnchecked(i);
  405. if (! tab->button->isVisible())
  406. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  407. }
  408. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  409. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  410. }
  411. //==============================================================================
  412. void TabbedButtonBar::currentTabChanged (const int, const String&)
  413. {
  414. }
  415. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  416. {
  417. }