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.

699 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. static juce_wchar getDefaultPasswordChar() noexcept
  22. {
  23. #if JUCE_LINUX
  24. return 0x2022;
  25. #else
  26. return 0x25cf;
  27. #endif
  28. }
  29. //==============================================================================
  30. AlertWindow::AlertWindow (const String& title,
  31. const String& message,
  32. AlertIconType iconType,
  33. Component* comp)
  34. : TopLevelWindow (title, true),
  35. alertIconType (iconType),
  36. associatedComponent (comp)
  37. {
  38. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  39. if (message.isEmpty())
  40. text = " "; // to force an update if the message is empty
  41. setMessage (message);
  42. AlertWindow::lookAndFeelChanged();
  43. constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000);
  44. }
  45. AlertWindow::~AlertWindow()
  46. {
  47. removeAllChildren();
  48. }
  49. void AlertWindow::userTriedToCloseWindow()
  50. {
  51. if (escapeKeyCancels || buttons.size() > 0)
  52. exitModalState (0);
  53. }
  54. //==============================================================================
  55. void AlertWindow::setMessage (const String& message)
  56. {
  57. auto newMessage = message.substring (0, 2048);
  58. if (text != newMessage)
  59. {
  60. text = newMessage;
  61. updateLayout (true);
  62. repaint();
  63. }
  64. }
  65. //==============================================================================
  66. void AlertWindow::buttonClicked (Button* button)
  67. {
  68. if (auto* parent = button->getParentComponent())
  69. parent->exitModalState (button->getCommandID());
  70. }
  71. //==============================================================================
  72. void AlertWindow::addButton (const String& name,
  73. const int returnValue,
  74. const KeyPress& shortcutKey1,
  75. const KeyPress& shortcutKey2)
  76. {
  77. auto* b = new TextButton (name, {});
  78. buttons.add (b);
  79. b->setWantsKeyboardFocus (true);
  80. b->setMouseClickGrabsKeyboardFocus (false);
  81. b->setCommandToTrigger (0, returnValue, false);
  82. b->addShortcut (shortcutKey1);
  83. b->addShortcut (shortcutKey2);
  84. b->addListener (this);
  85. Array<TextButton*> buttonsArray (buttons.begin(), buttons.size());
  86. auto& lf = getLookAndFeel();
  87. auto buttonHeight = lf.getAlertWindowButtonHeight();
  88. auto buttonWidths = lf.getWidthsForTextButtons (*this, buttonsArray);
  89. jassert (buttonWidths.size() == buttons.size());
  90. int i = 0;
  91. for (auto* button : buttons)
  92. button->setSize (buttonWidths[i++], buttonHeight);
  93. addAndMakeVisible (b, 0);
  94. updateLayout (false);
  95. }
  96. int AlertWindow::getNumButtons() const
  97. {
  98. return buttons.size();
  99. }
  100. void AlertWindow::triggerButtonClick (const String& buttonName)
  101. {
  102. for (auto* b : buttons)
  103. {
  104. if (buttonName == b->getName())
  105. {
  106. b->triggerClick();
  107. break;
  108. }
  109. }
  110. }
  111. void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
  112. {
  113. escapeKeyCancels = shouldEscapeKeyCancel;
  114. }
  115. //==============================================================================
  116. void AlertWindow::addTextEditor (const String& name,
  117. const String& initialContents,
  118. const String& onScreenLabel,
  119. const bool isPasswordBox)
  120. {
  121. auto* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
  122. ed->setSelectAllWhenFocused (true);
  123. ed->setEscapeAndReturnKeysConsumed (false);
  124. textBoxes.add (ed);
  125. allComps.add (ed);
  126. ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  127. ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
  128. addAndMakeVisible (ed);
  129. ed->setText (initialContents);
  130. ed->setCaretPosition (initialContents.length());
  131. textboxNames.add (onScreenLabel);
  132. updateLayout (false);
  133. }
  134. TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
  135. {
  136. for (auto* tb : textBoxes)
  137. if (tb->getName() == nameOfTextEditor)
  138. return tb;
  139. return nullptr;
  140. }
  141. String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
  142. {
  143. if (auto* t = getTextEditor (nameOfTextEditor))
  144. return t->getText();
  145. return {};
  146. }
  147. //==============================================================================
  148. void AlertWindow::addComboBox (const String& name,
  149. const StringArray& items,
  150. const String& onScreenLabel)
  151. {
  152. auto* cb = new ComboBox (name);
  153. comboBoxes.add (cb);
  154. allComps.add (cb);
  155. cb->addItemList (items, 1);
  156. addAndMakeVisible (cb);
  157. cb->setSelectedItemIndex (0);
  158. comboBoxNames.add (onScreenLabel);
  159. updateLayout (false);
  160. }
  161. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  162. {
  163. for (auto* cb : comboBoxes)
  164. if (cb->getName() == nameOfList)
  165. return cb;
  166. return nullptr;
  167. }
  168. //==============================================================================
  169. class AlertTextComp : public TextEditor
  170. {
  171. public:
  172. AlertTextComp (AlertWindow& owner, const String& message, const Font& font)
  173. {
  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. setReadOnly (true);
  180. setMultiLine (true, true);
  181. setCaretVisible (false);
  182. setScrollbarsShown (true);
  183. lookAndFeelChanged();
  184. setWantsKeyboardFocus (false);
  185. setFont (font);
  186. setText (message, false);
  187. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  188. }
  189. void updateLayout (const int width)
  190. {
  191. AttributedString s;
  192. s.setJustification (Justification::topLeft);
  193. s.append (getText(), getFont());
  194. TextLayout text;
  195. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  196. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  197. }
  198. int bestWidth;
  199. JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
  200. };
  201. void AlertWindow::addTextBlock (const String& textBlock)
  202. {
  203. auto* c = new AlertTextComp (*this, textBlock, getLookAndFeel().getAlertWindowMessageFont());
  204. textBlocks.add (c);
  205. allComps.add (c);
  206. addAndMakeVisible (c);
  207. updateLayout (false);
  208. }
  209. //==============================================================================
  210. void AlertWindow::addProgressBarComponent (double& progressValue)
  211. {
  212. auto* pb = new ProgressBar (progressValue);
  213. progressBars.add (pb);
  214. allComps.add (pb);
  215. addAndMakeVisible (pb);
  216. updateLayout (false);
  217. }
  218. //==============================================================================
  219. void AlertWindow::addCustomComponent (Component* const component)
  220. {
  221. customComps.add (component);
  222. allComps.add (component);
  223. addAndMakeVisible (component);
  224. updateLayout (false);
  225. }
  226. int AlertWindow::getNumCustomComponents() const { return customComps.size(); }
  227. Component* AlertWindow::getCustomComponent (int index) const { return customComps [index]; }
  228. Component* AlertWindow::removeCustomComponent (const int index)
  229. {
  230. auto* c = getCustomComponent (index);
  231. if (c != nullptr)
  232. {
  233. customComps.removeFirstMatchingValue (c);
  234. allComps.removeFirstMatchingValue (c);
  235. removeChildComponent (c);
  236. updateLayout (false);
  237. }
  238. return c;
  239. }
  240. //==============================================================================
  241. void AlertWindow::paint (Graphics& g)
  242. {
  243. auto& lf = getLookAndFeel();
  244. lf.drawAlertBox (g, *this, textArea, textLayout);
  245. g.setColour (findColour (textColourId));
  246. g.setFont (lf.getAlertWindowFont());
  247. for (int i = textBoxes.size(); --i >= 0;)
  248. {
  249. auto* te = textBoxes.getUnchecked(i);
  250. g.drawFittedText (textboxNames[i],
  251. te->getX(), te->getY() - 14,
  252. te->getWidth(), 14,
  253. Justification::centredLeft, 1);
  254. }
  255. for (int i = comboBoxNames.size(); --i >= 0;)
  256. {
  257. auto* cb = comboBoxes.getUnchecked(i);
  258. g.drawFittedText (comboBoxNames[i],
  259. cb->getX(), cb->getY() - 14,
  260. cb->getWidth(), 14,
  261. Justification::centredLeft, 1);
  262. }
  263. for (auto* c : customComps)
  264. g.drawFittedText (c->getName(),
  265. c->getX(), c->getY() - 14,
  266. c->getWidth(), 14,
  267. Justification::centredLeft, 1);
  268. }
  269. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  270. {
  271. const int titleH = 24;
  272. const int iconWidth = 80;
  273. auto& lf = getLookAndFeel();
  274. auto messageFont (lf.getAlertWindowMessageFont());
  275. auto wid = jmax (messageFont.getStringWidth (text),
  276. messageFont.getStringWidth (getName()));
  277. auto sw = (int) std::sqrt (messageFont.getHeight() * wid);
  278. auto 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. auto textLayoutH = (int) textLayout.getHeight();
  301. auto textBottom = 16 + titleH + textLayoutH;
  302. int h = textBottom;
  303. int buttonW = 40;
  304. for (auto* b : buttons)
  305. buttonW += 16 + b->getWidth();
  306. w = jmax (buttonW, w);
  307. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  308. if (auto* b = buttons[0])
  309. h += 20 + b->getHeight();
  310. for (auto* c : customComps)
  311. {
  312. w = jmax (w, (c->getWidth() * 100) / 80);
  313. h += 10 + c->getHeight();
  314. if (c->getName().isNotEmpty())
  315. h += labelHeight;
  316. }
  317. for (auto* tb : textBlocks)
  318. w = jmax (w, static_cast<const AlertTextComp*> (tb)->bestWidth);
  319. w = jmin (w, (int) (getParentWidth() * 0.7f));
  320. for (auto* tb : textBlocks)
  321. {
  322. auto* ac = static_cast<AlertTextComp*> (tb);
  323. ac->updateLayout ((int) (w * 0.8f));
  324. h += ac->getHeight() + 10;
  325. }
  326. h = jmin (getParentHeight() - 50, h);
  327. if (onlyIncreaseSize)
  328. {
  329. w = jmax (w, getWidth());
  330. h = jmax (h, getHeight());
  331. }
  332. if (! isVisible())
  333. centreAroundComponent (associatedComponent, w, h);
  334. else
  335. setBounds (getBounds().withSizeKeepingCentre (w, h));
  336. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  337. const int spacer = 16;
  338. int totalWidth = -spacer;
  339. for (auto* b : buttons)
  340. totalWidth += b->getWidth() + spacer;
  341. auto x = (w - totalWidth) / 2;
  342. auto y = (int) (getHeight() * 0.95f);
  343. for (auto* c : buttons)
  344. {
  345. int ny = proportionOfHeight (0.95f) - c->getHeight();
  346. c->setTopLeftPosition (x, ny);
  347. if (ny < y)
  348. y = ny;
  349. x += c->getWidth() + spacer;
  350. c->toFront (false);
  351. }
  352. y = textBottom;
  353. for (auto* c : allComps)
  354. {
  355. h = 22;
  356. const int comboIndex = comboBoxes.indexOf (dynamic_cast<ComboBox*> (c));
  357. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  358. y += labelHeight;
  359. const int tbIndex = textBoxes.indexOf (dynamic_cast<TextEditor*> (c));
  360. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  361. y += labelHeight;
  362. if (customComps.contains (c))
  363. {
  364. if (c->getName().isNotEmpty())
  365. y += labelHeight;
  366. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  367. h = c->getHeight();
  368. }
  369. else if (textBlocks.contains (c))
  370. {
  371. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  372. h = c->getHeight();
  373. }
  374. else
  375. {
  376. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  377. }
  378. y += h + 10;
  379. }
  380. setWantsKeyboardFocus (getNumChildComponents() == 0);
  381. }
  382. bool AlertWindow::containsAnyExtraComponents() const
  383. {
  384. return allComps.size() > 0;
  385. }
  386. //==============================================================================
  387. void AlertWindow::mouseDown (const MouseEvent& e)
  388. {
  389. dragger.startDraggingComponent (this, e);
  390. }
  391. void AlertWindow::mouseDrag (const MouseEvent& e)
  392. {
  393. dragger.dragComponent (this, e, &constrainer);
  394. }
  395. bool AlertWindow::keyPressed (const KeyPress& key)
  396. {
  397. for (auto* b : buttons)
  398. {
  399. if (b->isRegisteredForShortcut (key))
  400. {
  401. b->triggerClick();
  402. return true;
  403. }
  404. }
  405. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  406. {
  407. exitModalState (0);
  408. return true;
  409. }
  410. if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  411. {
  412. buttons.getUnchecked(0)->triggerClick();
  413. return true;
  414. }
  415. return false;
  416. }
  417. void AlertWindow::lookAndFeelChanged()
  418. {
  419. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  420. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  421. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  422. updateLayout (false);
  423. }
  424. int AlertWindow::getDesktopWindowStyleFlags() const
  425. {
  426. return getLookAndFeel().getAlertBoxWindowFlags();
  427. }
  428. //==============================================================================
  429. class AlertWindowInfo
  430. {
  431. public:
  432. AlertWindowInfo (const String& t, const String& m, Component* component,
  433. AlertWindow::AlertIconType icon, int numButts,
  434. ModalComponentManager::Callback* cb, bool runModally)
  435. : title (t), message (m), iconType (icon), numButtons (numButts),
  436. associatedComponent (component), callback (cb), modal (runModally)
  437. {
  438. }
  439. String title, message, button1, button2, button3;
  440. int invoke() const
  441. {
  442. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  443. return returnValue;
  444. }
  445. private:
  446. AlertWindow::AlertIconType iconType;
  447. int numButtons, returnValue = 0;
  448. WeakReference<Component> associatedComponent;
  449. ModalComponentManager::Callback* callback;
  450. bool modal;
  451. void show()
  452. {
  453. auto& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  454. : LookAndFeel::getDefaultLookAndFeel();
  455. ScopedPointer<Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  456. iconType, numButtons, associatedComponent));
  457. jassert (alertBox != nullptr); // you have to return one of these!
  458. alertBox->setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  459. #if JUCE_MODAL_LOOPS_PERMITTED
  460. if (modal)
  461. {
  462. returnValue = alertBox->runModalLoop();
  463. }
  464. else
  465. #endif
  466. {
  467. ignoreUnused (modal);
  468. alertBox->enterModalState (true, callback, true);
  469. alertBox.release();
  470. }
  471. }
  472. static void* showCallback (void* userData)
  473. {
  474. static_cast<AlertWindowInfo*> (userData)->show();
  475. return nullptr;
  476. }
  477. };
  478. #if JUCE_MODAL_LOOPS_PERMITTED
  479. void AlertWindow::showMessageBox (AlertIconType iconType,
  480. const String& title,
  481. const String& message,
  482. const String& buttonText,
  483. Component* associatedComponent)
  484. {
  485. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  486. {
  487. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  488. }
  489. else
  490. {
  491. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, nullptr, true);
  492. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  493. info.invoke();
  494. }
  495. }
  496. #endif
  497. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  498. const String& title,
  499. const String& message,
  500. const String& buttonText,
  501. Component* associatedComponent,
  502. ModalComponentManager::Callback* callback)
  503. {
  504. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  505. {
  506. NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent, callback);
  507. }
  508. else
  509. {
  510. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, callback, false);
  511. info.button1 = buttonText.isEmpty() ? TRANS("OK") : buttonText;
  512. info.invoke();
  513. }
  514. }
  515. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  516. const String& title,
  517. const String& message,
  518. const String& button1Text,
  519. const String& button2Text,
  520. Component* associatedComponent,
  521. ModalComponentManager::Callback* callback)
  522. {
  523. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  524. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  525. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  526. info.button1 = button1Text.isEmpty() ? TRANS("OK") : button1Text;
  527. info.button2 = button2Text.isEmpty() ? TRANS("Cancel") : button2Text;
  528. return info.invoke() != 0;
  529. }
  530. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  531. const String& title,
  532. const String& message,
  533. const String& button1Text,
  534. const String& button2Text,
  535. const String& button3Text,
  536. Component* associatedComponent,
  537. ModalComponentManager::Callback* callback)
  538. {
  539. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  540. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  541. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  542. info.button1 = button1Text.isEmpty() ? TRANS("Yes") : button1Text;
  543. info.button2 = button2Text.isEmpty() ? TRANS("No") : button2Text;
  544. info.button3 = button3Text.isEmpty() ? TRANS("Cancel") : button3Text;
  545. return info.invoke();
  546. }
  547. #if JUCE_MODAL_LOOPS_PERMITTED
  548. bool AlertWindow::showNativeDialogBox (const String& title,
  549. const String& bodyText,
  550. bool isOkCancel)
  551. {
  552. if (isOkCancel)
  553. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  554. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  555. return true;
  556. }
  557. #endif
  558. } // namespace juce