The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

732 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. static juce_wchar getDefaultPasswordChar() noexcept
  19. {
  20. #if JUCE_LINUX
  21. return 0x2022;
  22. #else
  23. return 0x25cf;
  24. #endif
  25. }
  26. //==============================================================================
  27. AlertWindow::AlertWindow (const String& title,
  28. const String& message,
  29. AlertIconType iconType,
  30. Component* comp)
  31. : TopLevelWindow (title, true),
  32. alertIconType (iconType),
  33. associatedComponent (comp),
  34. escapeKeyCancels (true)
  35. {
  36. if (message.isEmpty())
  37. text = " "; // to force an update if the message is empty
  38. setMessage (message);
  39. Desktop& desktop = Desktop::getInstance();
  40. for (int i = desktop.getNumComponents(); --i >= 0;)
  41. {
  42. if (Component* const c = desktop.getComponent (i))
  43. {
  44. if (c->isAlwaysOnTop() && c->isShowing())
  45. {
  46. setAlwaysOnTop (true);
  47. break;
  48. }
  49. }
  50. }
  51. AlertWindow::lookAndFeelChanged();
  52. constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000);
  53. }
  54. AlertWindow::~AlertWindow()
  55. {
  56. removeAllChildren();
  57. }
  58. void AlertWindow::userTriedToCloseWindow()
  59. {
  60. if (escapeKeyCancels || buttons.size() > 0)
  61. exitModalState (0);
  62. }
  63. //==============================================================================
  64. void AlertWindow::setMessage (const String& message)
  65. {
  66. const String newMessage (message.substring (0, 2048));
  67. if (text != newMessage)
  68. {
  69. text = newMessage;
  70. updateLayout (true);
  71. repaint();
  72. }
  73. }
  74. //==============================================================================
  75. void AlertWindow::buttonClicked (Button* button)
  76. {
  77. if (button->getParentComponent() != nullptr)
  78. button->getParentComponent()->exitModalState (button->getCommandID());
  79. }
  80. //==============================================================================
  81. void AlertWindow::addButton (const String& name,
  82. const int returnValue,
  83. const KeyPress& shortcutKey1,
  84. const KeyPress& shortcutKey2)
  85. {
  86. TextButton* const b = new TextButton (name, String::empty);
  87. buttons.add (b);
  88. b->setWantsKeyboardFocus (true);
  89. b->setMouseClickGrabsKeyboardFocus (false);
  90. b->setCommandToTrigger (0, returnValue, false);
  91. b->addShortcut (shortcutKey1);
  92. b->addShortcut (shortcutKey2);
  93. b->addListener (this);
  94. b->changeWidthToFitText (getLookAndFeel().getAlertWindowButtonHeight());
  95. addAndMakeVisible (b, 0);
  96. updateLayout (false);
  97. }
  98. int AlertWindow::getNumButtons() const
  99. {
  100. return buttons.size();
  101. }
  102. void AlertWindow::triggerButtonClick (const String& buttonName)
  103. {
  104. for (int i = buttons.size(); --i >= 0;)
  105. {
  106. TextButton* const b = buttons.getUnchecked(i);
  107. if (buttonName == b->getName())
  108. {
  109. b->triggerClick();
  110. break;
  111. }
  112. }
  113. }
  114. void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
  115. {
  116. escapeKeyCancels = shouldEscapeKeyCancel;
  117. }
  118. //==============================================================================
  119. void AlertWindow::addTextEditor (const String& name,
  120. const String& initialContents,
  121. const String& onScreenLabel,
  122. const bool isPasswordBox)
  123. {
  124. TextEditor* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
  125. ed->setSelectAllWhenFocused (true);
  126. ed->setEscapeAndReturnKeysConsumed (false);
  127. textBoxes.add (ed);
  128. allComps.add (ed);
  129. ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  130. ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
  131. ed->setText (initialContents);
  132. ed->setCaretPosition (initialContents.length());
  133. addAndMakeVisible (ed);
  134. textboxNames.add (onScreenLabel);
  135. updateLayout (false);
  136. }
  137. TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
  138. {
  139. for (int i = textBoxes.size(); --i >= 0;)
  140. if (textBoxes.getUnchecked(i)->getName() == nameOfTextEditor)
  141. return textBoxes.getUnchecked(i);
  142. return nullptr;
  143. }
  144. String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
  145. {
  146. TextEditor* const t = getTextEditor (nameOfTextEditor);
  147. return t != nullptr ? t->getText() : String::empty;
  148. }
  149. //==============================================================================
  150. void AlertWindow::addComboBox (const String& name,
  151. const StringArray& items,
  152. const String& onScreenLabel)
  153. {
  154. ComboBox* const cb = new ComboBox (name);
  155. comboBoxes.add (cb);
  156. allComps.add (cb);
  157. cb->addItemList (items, 1);
  158. addAndMakeVisible (cb);
  159. cb->setSelectedItemIndex (0);
  160. comboBoxNames.add (onScreenLabel);
  161. updateLayout (false);
  162. }
  163. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  164. {
  165. for (int i = comboBoxes.size(); --i >= 0;)
  166. if (comboBoxes.getUnchecked(i)->getName() == nameOfList)
  167. return comboBoxes.getUnchecked(i);
  168. return nullptr;
  169. }
  170. //==============================================================================
  171. class AlertTextComp : public TextEditor
  172. {
  173. public:
  174. AlertTextComp (const String& message,
  175. const Font& font)
  176. {
  177. setReadOnly (true);
  178. setMultiLine (true, true);
  179. setCaretVisible (false);
  180. setScrollbarsShown (true);
  181. lookAndFeelChanged();
  182. setWantsKeyboardFocus (false);
  183. setFont (font);
  184. setText (message, false);
  185. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  186. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  187. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  188. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  189. }
  190. int getPreferredWidth() const noexcept { return bestWidth; }
  191. void updateLayout (const int width)
  192. {
  193. AttributedString s;
  194. s.setJustification (Justification::topLeft);
  195. s.append (getText(), getFont());
  196. TextLayout text;
  197. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  198. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  199. }
  200. private:
  201. int bestWidth;
  202. JUCE_DECLARE_NON_COPYABLE (AlertTextComp);
  203. };
  204. void AlertWindow::addTextBlock (const String& textBlock)
  205. {
  206. AlertTextComp* const c = new AlertTextComp (textBlock, getLookAndFeel().getAlertWindowMessageFont());
  207. textBlocks.add (c);
  208. allComps.add (c);
  209. addAndMakeVisible (c);
  210. updateLayout (false);
  211. }
  212. //==============================================================================
  213. void AlertWindow::addProgressBarComponent (double& progressValue)
  214. {
  215. ProgressBar* const pb = new ProgressBar (progressValue);
  216. progressBars.add (pb);
  217. allComps.add (pb);
  218. addAndMakeVisible (pb);
  219. updateLayout (false);
  220. }
  221. //==============================================================================
  222. void AlertWindow::addCustomComponent (Component* const component)
  223. {
  224. customComps.add (component);
  225. allComps.add (component);
  226. addAndMakeVisible (component);
  227. updateLayout (false);
  228. }
  229. int AlertWindow::getNumCustomComponents() const
  230. {
  231. return customComps.size();
  232. }
  233. Component* AlertWindow::getCustomComponent (const int index) const
  234. {
  235. return customComps [index];
  236. }
  237. Component* AlertWindow::removeCustomComponent (const int index)
  238. {
  239. Component* const c = getCustomComponent (index);
  240. if (c != nullptr)
  241. {
  242. customComps.removeFirstMatchingValue (c);
  243. allComps.removeFirstMatchingValue (c);
  244. removeChildComponent (c);
  245. updateLayout (false);
  246. }
  247. return c;
  248. }
  249. //==============================================================================
  250. void AlertWindow::paint (Graphics& g)
  251. {
  252. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  253. g.setColour (findColour (textColourId));
  254. g.setFont (getLookAndFeel().getAlertWindowFont());
  255. for (int i = textBoxes.size(); --i >= 0;)
  256. {
  257. const TextEditor* const te = textBoxes.getUnchecked(i);
  258. g.drawFittedText (textboxNames[i],
  259. te->getX(), te->getY() - 14,
  260. te->getWidth(), 14,
  261. Justification::centredLeft, 1);
  262. }
  263. for (int i = comboBoxNames.size(); --i >= 0;)
  264. {
  265. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  266. g.drawFittedText (comboBoxNames[i],
  267. cb->getX(), cb->getY() - 14,
  268. cb->getWidth(), 14,
  269. Justification::centredLeft, 1);
  270. }
  271. for (int i = customComps.size(); --i >= 0;)
  272. {
  273. const Component* const c = customComps.getUnchecked(i);
  274. g.drawFittedText (c->getName(),
  275. c->getX(), c->getY() - 14,
  276. c->getWidth(), 14,
  277. Justification::centredLeft, 1);
  278. }
  279. }
  280. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  281. {
  282. const int titleH = 24;
  283. const int iconWidth = 80;
  284. const Font font (getLookAndFeel().getAlertWindowMessageFont());
  285. const int wid = jmax (font.getStringWidth (text),
  286. font.getStringWidth (getName()));
  287. const int sw = (int) std::sqrt (font.getHeight() * wid);
  288. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  289. const int edgeGap = 10;
  290. const int labelHeight = 18;
  291. int iconSpace = 0;
  292. AttributedString attributedText;
  293. attributedText.append (getName(), font.withHeight (font.getHeight() * 1.1f).boldened());
  294. if (text.isNotEmpty())
  295. attributedText.append ("\n\n" + text, font);
  296. attributedText.setColour (findColour (textColourId));
  297. if (alertIconType == NoIcon)
  298. {
  299. attributedText.setJustification (Justification::centredTop);
  300. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  301. }
  302. else
  303. {
  304. attributedText.setJustification (Justification::topLeft);
  305. textLayout.createLayoutWithBalancedLineLengths (attributedText, (float) w);
  306. iconSpace = iconWidth;
  307. }
  308. w = jmax (350, (int) textLayout.getWidth() + iconSpace + edgeGap * 4);
  309. w = jmin (w, (int) (getParentWidth() * 0.7f));
  310. const int textLayoutH = (int) textLayout.getHeight();
  311. const int textBottom = 16 + titleH + textLayoutH;
  312. int h = textBottom;
  313. int buttonW = 40;
  314. for (int i = 0; i < buttons.size(); ++i)
  315. buttonW += 16 + buttons.getUnchecked(i)->getWidth();
  316. w = jmax (buttonW, w);
  317. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  318. if (buttons.size() > 0)
  319. h += 20 + buttons.getUnchecked(0)->getHeight();
  320. for (int i = customComps.size(); --i >= 0;)
  321. {
  322. Component* c = customComps.getUnchecked(i);
  323. w = jmax (w, (c->getWidth() * 100) / 80);
  324. h += 10 + c->getHeight();
  325. if (c->getName().isNotEmpty())
  326. h += labelHeight;
  327. }
  328. for (int i = textBlocks.size(); --i >= 0;)
  329. {
  330. const AlertTextComp* const ac = static_cast <const AlertTextComp*> (textBlocks.getUnchecked(i));
  331. w = jmax (w, ac->getPreferredWidth());
  332. }
  333. w = jmin (w, (int) (getParentWidth() * 0.7f));
  334. for (int i = textBlocks.size(); --i >= 0;)
  335. {
  336. AlertTextComp* const ac = static_cast <AlertTextComp*> (textBlocks.getUnchecked(i));
  337. ac->updateLayout ((int) (w * 0.8f));
  338. h += ac->getHeight() + 10;
  339. }
  340. h = jmin (getParentHeight() - 50, h);
  341. if (onlyIncreaseSize)
  342. {
  343. w = jmax (w, getWidth());
  344. h = jmax (h, getHeight());
  345. }
  346. if (! isVisible())
  347. {
  348. centreAroundComponent (associatedComponent, w, h);
  349. }
  350. else
  351. {
  352. const int cx = getX() + getWidth() / 2;
  353. const int cy = getY() + getHeight() / 2;
  354. setBounds (cx - w / 2,
  355. cy - h / 2,
  356. w, h);
  357. }
  358. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  359. const int spacer = 16;
  360. int totalWidth = -spacer;
  361. for (int i = buttons.size(); --i >= 0;)
  362. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  363. int x = (w - totalWidth) / 2;
  364. int y = (int) (getHeight() * 0.95f);
  365. for (int i = 0; i < buttons.size(); ++i)
  366. {
  367. TextButton* const c = buttons.getUnchecked(i);
  368. int ny = proportionOfHeight (0.95f) - c->getHeight();
  369. c->setTopLeftPosition (x, ny);
  370. if (ny < y)
  371. y = ny;
  372. x += c->getWidth() + spacer;
  373. c->toFront (false);
  374. }
  375. y = textBottom;
  376. for (int i = 0; i < allComps.size(); ++i)
  377. {
  378. Component* const c = allComps.getUnchecked(i);
  379. h = 22;
  380. const int comboIndex = comboBoxes.indexOf (dynamic_cast <ComboBox*> (c));
  381. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  382. y += labelHeight;
  383. const int tbIndex = textBoxes.indexOf (dynamic_cast <TextEditor*> (c));
  384. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  385. y += labelHeight;
  386. if (customComps.contains (c))
  387. {
  388. if (c->getName().isNotEmpty())
  389. y += labelHeight;
  390. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  391. h = c->getHeight();
  392. }
  393. else if (textBlocks.contains (c))
  394. {
  395. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  396. h = c->getHeight();
  397. }
  398. else
  399. {
  400. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  401. }
  402. y += h + 10;
  403. }
  404. setWantsKeyboardFocus (getNumChildComponents() == 0);
  405. }
  406. bool AlertWindow::containsAnyExtraComponents() const
  407. {
  408. return allComps.size() > 0;
  409. }
  410. //==============================================================================
  411. void AlertWindow::mouseDown (const MouseEvent& e)
  412. {
  413. dragger.startDraggingComponent (this, e);
  414. }
  415. void AlertWindow::mouseDrag (const MouseEvent& e)
  416. {
  417. dragger.dragComponent (this, e, &constrainer);
  418. }
  419. bool AlertWindow::keyPressed (const KeyPress& key)
  420. {
  421. for (int i = buttons.size(); --i >= 0;)
  422. {
  423. TextButton* const b = buttons.getUnchecked(i);
  424. if (b->isRegisteredForShortcut (key))
  425. {
  426. b->triggerClick();
  427. return true;
  428. }
  429. }
  430. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  431. {
  432. exitModalState (0);
  433. return true;
  434. }
  435. else if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  436. {
  437. buttons.getUnchecked(0)->triggerClick();
  438. return true;
  439. }
  440. return false;
  441. }
  442. void AlertWindow::lookAndFeelChanged()
  443. {
  444. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  445. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  446. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  447. updateLayout (false);
  448. }
  449. int AlertWindow::getDesktopWindowStyleFlags() const
  450. {
  451. return getLookAndFeel().getAlertBoxWindowFlags();
  452. }
  453. //==============================================================================
  454. class AlertWindowInfo
  455. {
  456. public:
  457. AlertWindowInfo (const String& title_, const String& message_, Component* component,
  458. AlertWindow::AlertIconType iconType_, int numButtons_,
  459. ModalComponentManager::Callback* callback_, bool modal_)
  460. : title (title_), message (message_), iconType (iconType_),
  461. numButtons (numButtons_), returnValue (0), associatedComponent (component),
  462. callback (callback_), modal (modal_)
  463. {
  464. }
  465. String title, message, button1, button2, button3;
  466. int invoke() const
  467. {
  468. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  469. return returnValue;
  470. }
  471. private:
  472. AlertWindow::AlertIconType iconType;
  473. int numButtons, returnValue;
  474. WeakReference<Component> associatedComponent;
  475. ModalComponentManager::Callback* callback;
  476. bool modal;
  477. void show()
  478. {
  479. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  480. : LookAndFeel::getDefaultLookAndFeel();
  481. ScopedPointer <Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  482. iconType, numButtons, associatedComponent));
  483. jassert (alertBox != nullptr); // you have to return one of these!
  484. #if JUCE_MODAL_LOOPS_PERMITTED
  485. if (modal)
  486. {
  487. returnValue = alertBox->runModalLoop();
  488. }
  489. else
  490. #endif
  491. {
  492. alertBox->enterModalState (true, callback, true);
  493. alertBox.release();
  494. }
  495. }
  496. static void* showCallback (void* userData)
  497. {
  498. static_cast <AlertWindowInfo*> (userData)->show();
  499. return nullptr;
  500. }
  501. };
  502. #if JUCE_MODAL_LOOPS_PERMITTED
  503. void AlertWindow::showMessageBox (AlertIconType iconType,
  504. const String& title,
  505. const String& message,
  506. const String& buttonText,
  507. Component* associatedComponent)
  508. {
  509. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  510. {
  511. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  512. }
  513. else
  514. {
  515. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, 0, true);
  516. info.button1 = buttonText.isEmpty() ? TRANS("ok") : buttonText;
  517. info.invoke();
  518. }
  519. }
  520. #endif
  521. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  522. const String& title,
  523. const String& message,
  524. const String& buttonText,
  525. Component* associatedComponent)
  526. {
  527. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  528. {
  529. return NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent);
  530. }
  531. else
  532. {
  533. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, 0, false);
  534. info.button1 = buttonText.isEmpty() ? TRANS("ok") : buttonText;
  535. info.invoke();
  536. }
  537. }
  538. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  539. const String& title,
  540. const String& message,
  541. const String& button1Text,
  542. const String& button2Text,
  543. Component* associatedComponent,
  544. ModalComponentManager::Callback* callback)
  545. {
  546. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  547. {
  548. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  549. }
  550. else
  551. {
  552. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  553. info.button1 = button1Text.isEmpty() ? TRANS("ok") : button1Text;
  554. info.button2 = button2Text.isEmpty() ? TRANS("cancel") : button2Text;
  555. return info.invoke() != 0;
  556. }
  557. }
  558. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  559. const String& title,
  560. const String& message,
  561. const String& button1Text,
  562. const String& button2Text,
  563. const String& button3Text,
  564. Component* associatedComponent,
  565. ModalComponentManager::Callback* callback)
  566. {
  567. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  568. {
  569. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  570. }
  571. else
  572. {
  573. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  574. info.button1 = button1Text.isEmpty() ? TRANS("yes") : button1Text;
  575. info.button2 = button2Text.isEmpty() ? TRANS("no") : button2Text;
  576. info.button3 = button3Text.isEmpty() ? TRANS("cancel") : button3Text;
  577. return info.invoke();
  578. }
  579. }
  580. #if JUCE_MODAL_LOOPS_PERMITTED
  581. bool AlertWindow::showNativeDialogBox (const String& title,
  582. const String& bodyText,
  583. bool isOkCancel)
  584. {
  585. if (isOkCancel)
  586. {
  587. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  588. }
  589. else
  590. {
  591. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  592. return true;
  593. }
  594. }
  595. #endif