Audio plugin host https://kx.studio/carla
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.

708 lines
22KB

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