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.

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