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.

765 lines
23KB

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