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.

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