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.

706 lines
23KB

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