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.

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