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.

757 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. cb->addItemList (items, 1);
  182. addAndMakeVisible (cb);
  183. cb->setSelectedItemIndex (0);
  184. comboBoxNames.add (onScreenLabel);
  185. updateLayout (false);
  186. }
  187. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  188. {
  189. for (int i = comboBoxes.size(); --i >= 0;)
  190. if (comboBoxes.getUnchecked(i)->getName() == nameOfList)
  191. return comboBoxes.getUnchecked(i);
  192. return nullptr;
  193. }
  194. //==============================================================================
  195. class AlertTextComp : public TextEditor
  196. {
  197. public:
  198. AlertTextComp (const String& message,
  199. const Font& font)
  200. {
  201. setReadOnly (true);
  202. setMultiLine (true, true);
  203. setCaretVisible (false);
  204. setScrollbarsShown (true);
  205. lookAndFeelChanged();
  206. setWantsKeyboardFocus (false);
  207. setFont (font);
  208. setText (message, false);
  209. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  210. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  211. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  212. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  213. }
  214. int getPreferredWidth() const noexcept { return bestWidth; }
  215. void updateLayout (const int width)
  216. {
  217. TextLayout text;
  218. text.appendText (getText(), getFont());
  219. text.layout (width - 8, Justification::topLeft, true);
  220. setSize (width, jmin (width, text.getHeight() + (int) getFont().getHeight()));
  221. }
  222. private:
  223. int bestWidth;
  224. JUCE_DECLARE_NON_COPYABLE (AlertTextComp);
  225. };
  226. void AlertWindow::addTextBlock (const String& textBlock)
  227. {
  228. AlertTextComp* const c = new AlertTextComp (textBlock, font);
  229. textBlocks.add (c);
  230. allComps.add (c);
  231. addAndMakeVisible (c);
  232. updateLayout (false);
  233. }
  234. //==============================================================================
  235. void AlertWindow::addProgressBarComponent (double& progressValue)
  236. {
  237. ProgressBar* const pb = new ProgressBar (progressValue);
  238. progressBars.add (pb);
  239. allComps.add (pb);
  240. addAndMakeVisible (pb);
  241. updateLayout (false);
  242. }
  243. //==============================================================================
  244. void AlertWindow::addCustomComponent (Component* const component)
  245. {
  246. customComps.add (component);
  247. allComps.add (component);
  248. addAndMakeVisible (component);
  249. updateLayout (false);
  250. }
  251. int AlertWindow::getNumCustomComponents() const
  252. {
  253. return customComps.size();
  254. }
  255. Component* AlertWindow::getCustomComponent (const int index) const
  256. {
  257. return customComps [index];
  258. }
  259. Component* AlertWindow::removeCustomComponent (const int index)
  260. {
  261. Component* const c = getCustomComponent (index);
  262. if (c != nullptr)
  263. {
  264. customComps.removeValue (c);
  265. allComps.removeValue (c);
  266. removeChildComponent (c);
  267. updateLayout (false);
  268. }
  269. return c;
  270. }
  271. //==============================================================================
  272. void AlertWindow::paint (Graphics& g)
  273. {
  274. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  275. g.setColour (findColour (textColourId));
  276. g.setFont (getLookAndFeel().getAlertWindowFont());
  277. int i;
  278. for (i = textBoxes.size(); --i >= 0;)
  279. {
  280. const TextEditor* const te = textBoxes.getUnchecked(i);
  281. g.drawFittedText (textboxNames[i],
  282. te->getX(), te->getY() - 14,
  283. te->getWidth(), 14,
  284. Justification::centredLeft, 1);
  285. }
  286. for (i = comboBoxNames.size(); --i >= 0;)
  287. {
  288. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  289. g.drawFittedText (comboBoxNames[i],
  290. cb->getX(), cb->getY() - 14,
  291. cb->getWidth(), 14,
  292. Justification::centredLeft, 1);
  293. }
  294. for (i = customComps.size(); --i >= 0;)
  295. {
  296. const Component* const c = customComps.getUnchecked(i);
  297. g.drawFittedText (c->getName(),
  298. c->getX(), c->getY() - 14,
  299. c->getWidth(), 14,
  300. Justification::centredLeft, 1);
  301. }
  302. }
  303. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  304. {
  305. const int titleH = 24;
  306. const int iconWidth = 80;
  307. const int wid = jmax (font.getStringWidth (text),
  308. font.getStringWidth (getName()));
  309. const int sw = (int) std::sqrt (font.getHeight() * wid);
  310. int w = jmin (300 + sw * 2, (int) (getParentWidth() * 0.7f));
  311. const int edgeGap = 10;
  312. const int labelHeight = 18;
  313. int iconSpace = 0;
  314. if (alertIconType == NoIcon)
  315. {
  316. textLayout.layout (w, Justification::horizontallyCentred, true);
  317. }
  318. else
  319. {
  320. textLayout.layout (w, Justification::left, true);
  321. iconSpace = iconWidth;
  322. }
  323. w = jmax (350, textLayout.getWidth() + iconSpace + edgeGap * 4);
  324. w = jmin (w, (int) (getParentWidth() * 0.7f));
  325. const int textLayoutH = textLayout.getHeight();
  326. const int textBottom = 16 + titleH + textLayoutH;
  327. int h = textBottom;
  328. int buttonW = 40;
  329. int i;
  330. for (i = 0; i < buttons.size(); ++i)
  331. buttonW += 16 + buttons.getUnchecked(i)->getWidth();
  332. w = jmax (buttonW, w);
  333. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  334. if (buttons.size() > 0)
  335. h += 20 + buttons.getUnchecked(0)->getHeight();
  336. for (i = customComps.size(); --i >= 0;)
  337. {
  338. Component* c = customComps.getUnchecked(i);
  339. w = jmax (w, (c->getWidth() * 100) / 80);
  340. h += 10 + c->getHeight();
  341. if (c->getName().isNotEmpty())
  342. h += labelHeight;
  343. }
  344. for (i = textBlocks.size(); --i >= 0;)
  345. {
  346. const AlertTextComp* const ac = static_cast <const AlertTextComp*> (textBlocks.getUnchecked(i));
  347. w = jmax (w, ac->getPreferredWidth());
  348. }
  349. w = jmin (w, (int) (getParentWidth() * 0.7f));
  350. for (i = textBlocks.size(); --i >= 0;)
  351. {
  352. AlertTextComp* const ac = static_cast <AlertTextComp*> (textBlocks.getUnchecked(i));
  353. ac->updateLayout ((int) (w * 0.8f));
  354. h += ac->getHeight() + 10;
  355. }
  356. h = jmin (getParentHeight() - 50, h);
  357. if (onlyIncreaseSize)
  358. {
  359. w = jmax (w, getWidth());
  360. h = jmax (h, getHeight());
  361. }
  362. if (! isVisible())
  363. {
  364. centreAroundComponent (associatedComponent, w, h);
  365. }
  366. else
  367. {
  368. const int cx = getX() + getWidth() / 2;
  369. const int cy = getY() + getHeight() / 2;
  370. setBounds (cx - w / 2,
  371. cy - h / 2,
  372. w, h);
  373. }
  374. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  375. const int spacer = 16;
  376. int totalWidth = -spacer;
  377. for (i = buttons.size(); --i >= 0;)
  378. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  379. int x = (w - totalWidth) / 2;
  380. int y = (int) (getHeight() * 0.95f);
  381. for (i = 0; i < buttons.size(); ++i)
  382. {
  383. TextButton* const c = buttons.getUnchecked(i);
  384. int ny = proportionOfHeight (0.95f) - c->getHeight();
  385. c->setTopLeftPosition (x, ny);
  386. if (ny < y)
  387. y = ny;
  388. x += c->getWidth() + spacer;
  389. c->toFront (false);
  390. }
  391. y = textBottom;
  392. for (i = 0; i < allComps.size(); ++i)
  393. {
  394. Component* const c = allComps.getUnchecked(i);
  395. h = 22;
  396. const int comboIndex = comboBoxes.indexOf (dynamic_cast <ComboBox*> (c));
  397. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  398. y += labelHeight;
  399. const int tbIndex = textBoxes.indexOf (dynamic_cast <TextEditor*> (c));
  400. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  401. y += labelHeight;
  402. if (customComps.contains (c))
  403. {
  404. if (c->getName().isNotEmpty())
  405. y += labelHeight;
  406. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  407. h = c->getHeight();
  408. }
  409. else if (textBlocks.contains (c))
  410. {
  411. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  412. h = c->getHeight();
  413. }
  414. else
  415. {
  416. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  417. }
  418. y += h + 10;
  419. }
  420. setWantsKeyboardFocus (getNumChildComponents() == 0);
  421. }
  422. bool AlertWindow::containsAnyExtraComponents() const
  423. {
  424. return allComps.size() > 0;
  425. }
  426. //==============================================================================
  427. void AlertWindow::mouseDown (const MouseEvent& e)
  428. {
  429. dragger.startDraggingComponent (this, e);
  430. }
  431. void AlertWindow::mouseDrag (const MouseEvent& e)
  432. {
  433. dragger.dragComponent (this, e, &constrainer);
  434. }
  435. bool AlertWindow::keyPressed (const KeyPress& key)
  436. {
  437. for (int i = buttons.size(); --i >= 0;)
  438. {
  439. TextButton* const b = buttons.getUnchecked(i);
  440. if (b->isRegisteredForShortcut (key))
  441. {
  442. b->triggerClick();
  443. return true;
  444. }
  445. }
  446. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  447. {
  448. exitModalState (0);
  449. return true;
  450. }
  451. else if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  452. {
  453. buttons.getUnchecked(0)->triggerClick();
  454. return true;
  455. }
  456. return false;
  457. }
  458. void AlertWindow::lookAndFeelChanged()
  459. {
  460. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  461. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  462. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  463. }
  464. int AlertWindow::getDesktopWindowStyleFlags() const
  465. {
  466. return getLookAndFeel().getAlertBoxWindowFlags();
  467. }
  468. //==============================================================================
  469. class AlertWindowInfo
  470. {
  471. public:
  472. AlertWindowInfo (const String& title_, const String& message_, Component* component,
  473. AlertWindow::AlertIconType iconType_, int numButtons_,
  474. ModalComponentManager::Callback* callback_, bool modal_)
  475. : title (title_), message (message_), iconType (iconType_),
  476. numButtons (numButtons_), returnValue (0), associatedComponent (component),
  477. callback (callback_), modal (modal_)
  478. {
  479. }
  480. String title, message, button1, button2, button3;
  481. int invoke() const
  482. {
  483. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  484. return returnValue;
  485. }
  486. private:
  487. AlertWindow::AlertIconType iconType;
  488. int numButtons, returnValue;
  489. WeakReference<Component> associatedComponent;
  490. ModalComponentManager::Callback* callback;
  491. bool modal;
  492. void show()
  493. {
  494. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  495. : LookAndFeel::getDefaultLookAndFeel();
  496. ScopedPointer <Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  497. iconType, numButtons, associatedComponent));
  498. jassert (alertBox != nullptr); // you have to return one of these!
  499. #if JUCE_MODAL_LOOPS_PERMITTED
  500. if (modal)
  501. {
  502. returnValue = alertBox->runModalLoop();
  503. }
  504. else
  505. #endif
  506. {
  507. alertBox->enterModalState (true, callback, true);
  508. alertBox.release();
  509. }
  510. }
  511. static void* showCallback (void* userData)
  512. {
  513. static_cast <AlertWindowInfo*> (userData)->show();
  514. return nullptr;
  515. }
  516. };
  517. #if JUCE_MODAL_LOOPS_PERMITTED
  518. void AlertWindow::showMessageBox (AlertIconType iconType,
  519. const String& title,
  520. const String& message,
  521. const String& buttonText,
  522. Component* associatedComponent)
  523. {
  524. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  525. {
  526. NativeMessageBox::showMessageBox (iconType, title, message, associatedComponent);
  527. }
  528. else
  529. {
  530. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, 0, true);
  531. info.button1 = buttonText.isEmpty() ? TRANS("ok") : buttonText;
  532. info.invoke();
  533. }
  534. }
  535. #endif
  536. void AlertWindow::showMessageBoxAsync (AlertIconType iconType,
  537. const String& title,
  538. const String& message,
  539. const String& buttonText,
  540. Component* associatedComponent)
  541. {
  542. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  543. {
  544. return NativeMessageBox::showMessageBoxAsync (iconType, title, message, associatedComponent);
  545. }
  546. else
  547. {
  548. AlertWindowInfo info (title, message, associatedComponent, iconType, 1, 0, false);
  549. info.button1 = buttonText.isEmpty() ? TRANS("ok") : buttonText;
  550. info.invoke();
  551. }
  552. }
  553. bool AlertWindow::showOkCancelBox (AlertIconType iconType,
  554. const String& title,
  555. const String& message,
  556. const String& button1Text,
  557. const String& button2Text,
  558. Component* associatedComponent,
  559. ModalComponentManager::Callback* callback)
  560. {
  561. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  562. {
  563. return NativeMessageBox::showOkCancelBox (iconType, title, message, associatedComponent, callback);
  564. }
  565. else
  566. {
  567. AlertWindowInfo info (title, message, associatedComponent, iconType, 2, callback, callback == nullptr);
  568. info.button1 = button1Text.isEmpty() ? TRANS("ok") : button1Text;
  569. info.button2 = button2Text.isEmpty() ? TRANS("cancel") : button2Text;
  570. return info.invoke() != 0;
  571. }
  572. }
  573. int AlertWindow::showYesNoCancelBox (AlertIconType iconType,
  574. const String& title,
  575. const String& message,
  576. const String& button1Text,
  577. const String& button2Text,
  578. const String& button3Text,
  579. Component* associatedComponent,
  580. ModalComponentManager::Callback* callback)
  581. {
  582. if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
  583. {
  584. return NativeMessageBox::showYesNoCancelBox (iconType, title, message, associatedComponent, callback);
  585. }
  586. else
  587. {
  588. AlertWindowInfo info (title, message, associatedComponent, iconType, 3, callback, callback == nullptr);
  589. info.button1 = button1Text.isEmpty() ? TRANS("yes") : button1Text;
  590. info.button2 = button2Text.isEmpty() ? TRANS("no") : button2Text;
  591. info.button3 = button3Text.isEmpty() ? TRANS("cancel") : button3Text;
  592. return info.invoke();
  593. }
  594. }
  595. #if JUCE_MODAL_LOOPS_PERMITTED
  596. bool AlertWindow::showNativeDialogBox (const String& title,
  597. const String& bodyText,
  598. bool isOkCancel)
  599. {
  600. if (isOkCancel)
  601. {
  602. return NativeMessageBox::showOkCancelBox (AlertWindow::NoIcon, title, bodyText);
  603. }
  604. else
  605. {
  606. NativeMessageBox::showMessageBox (AlertWindow::NoIcon, title, bodyText);
  607. return true;
  608. }
  609. }
  610. #endif
  611. END_JUCE_NAMESPACE