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.

716 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 (const String& message,
  164. const Font& font)
  165. {
  166. setReadOnly (true);
  167. setMultiLine (true, true);
  168. setCaretVisible (false);
  169. setScrollbarsShown (true);
  170. lookAndFeelChanged();
  171. setWantsKeyboardFocus (false);
  172. setFont (font);
  173. setText (message, false);
  174. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  175. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  176. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  177. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  178. }
  179. int getPreferredWidth() const noexcept { return bestWidth; }
  180. void updateLayout (const int width)
  181. {
  182. AttributedString s;
  183. s.setJustification (Justification::topLeft);
  184. s.append (getText(), getFont());
  185. TextLayout text;
  186. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  187. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  188. }
  189. private:
  190. int bestWidth;
  191. JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
  192. };
  193. void AlertWindow::addTextBlock (const String& textBlock)
  194. {
  195. AlertTextComp* const c = new AlertTextComp (textBlock, getLookAndFeel().getAlertWindowMessageFont());
  196. textBlocks.add (c);
  197. allComps.add (c);
  198. addAndMakeVisible (c);
  199. updateLayout (false);
  200. }
  201. //==============================================================================
  202. void AlertWindow::addProgressBarComponent (double& progressValue)
  203. {
  204. ProgressBar* const pb = new ProgressBar (progressValue);
  205. progressBars.add (pb);
  206. allComps.add (pb);
  207. addAndMakeVisible (pb);
  208. updateLayout (false);
  209. }
  210. //==============================================================================
  211. void AlertWindow::addCustomComponent (Component* const component)
  212. {
  213. customComps.add (component);
  214. allComps.add (component);
  215. addAndMakeVisible (component);
  216. updateLayout (false);
  217. }
  218. int AlertWindow::getNumCustomComponents() const
  219. {
  220. return customComps.size();
  221. }
  222. Component* AlertWindow::getCustomComponent (const int index) const
  223. {
  224. return customComps [index];
  225. }
  226. Component* AlertWindow::removeCustomComponent (const int index)
  227. {
  228. Component* const c = getCustomComponent (index);
  229. if (c != nullptr)
  230. {
  231. customComps.removeFirstMatchingValue (c);
  232. allComps.removeFirstMatchingValue (c);
  233. removeChildComponent (c);
  234. updateLayout (false);
  235. }
  236. return c;
  237. }
  238. //==============================================================================
  239. void AlertWindow::paint (Graphics& g)
  240. {
  241. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  242. g.setColour (findColour (textColourId));
  243. g.setFont (getLookAndFeel().getAlertWindowFont());
  244. for (int i = textBoxes.size(); --i >= 0;)
  245. {
  246. const TextEditor* const te = textBoxes.getUnchecked(i);
  247. g.drawFittedText (textboxNames[i],
  248. te->getX(), te->getY() - 14,
  249. te->getWidth(), 14,
  250. Justification::centredLeft, 1);
  251. }
  252. for (int i = comboBoxNames.size(); --i >= 0;)
  253. {
  254. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  255. g.drawFittedText (comboBoxNames[i],
  256. cb->getX(), cb->getY() - 14,
  257. cb->getWidth(), 14,
  258. Justification::centredLeft, 1);
  259. }
  260. for (int i = customComps.size(); --i >= 0;)
  261. {
  262. const Component* const c = customComps.getUnchecked(i);
  263. g.drawFittedText (c->getName(),
  264. c->getX(), c->getY() - 14,
  265. c->getWidth(), 14,
  266. Justification::centredLeft, 1);
  267. }
  268. }
  269. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  270. {
  271. const int titleH = 24;
  272. const int iconWidth = 80;
  273. LookAndFeel& lf = getLookAndFeel();
  274. const Font messageFont (lf.getAlertWindowMessageFont());
  275. const int wid = jmax (messageFont.getStringWidth (text),
  276. messageFont.getStringWidth (getName()));
  277. const int sw = (int) std::sqrt (messageFont.getHeight() * wid);
  278. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  279. const int edgeGap = 10;
  280. const int labelHeight = 18;
  281. int iconSpace = 0;
  282. AttributedString attributedText;
  283. attributedText.append (getName(), lf.getAlertWindowTitleFont());
  284. if (text.isNotEmpty())
  285. attributedText.append ("\n\n" + text, messageFont);
  286. attributedText.setColour (findColour (textColourId));
  287. if (alertIconType == NoIcon)
  288. {
  289. attributedText.setJustification (Justification::centredTop);
  290. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  291. }
  292. else
  293. {
  294. attributedText.setJustification (Justification::topLeft);
  295. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  296. iconSpace = iconWidth;
  297. }
  298. w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
  299. w = jmin (w, (int) (getParentWidth() * 0.7f));
  300. const int textLayoutH = (int) textLayout.getHeight();
  301. const int textBottom = 16 + titleH + textLayoutH;
  302. int h = textBottom;
  303. int buttonW = 40;
  304. for (int i = 0; i < buttons.size(); ++i)
  305. buttonW += 16 + buttons.getUnchecked (i)->getWidth();
  306. w = jmax (buttonW, w);
  307. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  308. if (buttons.size() > 0)
  309. h += 20 + buttons.getUnchecked (0)->getHeight();
  310. for (int i = customComps.size(); --i >= 0;)
  311. {
  312. Component* c = customComps.getUnchecked (i);
  313. w = jmax (w, (c->getWidth() * 100) / 80);
  314. h += 10 + c->getHeight();
  315. if (c->getName().isNotEmpty())
  316. h += labelHeight;
  317. }
  318. for (int i = textBlocks.size(); --i >= 0;)
  319. {
  320. const AlertTextComp* const ac = static_cast<const AlertTextComp*> (textBlocks.getUnchecked(i));
  321. w = jmax (w, ac->getPreferredWidth());
  322. }
  323. w = jmin (w, (int) (getParentWidth() * 0.7f));
  324. for (int i = textBlocks.size(); --i >= 0;)
  325. {
  326. AlertTextComp* const ac = static_cast<AlertTextComp*> (textBlocks.getUnchecked(i));
  327. ac->updateLayout ((int) (w * 0.8f));
  328. h += ac->getHeight() + 10;
  329. }
  330. h = jmin (getParentHeight() - 50, h);
  331. if (onlyIncreaseSize)
  332. {
  333. w = jmax (w, getWidth());
  334. h = jmax (h, getHeight());
  335. }
  336. if (! isVisible())
  337. {
  338. centreAroundComponent (associatedComponent, w, h);
  339. }
  340. else
  341. {
  342. const int cx = getX() + getWidth() / 2;
  343. const int cy = getY() + getHeight() / 2;
  344. setBounds (cx - w / 2,
  345. cy - h / 2,
  346. w, h);
  347. }
  348. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  349. const int spacer = 16;
  350. int totalWidth = -spacer;
  351. for (int i = buttons.size(); --i >= 0;)
  352. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  353. int x = (w - totalWidth) / 2;
  354. int y = (int) (getHeight() * 0.95f);
  355. for (int i = 0; i < buttons.size(); ++i)
  356. {
  357. TextButton* const c = buttons.getUnchecked(i);
  358. int ny = proportionOfHeight (0.95f) - c->getHeight();
  359. c->setTopLeftPosition (x, ny);
  360. if (ny < y)
  361. y = ny;
  362. x += c->getWidth() + spacer;
  363. c->toFront (false);
  364. }
  365. y = textBottom;
  366. for (int i = 0; i < allComps.size(); ++i)
  367. {
  368. Component* const c = allComps.getUnchecked(i);
  369. h = 22;
  370. const int comboIndex = comboBoxes.indexOf (dynamic_cast<ComboBox*> (c));
  371. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  372. y += labelHeight;
  373. const int tbIndex = textBoxes.indexOf (dynamic_cast<TextEditor*> (c));
  374. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  375. y += labelHeight;
  376. if (customComps.contains (c))
  377. {
  378. if (c->getName().isNotEmpty())
  379. y += labelHeight;
  380. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  381. h = c->getHeight();
  382. }
  383. else if (textBlocks.contains (c))
  384. {
  385. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  386. h = c->getHeight();
  387. }
  388. else
  389. {
  390. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  391. }
  392. y += h + 10;
  393. }
  394. setWantsKeyboardFocus (getNumChildComponents() == 0);
  395. }
  396. bool AlertWindow::containsAnyExtraComponents() const
  397. {
  398. return allComps.size() > 0;
  399. }
  400. //==============================================================================
  401. void AlertWindow::mouseDown (const MouseEvent& e)
  402. {
  403. dragger.startDraggingComponent (this, e);
  404. }
  405. void AlertWindow::mouseDrag (const MouseEvent& e)
  406. {
  407. dragger.dragComponent (this, e, &constrainer);
  408. }
  409. bool AlertWindow::keyPressed (const KeyPress& key)
  410. {
  411. for (int i = buttons.size(); --i >= 0;)
  412. {
  413. TextButton* const b = buttons.getUnchecked(i);
  414. if (b->isRegisteredForShortcut (key))
  415. {
  416. b->triggerClick();
  417. return true;
  418. }
  419. }
  420. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  421. {
  422. exitModalState (0);
  423. return true;
  424. }
  425. if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  426. {
  427. buttons.getUnchecked(0)->triggerClick();
  428. return true;
  429. }
  430. return false;
  431. }
  432. void AlertWindow::lookAndFeelChanged()
  433. {
  434. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  435. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  436. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  437. updateLayout (false);
  438. }
  439. int AlertWindow::getDesktopWindowStyleFlags() const
  440. {
  441. return getLookAndFeel().getAlertBoxWindowFlags();
  442. }
  443. //==============================================================================
  444. class AlertWindowInfo
  445. {
  446. public:
  447. AlertWindowInfo (const String& t, const String& m, Component* component,
  448. AlertWindow::AlertIconType icon, int numButts,
  449. ModalComponentManager::Callback* cb, bool runModally)
  450. : title (t), message (m), iconType (icon), numButtons (numButts),
  451. returnValue (0), associatedComponent (component),
  452. callback (cb), modal (runModally)
  453. {
  454. }
  455. String title, message, button1, button2, button3;
  456. int invoke() const
  457. {
  458. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  459. return returnValue;
  460. }
  461. private:
  462. AlertWindow::AlertIconType iconType;
  463. int numButtons, returnValue;
  464. WeakReference<Component> associatedComponent;
  465. ModalComponentManager::Callback* callback;
  466. bool modal;
  467. void show()
  468. {
  469. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  470. : LookAndFeel::getDefaultLookAndFeel();
  471. ScopedPointer<Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  472. iconType, numButtons, associatedComponent));
  473. jassert (alertBox != nullptr); // you have to return one of these!
  474. #if JUCE_MODAL_LOOPS_PERMITTED
  475. if (modal)
  476. {
  477. returnValue = alertBox->runModalLoop();
  478. }
  479. else
  480. #endif
  481. {
  482. (void) modal; // (to avoid an unused variable warning)
  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