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.

712 lines
22KB

  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. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  37. if (message.isEmpty())
  38. text = " "; // to force an update if the message is empty
  39. setMessage (message);
  40. AlertWindow::lookAndFeelChanged();
  41. constrainer.setMinimumOnscreenAmounts (0x10000, 0x10000, 0x10000, 0x10000);
  42. }
  43. AlertWindow::~AlertWindow()
  44. {
  45. removeAllChildren();
  46. }
  47. void AlertWindow::userTriedToCloseWindow()
  48. {
  49. if (escapeKeyCancels || buttons.size() > 0)
  50. exitModalState (0);
  51. }
  52. //==============================================================================
  53. void AlertWindow::setMessage (const String& message)
  54. {
  55. const String newMessage (message.substring (0, 2048));
  56. if (text != newMessage)
  57. {
  58. text = newMessage;
  59. updateLayout (true);
  60. repaint();
  61. }
  62. }
  63. //==============================================================================
  64. void AlertWindow::buttonClicked (Button* button)
  65. {
  66. if (Component* parent = button->getParentComponent())
  67. parent->exitModalState (button->getCommandID());
  68. }
  69. //==============================================================================
  70. void AlertWindow::addButton (const String& name,
  71. const int returnValue,
  72. const KeyPress& shortcutKey1,
  73. const KeyPress& shortcutKey2)
  74. {
  75. TextButton* const b = new TextButton (name, String::empty);
  76. buttons.add (b);
  77. b->setWantsKeyboardFocus (true);
  78. b->setMouseClickGrabsKeyboardFocus (false);
  79. b->setCommandToTrigger (0, returnValue, false);
  80. b->addShortcut (shortcutKey1);
  81. b->addShortcut (shortcutKey2);
  82. b->addListener (this);
  83. b->changeWidthToFitText (getLookAndFeel().getAlertWindowButtonHeight());
  84. addAndMakeVisible (b, 0);
  85. updateLayout (false);
  86. }
  87. int AlertWindow::getNumButtons() const
  88. {
  89. return buttons.size();
  90. }
  91. void AlertWindow::triggerButtonClick (const String& buttonName)
  92. {
  93. for (int i = buttons.size(); --i >= 0;)
  94. {
  95. TextButton* const b = buttons.getUnchecked(i);
  96. if (buttonName == b->getName())
  97. {
  98. b->triggerClick();
  99. break;
  100. }
  101. }
  102. }
  103. void AlertWindow::setEscapeKeyCancels (bool shouldEscapeKeyCancel)
  104. {
  105. escapeKeyCancels = shouldEscapeKeyCancel;
  106. }
  107. //==============================================================================
  108. void AlertWindow::addTextEditor (const String& name,
  109. const String& initialContents,
  110. const String& onScreenLabel,
  111. const bool isPasswordBox)
  112. {
  113. TextEditor* ed = new TextEditor (name, isPasswordBox ? getDefaultPasswordChar() : 0);
  114. ed->setSelectAllWhenFocused (true);
  115. ed->setEscapeAndReturnKeysConsumed (false);
  116. textBoxes.add (ed);
  117. allComps.add (ed);
  118. ed->setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  119. ed->setFont (getLookAndFeel().getAlertWindowMessageFont());
  120. ed->setText (initialContents);
  121. ed->setCaretPosition (initialContents.length());
  122. addAndMakeVisible (ed);
  123. textboxNames.add (onScreenLabel);
  124. updateLayout (false);
  125. }
  126. TextEditor* AlertWindow::getTextEditor (const String& nameOfTextEditor) const
  127. {
  128. for (int i = textBoxes.size(); --i >= 0;)
  129. if (textBoxes.getUnchecked(i)->getName() == nameOfTextEditor)
  130. return textBoxes.getUnchecked(i);
  131. return nullptr;
  132. }
  133. String AlertWindow::getTextEditorContents (const String& nameOfTextEditor) const
  134. {
  135. if (TextEditor* const t = getTextEditor (nameOfTextEditor))
  136. return t->getText();
  137. return String::empty;
  138. }
  139. //==============================================================================
  140. void AlertWindow::addComboBox (const String& name,
  141. const StringArray& items,
  142. const String& onScreenLabel)
  143. {
  144. ComboBox* const cb = new ComboBox (name);
  145. comboBoxes.add (cb);
  146. allComps.add (cb);
  147. cb->addItemList (items, 1);
  148. addAndMakeVisible (cb);
  149. cb->setSelectedItemIndex (0);
  150. comboBoxNames.add (onScreenLabel);
  151. updateLayout (false);
  152. }
  153. ComboBox* AlertWindow::getComboBoxComponent (const String& nameOfList) const
  154. {
  155. for (int i = comboBoxes.size(); --i >= 0;)
  156. if (comboBoxes.getUnchecked(i)->getName() == nameOfList)
  157. return comboBoxes.getUnchecked(i);
  158. return nullptr;
  159. }
  160. //==============================================================================
  161. class AlertTextComp : public TextEditor
  162. {
  163. public:
  164. AlertTextComp (const String& message,
  165. const Font& font)
  166. {
  167. setReadOnly (true);
  168. setMultiLine (true, true);
  169. setCaretVisible (false);
  170. setScrollbarsShown (true);
  171. lookAndFeelChanged();
  172. setWantsKeyboardFocus (false);
  173. setFont (font);
  174. setText (message, false);
  175. bestWidth = 2 * (int) std::sqrt (font.getHeight() * font.getStringWidth (message));
  176. setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
  177. setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  178. setColour (TextEditor::shadowColourId, Colours::transparentBlack);
  179. }
  180. int getPreferredWidth() const noexcept { return bestWidth; }
  181. void updateLayout (const int width)
  182. {
  183. AttributedString s;
  184. s.setJustification (Justification::topLeft);
  185. s.append (getText(), getFont());
  186. TextLayout text;
  187. text.createLayoutWithBalancedLineLengths (s, width - 8.0f);
  188. setSize (width, jmin (width, (int) (text.getHeight() + getFont().getHeight())));
  189. }
  190. private:
  191. int bestWidth;
  192. JUCE_DECLARE_NON_COPYABLE (AlertTextComp)
  193. };
  194. void AlertWindow::addTextBlock (const String& textBlock)
  195. {
  196. AlertTextComp* const c = new AlertTextComp (textBlock, getLookAndFeel().getAlertWindowMessageFont());
  197. textBlocks.add (c);
  198. allComps.add (c);
  199. addAndMakeVisible (c);
  200. updateLayout (false);
  201. }
  202. //==============================================================================
  203. void AlertWindow::addProgressBarComponent (double& progressValue)
  204. {
  205. ProgressBar* const pb = new ProgressBar (progressValue);
  206. progressBars.add (pb);
  207. allComps.add (pb);
  208. addAndMakeVisible (pb);
  209. updateLayout (false);
  210. }
  211. //==============================================================================
  212. void AlertWindow::addCustomComponent (Component* const component)
  213. {
  214. customComps.add (component);
  215. allComps.add (component);
  216. addAndMakeVisible (component);
  217. updateLayout (false);
  218. }
  219. int AlertWindow::getNumCustomComponents() const
  220. {
  221. return customComps.size();
  222. }
  223. Component* AlertWindow::getCustomComponent (const int index) const
  224. {
  225. return customComps [index];
  226. }
  227. Component* AlertWindow::removeCustomComponent (const int index)
  228. {
  229. Component* const c = getCustomComponent (index);
  230. if (c != nullptr)
  231. {
  232. customComps.removeFirstMatchingValue (c);
  233. allComps.removeFirstMatchingValue (c);
  234. removeChildComponent (c);
  235. updateLayout (false);
  236. }
  237. return c;
  238. }
  239. //==============================================================================
  240. void AlertWindow::paint (Graphics& g)
  241. {
  242. getLookAndFeel().drawAlertBox (g, *this, textArea, textLayout);
  243. g.setColour (findColour (textColourId));
  244. g.setFont (getLookAndFeel().getAlertWindowFont());
  245. for (int i = textBoxes.size(); --i >= 0;)
  246. {
  247. const TextEditor* const te = textBoxes.getUnchecked(i);
  248. g.drawFittedText (textboxNames[i],
  249. te->getX(), te->getY() - 14,
  250. te->getWidth(), 14,
  251. Justification::centredLeft, 1);
  252. }
  253. for (int i = comboBoxNames.size(); --i >= 0;)
  254. {
  255. const ComboBox* const cb = comboBoxes.getUnchecked(i);
  256. g.drawFittedText (comboBoxNames[i],
  257. cb->getX(), cb->getY() - 14,
  258. cb->getWidth(), 14,
  259. Justification::centredLeft, 1);
  260. }
  261. for (int i = customComps.size(); --i >= 0;)
  262. {
  263. const Component* const c = customComps.getUnchecked(i);
  264. g.drawFittedText (c->getName(),
  265. c->getX(), c->getY() - 14,
  266. c->getWidth(), 14,
  267. Justification::centredLeft, 1);
  268. }
  269. }
  270. void AlertWindow::updateLayout (const bool onlyIncreaseSize)
  271. {
  272. const int titleH = 24;
  273. const int iconWidth = 80;
  274. const Font font (getLookAndFeel().getAlertWindowMessageFont());
  275. const int wid = jmax (font.getStringWidth (text),
  276. font.getStringWidth (getName()));
  277. const int sw = (int) std::sqrt (font.getHeight() * wid);
  278. int 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(), font.withHeight (font.getHeight() * 1.1f).boldened());
  284. if (text.isNotEmpty())
  285. attributedText.append ("\n\n" + text, font);
  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. const int textLayoutH = (int) textLayout.getHeight();
  301. const int textBottom = 16 + titleH + textLayoutH;
  302. int h = textBottom;
  303. int buttonW = 40;
  304. for (int i = 0; i < buttons.size(); ++i)
  305. buttonW += 16 + buttons.getUnchecked(i)->getWidth();
  306. w = jmax (buttonW, w);
  307. h += (textBoxes.size() + comboBoxes.size() + progressBars.size()) * 50;
  308. if (buttons.size() > 0)
  309. h += 20 + buttons.getUnchecked(0)->getHeight();
  310. for (int i = customComps.size(); --i >= 0;)
  311. {
  312. Component* c = customComps.getUnchecked(i);
  313. w = jmax (w, (c->getWidth() * 100) / 80);
  314. h += 10 + c->getHeight();
  315. if (c->getName().isNotEmpty())
  316. h += labelHeight;
  317. }
  318. for (int i = textBlocks.size(); --i >= 0;)
  319. {
  320. const AlertTextComp* const ac = static_cast <const AlertTextComp*> (textBlocks.getUnchecked(i));
  321. w = jmax (w, ac->getPreferredWidth());
  322. }
  323. w = jmin (w, (int) (getParentWidth() * 0.7f));
  324. for (int i = textBlocks.size(); --i >= 0;)
  325. {
  326. AlertTextComp* const ac = static_cast <AlertTextComp*> (textBlocks.getUnchecked(i));
  327. ac->updateLayout ((int) (w * 0.8f));
  328. h += ac->getHeight() + 10;
  329. }
  330. h = jmin (getParentHeight() - 50, h);
  331. if (onlyIncreaseSize)
  332. {
  333. w = jmax (w, getWidth());
  334. h = jmax (h, getHeight());
  335. }
  336. if (! isVisible())
  337. {
  338. centreAroundComponent (associatedComponent, w, h);
  339. }
  340. else
  341. {
  342. const int cx = getX() + getWidth() / 2;
  343. const int cy = getY() + getHeight() / 2;
  344. setBounds (cx - w / 2,
  345. cy - h / 2,
  346. w, h);
  347. }
  348. textArea.setBounds (edgeGap, edgeGap, w - (edgeGap * 2), h - edgeGap);
  349. const int spacer = 16;
  350. int totalWidth = -spacer;
  351. for (int i = buttons.size(); --i >= 0;)
  352. totalWidth += buttons.getUnchecked(i)->getWidth() + spacer;
  353. int x = (w - totalWidth) / 2;
  354. int y = (int) (getHeight() * 0.95f);
  355. for (int i = 0; i < buttons.size(); ++i)
  356. {
  357. TextButton* const c = buttons.getUnchecked(i);
  358. int ny = proportionOfHeight (0.95f) - c->getHeight();
  359. c->setTopLeftPosition (x, ny);
  360. if (ny < y)
  361. y = ny;
  362. x += c->getWidth() + spacer;
  363. c->toFront (false);
  364. }
  365. y = textBottom;
  366. for (int i = 0; i < allComps.size(); ++i)
  367. {
  368. Component* const c = allComps.getUnchecked(i);
  369. h = 22;
  370. const int comboIndex = comboBoxes.indexOf (dynamic_cast <ComboBox*> (c));
  371. if (comboIndex >= 0 && comboBoxNames [comboIndex].isNotEmpty())
  372. y += labelHeight;
  373. const int tbIndex = textBoxes.indexOf (dynamic_cast <TextEditor*> (c));
  374. if (tbIndex >= 0 && textboxNames[tbIndex].isNotEmpty())
  375. y += labelHeight;
  376. if (customComps.contains (c))
  377. {
  378. if (c->getName().isNotEmpty())
  379. y += labelHeight;
  380. c->setTopLeftPosition (proportionOfWidth (0.1f), y);
  381. h = c->getHeight();
  382. }
  383. else if (textBlocks.contains (c))
  384. {
  385. c->setTopLeftPosition ((getWidth() - c->getWidth()) / 2, y);
  386. h = c->getHeight();
  387. }
  388. else
  389. {
  390. c->setBounds (proportionOfWidth (0.1f), y, proportionOfWidth (0.8f), h);
  391. }
  392. y += h + 10;
  393. }
  394. setWantsKeyboardFocus (getNumChildComponents() == 0);
  395. }
  396. bool AlertWindow::containsAnyExtraComponents() const
  397. {
  398. return allComps.size() > 0;
  399. }
  400. //==============================================================================
  401. void AlertWindow::mouseDown (const MouseEvent& e)
  402. {
  403. dragger.startDraggingComponent (this, e);
  404. }
  405. void AlertWindow::mouseDrag (const MouseEvent& e)
  406. {
  407. dragger.dragComponent (this, e, &constrainer);
  408. }
  409. bool AlertWindow::keyPressed (const KeyPress& key)
  410. {
  411. for (int i = buttons.size(); --i >= 0;)
  412. {
  413. TextButton* const b = buttons.getUnchecked(i);
  414. if (b->isRegisteredForShortcut (key))
  415. {
  416. b->triggerClick();
  417. return true;
  418. }
  419. }
  420. if (key.isKeyCode (KeyPress::escapeKey) && escapeKeyCancels && buttons.size() == 0)
  421. {
  422. exitModalState (0);
  423. return true;
  424. }
  425. else if (key.isKeyCode (KeyPress::returnKey) && buttons.size() == 1)
  426. {
  427. buttons.getUnchecked(0)->triggerClick();
  428. return true;
  429. }
  430. return false;
  431. }
  432. void AlertWindow::lookAndFeelChanged()
  433. {
  434. const int newFlags = getLookAndFeel().getAlertBoxWindowFlags();
  435. setUsingNativeTitleBar ((newFlags & ComponentPeer::windowHasTitleBar) != 0);
  436. setDropShadowEnabled (isOpaque() && (newFlags & ComponentPeer::windowHasDropShadow) != 0);
  437. updateLayout (false);
  438. }
  439. int AlertWindow::getDesktopWindowStyleFlags() const
  440. {
  441. return getLookAndFeel().getAlertBoxWindowFlags();
  442. }
  443. //==============================================================================
  444. class AlertWindowInfo
  445. {
  446. public:
  447. AlertWindowInfo (const String& t, const String& m, Component* component,
  448. AlertWindow::AlertIconType icon, int numButts,
  449. ModalComponentManager::Callback* cb, bool runModally)
  450. : title (t), message (m), iconType (icon), numButtons (numButts),
  451. returnValue (0), associatedComponent (component),
  452. callback (cb), modal (runModally)
  453. {
  454. }
  455. String title, message, button1, button2, button3;
  456. int invoke() const
  457. {
  458. MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
  459. return returnValue;
  460. }
  461. private:
  462. AlertWindow::AlertIconType iconType;
  463. int numButtons, returnValue;
  464. WeakReference<Component> associatedComponent;
  465. ModalComponentManager::Callback* callback;
  466. bool modal;
  467. void show()
  468. {
  469. LookAndFeel& lf = associatedComponent != nullptr ? associatedComponent->getLookAndFeel()
  470. : LookAndFeel::getDefaultLookAndFeel();
  471. ScopedPointer <Component> alertBox (lf.createAlertWindow (title, message, button1, button2, button3,
  472. iconType, numButtons, associatedComponent));
  473. jassert (alertBox != nullptr); // you have to return one of these!
  474. #if JUCE_MODAL_LOOPS_PERMITTED
  475. if (modal)
  476. {
  477. returnValue = alertBox->runModalLoop();
  478. }
  479. else
  480. #endif
  481. {
  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