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.

713 lines
22KB

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