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.

714 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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::empty);
  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. ed->setText (initialContents);
  120. ed->setCaretPosition (initialContents.length());
  121. addAndMakeVisible (ed);
  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::empty;
  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. const Font font (getLookAndFeel().getAlertWindowMessageFont());
  274. const int wid = jmax (font.getStringWidth (text),
  275. font.getStringWidth (getName()));
  276. const int sw = (int) std::sqrt (font.getHeight() * wid);
  277. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  278. const int edgeGap = 10;
  279. const int labelHeight = 18;
  280. int iconSpace = 0;
  281. AttributedString attributedText;
  282. attributedText.append (getName(), font.withHeight (font.getHeight() * 1.1f).boldened());
  283. if (text.isNotEmpty())
  284. attributedText.append ("\n\n" + text, font);
  285. attributedText.setColour (findColour (textColourId));
  286. if (alertIconType == NoIcon)
  287. {
  288. attributedText.setJustification (Justification::centredTop);
  289. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  290. }
  291. else
  292. {
  293. attributedText.setJustification (Justification::topLeft);
  294. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  295. iconSpace = iconWidth;
  296. }
  297. w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
  298. w = jmin (w, (int) (getParentWidth() * 0.7f));
  299. const int textLayoutH = (int) textLayout.getHeight();
  300. const int textBottom = 16 + titleH + textLayoutH;
  301. int h = textBottom;
  302. int buttonW = 40;
  303. for (int i = 0; i < buttons.size(); ++i)
  304. buttonW += 16 + buttons.getUnchecked(i)->getWidth();
  305. w = jmax (buttonW, w);
  306. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  307. if (buttons.size() > 0)
  308. h += 20 + buttons.getUnchecked(0)->getHeight();
  309. for (int i = customComps.size(); --i >= 0;)
  310. {
  311. Component* c = customComps.getUnchecked(i);
  312. w = jmax (w, (c->getWidth() * 100) / 80);
  313. h += 10 + c->getHeight();
  314. if (c->getName().isNotEmpty())
  315. h += labelHeight;
  316. }
  317. for (int i = textBlocks.size(); --i >= 0;)
  318. {
  319. const AlertTextComp* const ac = static_cast<const AlertTextComp*> (textBlocks.getUnchecked(i));
  320. w = jmax (w, ac->getPreferredWidth());
  321. }
  322. w = jmin (w, (int) (getParentWidth() * 0.7f));
  323. for (int i = textBlocks.size(); --i >= 0;)
  324. {
  325. AlertTextComp* const ac = static_cast<AlertTextComp*> (textBlocks.getUnchecked(i));
  326. ac->updateLayout ((int) (w * 0.8f));
  327. h += ac->getHeight() + 10;
  328. }
  329. h = jmin (getParentHeight() - 50, h);
  330. if (onlyIncreaseSize)
  331. {
  332. w = jmax (w, getWidth());
  333. h = jmax (h, getHeight());
  334. }
  335. if (! isVisible())
  336. {
  337. centreAroundComponent (associatedComponent, w, h);
  338. }
  339. else
  340. {
  341. const int cx = getX() + getWidth() / 2;
  342. const int cy = getY() + getHeight() / 2;
  343. setBounds (cx - w / 2,
  344. cy - h / 2,
  345. w, h);
  346. }
  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. #if JUCE_MODAL_LOOPS_PERMITTED
  474. if (modal)
  475. {
  476. returnValue = alertBox->runModalLoop();
  477. }
  478. else
  479. #endif
  480. {
  481. (void) modal; // (to avoid an unused variable warning)
  482. alertBox->enterModalState (true, callback, true);
  483. alertBox.release();
  484. }
  485. }
  486. static void* showCallback (void* userData)
  487. {
  488. static_cast<AlertWindowInfo*> (userData)->show();
  489. return nullptr;
  490. }
  491. };
  492. #if JUCE_MODAL_LOOPS_PERMITTED
  493. void AlertWindow::showMessageBox (AlertIconType iconType,
  494. const String& title,
  495. const String& message,
  496. const String& buttonText,
  497. Component* associatedComponent)
  498. {
  499. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  500. {
  501. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  502. }
  503. else
  504. {
  505. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, nullptr, true);
  506. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  507. info.invoke();
  508. }
  509. }
  510. #endif
  511. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  512. const String& title,
  513. const String& message,
  514. const String& buttonText,
  515. Component* associatedComponent,
  516. ModalComponentManager::Callback* callback)
  517. {
  518. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  519. {
  520. NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent, callback);
  521. }
  522. else
  523. {
  524. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, callback, false);
  525. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  526. info.invoke();
  527. }
  528. }
  529. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  530. const String& title,
  531. const String& message,
  532. const String& button1Text,
  533. const String& button2Text,
  534. Component* associatedComponent,
  535. ModalComponentManager::Callback* callback)
  536. {
  537. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  538. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  539. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  540. info.button1 = button1Text.isEmpty() ? TRANS("OK") : button1Text;
  541. info.button2 = button2Text.isEmpty() ? TRANS("Cancel") : button2Text;
  542. return info.invoke() != 0;
  543. }
  544. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  545. const String& title,
  546. const String& message,
  547. const String& button1Text,
  548. const String& button2Text,
  549. const String& button3Text,
  550. Component* associatedComponent,
  551. ModalComponentManager::Callback* callback)
  552. {
  553. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  554. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  555. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  556. info.button1 = button1Text.isEmpty() ? TRANS("Yes") : button1Text;
  557. info.button2 = button2Text.isEmpty() ? TRANS("No") : button2Text;
  558. info.button3 = button3Text.isEmpty() ? TRANS("Cancel") : button3Text;
  559. return info.invoke();
  560. }
  561. #if JUCE_MODAL_LOOPS_PERMITTED
  562. bool AlertWindow::showNativeDialogBox (const String& title,
  563. const String& bodyText,
  564. bool isOkCancel)
  565. {
  566. if (isOkCancel)
  567. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  568. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  569. return true;
  570. }
  571. #endif