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