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.

722 lines
23KB

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