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.

701 lines
20KB

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