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.

497 lines
14KB

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