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.

709 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->changeWidthToFitText (getLookAndFeel().getAlertWindowButtonHeight());
  83. addAndMakeVisible (b, 0);
  84. updateLayout (false);
  85. }
  86. int AlertWindow::getNumButtons() const
  87. {
  88. return buttons.size();
  89. }
  90. void AlertWindow::triggerButtonClick (const String& buttonName)
  91. {
  92. for (int i = buttons.size(); --i >= 0;)
  93. {
  94. TextButton* const b = buttons.getUnchecked(i);
  95. if (buttonName == b->getName())
  96. {
  97. b->triggerClick();
  98. break;
  99. }
  100. }
  101. }
  102. void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
  103. {
  104. escapeKeyCancels = shouldEscapeKeyCancel;
  105. }
  106. //==============================================================================
  107. void AlertWindow::addTextEditor (const String& name,
  108. const String& initialContents,
  109. const String& onScreenLabel,
  110. const bool isPasswordBox)
  111. {
  112. TextEditor* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
  113. ed->setSelectAllWhenFocused (true);
  114. ed->setEscapeAndReturnKeysConsumed (false);
  115. textBoxes.add (ed);
  116. allComps.add (ed);
  117. ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  118. ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
  119. addAndMakeVisible (ed);
  120. ed->setText (initialContents);
  121. ed->setCaretPosition (initialContents.length());
  122. textboxNames.add (onScreenLabel);
  123. updateLayout (false);
  124. }
  125. TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
  126. {
  127. for (int i = textBoxes.size(); --i >= 0;)
  128. if (textBoxes.getUnchecked(i)->getName() == nameOfTextEditor)
  129. return textBoxes.getUnchecked(i);
  130. return nullptr;
  131. }
  132. String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
  133. {
  134. if (TextEditor* const t = getTextEditor (nameOfTextEditor))
  135. return t->getText();
  136. return String();
  137. }
  138. //==============================================================================
  139. void AlertWindow::addComboBox (const String& name,
  140. const StringArray& items,
  141. const String& onScreenLabel)
  142. {
  143. ComboBox* const cb = new ComboBox (name);
  144. comboBoxes.add (cb);
  145. allComps.add (cb);
  146. cb->addItemList (items, 1);
  147. addAndMakeVisible (cb);
  148. cb->setSelectedItemIndex (0);
  149. comboBoxNames.add (onScreenLabel);
  150. updateLayout (false);
  151. }
  152. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  153. {
  154. for (int i = comboBoxes.size(); --i >= 0;)
  155. if (comboBoxes.getUnchecked(i)->getName() == nameOfList)
  156. return comboBoxes.getUnchecked(i);
  157. return nullptr;
  158. }
  159. //==============================================================================
  160. class AlertTextComp : public TextEditor
  161. {
  162. public:
  163. AlertTextComp (AlertWindow& owner, const String& message, const Font& font)
  164. {
  165. setReadOnly (true);
  166. setMultiLine (true, true);
  167. setCaretVisible (false);
  168. setScrollbarsShown (true);
  169. lookAndFeelChanged();
  170. setWantsKeyboardFocus (false);
  171. setFont (font);
  172. setText (message, false);
  173. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  174. if (owner.isColourSpecified (AlertWindow::textColourId))
  175. setColour (TextEditor::textColourId, owner.findColour (AlertWindow::textColourId));
  176. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  177. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  178. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  179. }
  180. int getPreferredWidth() const noexcept { return bestWidth; }
  181. void updateLayout (const int width)
  182. {
  183. AttributedString s;
  184. s.setJustification (Justification::topLeft);
  185. s.append (getText(), getFont());
  186. TextLayout text;
  187. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  188. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  189. }
  190. private:
  191. int bestWidth;
  192. JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
  193. };
  194. void AlertWindow::addTextBlock (const String& textBlock)
  195. {
  196. AlertTextComp* const c = new AlertTextComp (*this, textBlock, getLookAndFeel().getAlertWindowMessageFont());
  197. textBlocks.add (c);
  198. allComps.add (c);
  199. addAndMakeVisible (c);
  200. updateLayout (false);
  201. }
  202. //==============================================================================
  203. void AlertWindow::addProgressBarComponent (double& progressValue)
  204. {
  205. ProgressBar* const pb = new ProgressBar (progressValue);
  206. progressBars.add (pb);
  207. allComps.add (pb);
  208. addAndMakeVisible (pb);
  209. updateLayout (false);
  210. }
  211. //==============================================================================
  212. void AlertWindow::addCustomComponent (Component* const component)
  213. {
  214. customComps.add (component);
  215. allComps.add (component);
  216. addAndMakeVisible (component);
  217. updateLayout (false);
  218. }
  219. int AlertWindow::getNumCustomComponents() const
  220. {
  221. return customComps.size();
  222. }
  223. Component* AlertWindow::getCustomComponent (const int index) const
  224. {
  225. return customComps [index];
  226. }
  227. Component* AlertWindow::removeCustomComponent (const int index)
  228. {
  229. Component* const c = getCustomComponent (index);
  230. if (c != nullptr)
  231. {
  232. customComps.removeFirstMatchingValue (c);
  233. allComps.removeFirstMatchingValue (c);
  234. removeChildComponent (c);
  235. updateLayout (false);
  236. }
  237. return c;
  238. }
  239. //==============================================================================
  240. void AlertWindow::paint (Graphics& g)
  241. {
  242. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  243. g.setColour (findColour (textColourId));
  244. g.setFont (getLookAndFeel().getAlertWindowFont());
  245. for (int i = textBoxes.size(); --i >= 0;)
  246. {
  247. const TextEditor* const te = textBoxes.getUnchecked(i);
  248. g.drawFittedText (textboxNames[i],
  249. te->getX(), te->getY() - 14,
  250. te->getWidth(), 14,
  251. Justification::centredLeft, 1);
  252. }
  253. for (int i = comboBoxNames.size(); --i >= 0;)
  254. {
  255. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  256. g.drawFittedText (comboBoxNames[i],
  257. cb->getX(), cb->getY() - 14,
  258. cb->getWidth(), 14,
  259. Justification::centredLeft, 1);
  260. }
  261. for (int i = customComps.size(); --i >= 0;)
  262. {
  263. const Component* const c = customComps.getUnchecked(i);
  264. g.drawFittedText (c->getName(),
  265. c->getX(), c->getY() - 14,
  266. c->getWidth(), 14,
  267. Justification::centredLeft, 1);
  268. }
  269. }
  270. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  271. {
  272. const int titleH = 24;
  273. const int iconWidth = 80;
  274. LookAndFeel& lf = getLookAndFeel();
  275. const Font messageFont (lf.getAlertWindowMessageFont());
  276. const int wid = jmax (messageFont.getStringWidth (text),
  277. messageFont.getStringWidth (getName()));
  278. const int sw = (int) std::sqrt (messageFont.getHeight() * wid);
  279. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  280. const int edgeGap = 10;
  281. const int labelHeight = 18;
  282. int iconSpace = 0;
  283. AttributedString attributedText;
  284. attributedText.append (getName(), lf.getAlertWindowTitleFont());
  285. if (text.isNotEmpty())
  286. attributedText.append ("\n\n" + text, messageFont);
  287. attributedText.setColour (findColour (textColourId));
  288. if (alertIconType == NoIcon)
  289. {
  290. attributedText.setJustification (Justification::centredTop);
  291. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  292. }
  293. else
  294. {
  295. attributedText.setJustification (Justification::topLeft);
  296. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  297. iconSpace = iconWidth;
  298. }
  299. w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
  300. w = jmin (w, (int) (getParentWidth() * 0.7f));
  301. const int textLayoutH = (int) textLayout.getHeight();
  302. const int textBottom = 16 + titleH + textLayoutH;
  303. int h = textBottom;
  304. int buttonW = 40;
  305. for (int i = 0; i < buttons.size(); ++i)
  306. buttonW += 16 + buttons.getUnchecked (i)->getWidth();
  307. w = jmax (buttonW, w);
  308. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  309. if (buttons.size() > 0)
  310. h += 20 + buttons.getUnchecked (0)->getHeight();
  311. for (int i = customComps.size(); --i >= 0;)
  312. {
  313. Component* c = customComps.getUnchecked (i);
  314. w = jmax (w, (c->getWidth() * 100) / 80);
  315. h += 10 + c->getHeight();
  316. if (c->getName().isNotEmpty())
  317. h += labelHeight;
  318. }
  319. for (int i = textBlocks.size(); --i >= 0;)
  320. {
  321. const AlertTextComp* const ac = static_cast<const AlertTextComp*> (textBlocks.getUnchecked(i));
  322. w = jmax (w, ac->getPreferredWidth());
  323. }
  324. w = jmin (w, (int) (getParentWidth() * 0.7f));
  325. for (int i = textBlocks.size(); --i >= 0;)
  326. {
  327. AlertTextComp* const ac = static_cast<AlertTextComp*> (textBlocks.getUnchecked(i));
  328. ac->updateLayout ((int) (w * 0.8f));
  329. h += ac->getHeight() + 10;
  330. }
  331. h = jmin (getParentHeight() - 50, h);
  332. if (onlyIncreaseSize)
  333. {
  334. w = jmax (w, getWidth());
  335. h = jmax (h, getHeight());
  336. }
  337. if (! isVisible())
  338. centreAroundComponent (associatedComponent, w, h);
  339. else
  340. setBounds (getBounds().withSizeKeepingCentre (w, h));
  341. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  342. const int spacer = 16;
  343. int totalWidth = -spacer;
  344. for (int i = buttons.size(); --i >= 0;)
  345. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  346. int x = (w - totalWidth) / 2;
  347. int y = (int) (getHeight() * 0.95f);
  348. for (int i = 0; i < buttons.size(); ++i)
  349. {
  350. TextButton* const c = buttons.getUnchecked(i);
  351. int ny = proportionOfHeight (0.95f) - c->getHeight();
  352. c->setTopLeftPosition (x, ny);
  353. if (ny < y)
  354. y = ny;
  355. x += c->getWidth() + spacer;
  356. c->toFront (false);
  357. }
  358. y = textBottom;
  359. for (int i = 0; i < allComps.size(); ++i)
  360. {
  361. Component* const c = allComps.getUnchecked(i);
  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 (int i = buttons.size(); --i >= 0;)
  405. {
  406. TextButton* const b = buttons.getUnchecked(i);
  407. if (b->isRegisteredForShortcut (key))
  408. {
  409. b->triggerClick();
  410. return true;
  411. }
  412. }
  413. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  414. {
  415. exitModalState (0);
  416. return true;
  417. }
  418. if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  419. {
  420. buttons.getUnchecked(0)->triggerClick();
  421. return true;
  422. }
  423. return false;
  424. }
  425. void AlertWindow::lookAndFeelChanged()
  426. {
  427. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  428. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  429. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  430. updateLayout (false);
  431. }
  432. int AlertWindow::getDesktopWindowStyleFlags() const
  433. {
  434. return getLookAndFeel().getAlertBoxWindowFlags();
  435. }
  436. //==============================================================================
  437. class AlertWindowInfo
  438. {
  439. public:
  440. AlertWindowInfo (const String& t, const String& m, Component* component,
  441. AlertWindow::AlertIconType icon, int numButts,
  442. ModalComponentManager::Callback* cb, bool runModally)
  443. : title (t), message (m), iconType (icon), numButtons (numButts),
  444. returnValue (0), associatedComponent (component),
  445. callback (cb), modal (runModally)
  446. {
  447. }
  448. String title, message, button1, button2, button3;
  449. int invoke() const
  450. {
  451. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  452. return returnValue;
  453. }
  454. private:
  455. AlertWindow::AlertIconType iconType;
  456. int numButtons, returnValue;
  457. WeakReference<Component> associatedComponent;
  458. ModalComponentManager::Callback* callback;
  459. bool modal;
  460. void show()
  461. {
  462. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  463. : LookAndFeel::getDefaultLookAndFeel();
  464. ScopedPointer<Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  465. iconType, numButtons, associatedComponent));
  466. jassert (alertBox != nullptr); // you have to return one of these!
  467. #if JUCE_MODAL_LOOPS_PERMITTED
  468. if (modal)
  469. {
  470. returnValue = alertBox->runModalLoop();
  471. }
  472. else
  473. #endif
  474. {
  475. ignoreUnused (modal);
  476. alertBox->enterModalState (true, callback, true);
  477. alertBox.release();
  478. }
  479. }
  480. static void* showCallback (void* userData)
  481. {
  482. static_cast<AlertWindowInfo*> (userData)->show();
  483. return nullptr;
  484. }
  485. };
  486. #if JUCE_MODAL_LOOPS_PERMITTED
  487. void AlertWindow::showMessageBox (AlertIconType iconType,
  488. const String& title,
  489. const String& message,
  490. const String& buttonText,
  491. Component* associatedComponent)
  492. {
  493. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  494. {
  495. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  496. }
  497. else
  498. {
  499. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, nullptr, true);
  500. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  501. info.invoke();
  502. }
  503. }
  504. #endif
  505. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  506. const String& title,
  507. const String& message,
  508. const String& buttonText,
  509. Component* associatedComponent,
  510. ModalComponentManager::Callback* callback)
  511. {
  512. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  513. {
  514. NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent, callback);
  515. }
  516. else
  517. {
  518. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, callback, false);
  519. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  520. info.invoke();
  521. }
  522. }
  523. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  524. const String& title,
  525. const String& message,
  526. const String& button1Text,
  527. const String& button2Text,
  528. Component* associatedComponent,
  529. ModalComponentManager::Callback* callback)
  530. {
  531. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  532. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  533. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  534. info.button1 = button1Text.isEmpty() ? TRANS("OK") : button1Text;
  535. info.button2 = button2Text.isEmpty() ? TRANS("Cancel") : button2Text;
  536. return info.invoke() != 0;
  537. }
  538. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  539. const String& title,
  540. const String& message,
  541. const String& button1Text,
  542. const String& button2Text,
  543. const String& button3Text,
  544. Component* associatedComponent,
  545. ModalComponentManager::Callback* callback)
  546. {
  547. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  548. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  549. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  550. info.button1 = button1Text.isEmpty() ? TRANS("Yes") : button1Text;
  551. info.button2 = button2Text.isEmpty() ? TRANS("No") : button2Text;
  552. info.button3 = button3Text.isEmpty() ? TRANS("Cancel") : button3Text;
  553. return info.invoke();
  554. }
  555. #if JUCE_MODAL_LOOPS_PERMITTED
  556. bool AlertWindow::showNativeDialogBox (const String& title,
  557. const String& bodyText,
  558. bool isOkCancel)
  559. {
  560. if (isOkCancel)
  561. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  562. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  563. return true;
  564. }
  565. #endif