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.

724 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. static juce_wchar getDefaultPasswordChar() noexcept
  20. {
  21. #if JUCE_LINUX
  22. return 0x2022;
  23. #else
  24. return 0x25cf;
  25. #endif
  26. }
  27. //==============================================================================
  28. AlertWindow::AlertWindow (const String& title,
  29. const String& message,
  30. AlertIconType iconType,
  31. Component* comp)
  32. : TopLevelWindow (title, true),
  33. alertIconType (iconType),
  34. associatedComponent (comp),
  35. escapeKeyCancels (true)
  36. {
  37. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  38. if (message.isEmpty())
  39. text = " "; // to force an update if the message is empty
  40. setMessage (message);
  41. AlertWindow::lookAndFeelChanged();
  42. constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000);
  43. }
  44. AlertWindow::~AlertWindow()
  45. {
  46. removeAllChildren();
  47. }
  48. void AlertWindow::userTriedToCloseWindow()
  49. {
  50. if (escapeKeyCancels || buttons.size() > 0)
  51. exitModalState (0);
  52. }
  53. //==============================================================================
  54. void AlertWindow::setMessage (const String& message)
  55. {
  56. const String newMessage (message.substring (0, 2048));
  57. if (text != newMessage)
  58. {
  59. text = newMessage;
  60. updateLayout (true);
  61. repaint();
  62. }
  63. }
  64. //==============================================================================
  65. void AlertWindow::buttonClicked (Button* button)
  66. {
  67. if (Component* parent = button->getParentComponent())
  68. parent->exitModalState (button->getCommandID());
  69. }
  70. //==============================================================================
  71. void AlertWindow::addButton (const String& name,
  72. const int returnValue,
  73. const KeyPress& shortcutKey1,
  74. const KeyPress& shortcutKey2)
  75. {
  76. TextButton* const b = new TextButton (name, String());
  77. buttons.add (b);
  78. b->setWantsKeyboardFocus (true);
  79. b->setMouseClickGrabsKeyboardFocus (false);
  80. b->setCommandToTrigger (0, returnValue, false);
  81. b->addShortcut (shortcutKey1);
  82. b->addShortcut (shortcutKey2);
  83. b->addListener (this);
  84. Array<TextButton*> buttonsArray (buttons.begin(), buttons.size());
  85. const int buttonHeight = getLookAndFeel().getAlertWindowButtonHeight();
  86. const Array<int> buttonWidths = getLookAndFeel().getWidthsForTextButtons (*this, buttonsArray);
  87. jassert (buttonWidths.size() == buttons.size());
  88. const int n = buttonWidths.size();
  89. for (int i = 0; i < n; ++i)
  90. buttons.getUnchecked (i)->setSize (buttonWidths.getReference (i), buttonHeight);
  91. addAndMakeVisible (b, 0);
  92. updateLayout (false);
  93. }
  94. int AlertWindow::getNumButtons() const
  95. {
  96. return buttons.size();
  97. }
  98. void AlertWindow::triggerButtonClick (const String& buttonName)
  99. {
  100. for (int i = buttons.size(); --i >= 0;)
  101. {
  102. TextButton* const b = buttons.getUnchecked(i);
  103. if (buttonName == b->getName())
  104. {
  105. b->triggerClick();
  106. break;
  107. }
  108. }
  109. }
  110. void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
  111. {
  112. escapeKeyCancels = shouldEscapeKeyCancel;
  113. }
  114. //==============================================================================
  115. void AlertWindow::addTextEditor (const String& name,
  116. const String& initialContents,
  117. const String& onScreenLabel,
  118. const bool isPasswordBox)
  119. {
  120. TextEditor* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
  121. ed->setSelectAllWhenFocused (true);
  122. ed->setEscapeAndReturnKeysConsumed (false);
  123. textBoxes.add (ed);
  124. allComps.add (ed);
  125. ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  126. ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
  127. addAndMakeVisible (ed);
  128. ed->setText (initialContents);
  129. ed->setCaretPosition (initialContents.length());
  130. textboxNames.add (onScreenLabel);
  131. updateLayout (false);
  132. }
  133. TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
  134. {
  135. for (int i = textBoxes.size(); --i >= 0;)
  136. if (textBoxes.getUnchecked(i)->getName() == nameOfTextEditor)
  137. return textBoxes.getUnchecked(i);
  138. return nullptr;
  139. }
  140. String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
  141. {
  142. if (auto* t = getTextEditor (nameOfTextEditor))
  143. return t->getText();
  144. return {};
  145. }
  146. //==============================================================================
  147. void AlertWindow::addComboBox (const String& name,
  148. const StringArray& items,
  149. const String& onScreenLabel)
  150. {
  151. ComboBox* const cb = new ComboBox (name);
  152. comboBoxes.add (cb);
  153. allComps.add (cb);
  154. cb->addItemList (items, 1);
  155. addAndMakeVisible (cb);
  156. cb->setSelectedItemIndex (0);
  157. comboBoxNames.add (onScreenLabel);
  158. updateLayout (false);
  159. }
  160. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  161. {
  162. for (int i = comboBoxes.size(); --i >= 0;)
  163. if (comboBoxes.getUnchecked(i)->getName() == nameOfList)
  164. return comboBoxes.getUnchecked(i);
  165. return nullptr;
  166. }
  167. //==============================================================================
  168. class AlertTextComp : public TextEditor
  169. {
  170. public:
  171. AlertTextComp (AlertWindow& owner, const String& message, const Font& font)
  172. {
  173. setReadOnly (true);
  174. setMultiLine (true, true);
  175. setCaretVisible (false);
  176. setScrollbarsShown (true);
  177. lookAndFeelChanged();
  178. setWantsKeyboardFocus (false);
  179. setFont (font);
  180. setText (message, false);
  181. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  182. if (owner.isColourSpecified (AlertWindow::textColourId))
  183. setColour (TextEditor::textColourId, owner.findColour (AlertWindow::textColourId));
  184. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  185. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  186. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  187. }
  188. int getPreferredWidth() const noexcept { return bestWidth; }
  189. void updateLayout (const int width)
  190. {
  191. AttributedString s;
  192. s.setJustification (Justification::topLeft);
  193. s.append (getText(), getFont());
  194. TextLayout text;
  195. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  196. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  197. }
  198. private:
  199. int bestWidth;
  200. JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
  201. };
  202. void AlertWindow::addTextBlock (const String& textBlock)
  203. {
  204. AlertTextComp* const c = new AlertTextComp (*this, textBlock, getLookAndFeel().getAlertWindowMessageFont());
  205. textBlocks.add (c);
  206. allComps.add (c);
  207. addAndMakeVisible (c);
  208. updateLayout (false);
  209. }
  210. //==============================================================================
  211. void AlertWindow::addProgressBarComponent (double& progressValue)
  212. {
  213. ProgressBar* const pb = new ProgressBar (progressValue);
  214. progressBars.add (pb);
  215. allComps.add (pb);
  216. addAndMakeVisible (pb);
  217. updateLayout (false);
  218. }
  219. //==============================================================================
  220. void AlertWindow::addCustomComponent (Component* const component)
  221. {
  222. customComps.add (component);
  223. allComps.add (component);
  224. addAndMakeVisible (component);
  225. updateLayout (false);
  226. }
  227. int AlertWindow::getNumCustomComponents() const
  228. {
  229. return customComps.size();
  230. }
  231. Component* AlertWindow::getCustomComponent (const int index) const
  232. {
  233. return customComps [index];
  234. }
  235. Component* AlertWindow::removeCustomComponent (const int index)
  236. {
  237. Component* const c = getCustomComponent (index);
  238. if (c != nullptr)
  239. {
  240. customComps.removeFirstMatchingValue (c);
  241. allComps.removeFirstMatchingValue (c);
  242. removeChildComponent (c);
  243. updateLayout (false);
  244. }
  245. return c;
  246. }
  247. //==============================================================================
  248. void AlertWindow::paint (Graphics& g)
  249. {
  250. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  251. g.setColour (findColour (textColourId));
  252. g.setFont (getLookAndFeel().getAlertWindowFont());
  253. for (int i = textBoxes.size(); --i >= 0;)
  254. {
  255. const TextEditor* const te = textBoxes.getUnchecked(i);
  256. g.drawFittedText (textboxNames[i],
  257. te->getX(), te->getY() - 14,
  258. te->getWidth(), 14,
  259. Justification::centredLeft, 1);
  260. }
  261. for (int i = comboBoxNames.size(); --i >= 0;)
  262. {
  263. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  264. g.drawFittedText (comboBoxNames[i],
  265. cb->getX(), cb->getY() - 14,
  266. cb->getWidth(), 14,
  267. Justification::centredLeft, 1);
  268. }
  269. for (int i = customComps.size(); --i >= 0;)
  270. {
  271. const Component* const c = customComps.getUnchecked(i);
  272. g.drawFittedText (c->getName(),
  273. c->getX(), c->getY() - 14,
  274. c->getWidth(), 14,
  275. Justification::centredLeft, 1);
  276. }
  277. }
  278. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  279. {
  280. const int titleH = 24;
  281. const int iconWidth = 80;
  282. LookAndFeel& lf = getLookAndFeel();
  283. const Font messageFont (lf.getAlertWindowMessageFont());
  284. const int wid = jmax (messageFont.getStringWidth (text),
  285. messageFont.getStringWidth (getName()));
  286. const int sw = (int) std::sqrt (messageFont.getHeight() * wid);
  287. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  288. const int edgeGap = 10;
  289. const int labelHeight = 18;
  290. int iconSpace = 0;
  291. AttributedString attributedText;
  292. attributedText.append (getName(), lf.getAlertWindowTitleFont());
  293. if (text.isNotEmpty())
  294. attributedText.append ("\n\n" + text, messageFont);
  295. attributedText.setColour (findColour (textColourId));
  296. if (alertIconType == NoIcon)
  297. {
  298. attributedText.setJustification (Justification::centredTop);
  299. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  300. }
  301. else
  302. {
  303. attributedText.setJustification (Justification::topLeft);
  304. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  305. iconSpace = iconWidth;
  306. }
  307. w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
  308. w = jmin (w, (int) (getParentWidth() * 0.7f));
  309. const int textLayoutH = (int) textLayout.getHeight();
  310. const int textBottom = 16 + titleH + textLayoutH;
  311. int h = textBottom;
  312. int buttonW = 40;
  313. for (int i = 0; i < buttons.size(); ++i)
  314. buttonW += 16 + buttons.getUnchecked (i)->getWidth();
  315. w = jmax (buttonW, w);
  316. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  317. if (buttons.size() > 0)
  318. h += 20 + buttons.getUnchecked (0)->getHeight();
  319. for (int i = customComps.size(); --i >= 0;)
  320. {
  321. Component* c = customComps.getUnchecked (i);
  322. w = jmax (w, (c->getWidth() * 100) / 80);
  323. h += 10 + c->getHeight();
  324. if (c->getName().isNotEmpty())
  325. h += labelHeight;
  326. }
  327. for (int i = textBlocks.size(); --i >= 0;)
  328. {
  329. const AlertTextComp* const ac = static_cast<const AlertTextComp*> (textBlocks.getUnchecked(i));
  330. w = jmax (w, ac->getPreferredWidth());
  331. }
  332. w = jmin (w, (int) (getParentWidth() * 0.7f));
  333. for (int i = textBlocks.size(); --i >= 0;)
  334. {
  335. AlertTextComp* const ac = static_cast<AlertTextComp*> (textBlocks.getUnchecked(i));
  336. ac->updateLayout ((int) (w * 0.8f));
  337. h += ac->getHeight() + 10;
  338. }
  339. h = jmin (getParentHeight() - 50, h);
  340. if (onlyIncreaseSize)
  341. {
  342. w = jmax (w, getWidth());
  343. h = jmax (h, getHeight());
  344. }
  345. if (! isVisible())
  346. centreAroundComponent (associatedComponent, w, h);
  347. else
  348. setBounds (getBounds().withSizeKeepingCentre (w, h));
  349. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  350. const int spacer = 16;
  351. int totalWidth = -spacer;
  352. for (int i = buttons.size(); --i >= 0;)
  353. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  354. int x = (w - totalWidth) / 2;
  355. int y = (int) (getHeight() * 0.95f);
  356. for (int i = 0; i < buttons.size(); ++i)
  357. {
  358. TextButton* const c = buttons.getUnchecked(i);
  359. int ny = proportionOfHeight (0.95f) - c->getHeight();
  360. c->setTopLeftPosition (x, ny);
  361. if (ny < y)
  362. y = ny;
  363. x += c->getWidth() + spacer;
  364. c->toFront (false);
  365. }
  366. y = textBottom;
  367. for (int i = 0; i < allComps.size(); ++i)
  368. {
  369. Component* const c = allComps.getUnchecked(i);
  370. h = 22;
  371. const int comboIndex = comboBoxes.indexOf (dynamic_cast<ComboBox*> (c));
  372. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  373. y += labelHeight;
  374. const int tbIndex = textBoxes.indexOf (dynamic_cast<TextEditor*> (c));
  375. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  376. y += labelHeight;
  377. if (customComps.contains (c))
  378. {
  379. if (c->getName().isNotEmpty())
  380. y += labelHeight;
  381. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  382. h = c->getHeight();
  383. }
  384. else if (textBlocks.contains (c))
  385. {
  386. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  387. h = c->getHeight();
  388. }
  389. else
  390. {
  391. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  392. }
  393. y += h + 10;
  394. }
  395. setWantsKeyboardFocus (getNumChildComponents() == 0);
  396. }
  397. bool AlertWindow::containsAnyExtraComponents() const
  398. {
  399. return allComps.size() > 0;
  400. }
  401. //==============================================================================
  402. void AlertWindow::mouseDown (const MouseEvent& e)
  403. {
  404. dragger.startDraggingComponent (this, e);
  405. }
  406. void AlertWindow::mouseDrag (const MouseEvent& e)
  407. {
  408. dragger.dragComponent (this, e, &constrainer);
  409. }
  410. bool AlertWindow::keyPressed (const KeyPress& key)
  411. {
  412. for (int i = buttons.size(); --i >= 0;)
  413. {
  414. TextButton* const b = buttons.getUnchecked(i);
  415. if (b->isRegisteredForShortcut (key))
  416. {
  417. b->triggerClick();
  418. return true;
  419. }
  420. }
  421. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  422. {
  423. exitModalState (0);
  424. return true;
  425. }
  426. if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  427. {
  428. buttons.getUnchecked(0)->triggerClick();
  429. return true;
  430. }
  431. return false;
  432. }
  433. void AlertWindow::lookAndFeelChanged()
  434. {
  435. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  436. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  437. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  438. updateLayout (false);
  439. }
  440. int AlertWindow::getDesktopWindowStyleFlags() const
  441. {
  442. return getLookAndFeel().getAlertBoxWindowFlags();
  443. }
  444. //==============================================================================
  445. class AlertWindowInfo
  446. {
  447. public:
  448. AlertWindowInfo (const String& t, const String& m, Component* component,
  449. AlertWindow::AlertIconType icon, int numButts,
  450. ModalComponentManager::Callback* cb, bool runModally)
  451. : title (t), message (m), iconType (icon), numButtons (numButts),
  452. returnValue (0), associatedComponent (component),
  453. callback (cb), modal (runModally)
  454. {
  455. }
  456. String title, message, button1, button2, button3;
  457. int invoke() const
  458. {
  459. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  460. return returnValue;
  461. }
  462. private:
  463. AlertWindow::AlertIconType iconType;
  464. int numButtons, returnValue;
  465. WeakReference<Component> associatedComponent;
  466. ModalComponentManager::Callback* callback;
  467. bool modal;
  468. void show()
  469. {
  470. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  471. : LookAndFeel::getDefaultLookAndFeel();
  472. ScopedPointer<Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  473. iconType, numButtons, associatedComponent));
  474. jassert (alertBox != nullptr); // you have to return one of these!
  475. alertBox->setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  476. #if JUCE_MODAL_LOOPS_PERMITTED
  477. if (modal)
  478. {
  479. returnValue = alertBox->runModalLoop();
  480. }
  481. else
  482. #endif
  483. {
  484. ignoreUnused (modal);
  485. alertBox->enterModalState (true, callback, true);
  486. alertBox.release();
  487. }
  488. }
  489. static void* showCallback (void* userData)
  490. {
  491. static_cast<AlertWindowInfo*> (userData)->show();
  492. return nullptr;
  493. }
  494. };
  495. #if JUCE_MODAL_LOOPS_PERMITTED
  496. void AlertWindow::showMessageBox (AlertIconType iconType,
  497. const String& title,
  498. const String& message,
  499. const String& buttonText,
  500. Component* associatedComponent)
  501. {
  502. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  503. {
  504. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  505. }
  506. else
  507. {
  508. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, nullptr, true);
  509. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  510. info.invoke();
  511. }
  512. }
  513. #endif
  514. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  515. const String& title,
  516. const String& message,
  517. const String& buttonText,
  518. Component* associatedComponent,
  519. ModalComponentManager::Callback* callback)
  520. {
  521. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  522. {
  523. NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent, callback);
  524. }
  525. else
  526. {
  527. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, callback, false);
  528. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  529. info.invoke();
  530. }
  531. }
  532. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  533. const String& title,
  534. const String& message,
  535. const String& button1Text,
  536. const String& button2Text,
  537. Component* associatedComponent,
  538. ModalComponentManager::Callback* callback)
  539. {
  540. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  541. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  542. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  543. info.button1 = button1Text.isEmpty() ? TRANS("OK") : button1Text;
  544. info.button2 = button2Text.isEmpty() ? TRANS("Cancel") : button2Text;
  545. return info.invoke() != 0;
  546. }
  547. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  548. const String& title,
  549. const String& message,
  550. const String& button1Text,
  551. const String& button2Text,
  552. const String& button3Text,
  553. Component* associatedComponent,
  554. ModalComponentManager::Callback* callback)
  555. {
  556. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  557. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  558. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  559. info.button1 = button1Text.isEmpty() ? TRANS("Yes") : button1Text;
  560. info.button2 = button2Text.isEmpty() ? TRANS("No") : button2Text;
  561. info.button3 = button3Text.isEmpty() ? TRANS("Cancel") : button3Text;
  562. return info.invoke();
  563. }
  564. #if JUCE_MODAL_LOOPS_PERMITTED
  565. bool AlertWindow::showNativeDialogBox (const String& title,
  566. const String& bodyText,
  567. bool isOkCancel)
  568. {
  569. if (isOkCancel)
  570. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  571. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  572. return true;
  573. }
  574. #endif