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.

498 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* 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. addAndMakeVisible (newTab->component, insertIndex);
  177. resized();
  178. if (currentTabIndex < 0)
  179. setCurrentTabIndex (0);
  180. }
  181. }
  182. void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
  183. {
  184. TabInfo* const tab = tabs [tabIndex];
  185. if (tab != nullptr && tab->name != newName)
  186. {
  187. tab->name = newName;
  188. tab->component->setButtonText (newName);
  189. resized();
  190. }
  191. }
  192. void TabbedButtonBar::removeTab (const int tabIndex)
  193. {
  194. if (tabs [tabIndex] != nullptr)
  195. {
  196. const int oldTabIndex = currentTabIndex;
  197. if (currentTabIndex == tabIndex)
  198. currentTabIndex = -1;
  199. tabs.remove (tabIndex);
  200. resized();
  201. setCurrentTabIndex (jlimit (0, jmax (0, tabs.size() - 1), oldTabIndex));
  202. }
  203. }
  204. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
  205. {
  206. tabs.move (currentIndex, newIndex);
  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. int depth = getWidth();
  263. int length = getHeight();
  264. if (orientation == TabsAtTop || orientation == TabsAtBottom)
  265. std::swap (depth, length);
  266. const int overlap = getLookAndFeel().getTabButtonOverlap (depth)
  267. + getLookAndFeel().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 = getLookAndFeel().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. }