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.

699 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. #include "jucer_ProjectContentComponent.h"
  19. #include "../Application/jucer_MainWindow.h"
  20. #include "../Application/jucer_Application.h"
  21. #include "../Code Editor/jucer_SourceCodeEditor.h"
  22. #include "jucer_ConfigPage.h"
  23. #include "jucer_TreeViewTypes.h"
  24. #include "../Project Saving/jucer_ProjectExporter.h"
  25. //==============================================================================
  26. class FileTreeTab : public TreePanelBase
  27. {
  28. public:
  29. FileTreeTab (Project& project)
  30. : TreePanelBase ("treeViewState_" + project.getProjectUID())
  31. {
  32. tree.setMultiSelectEnabled (true);
  33. setRoot (new GroupTreeViewItem (project.getMainGroup()));
  34. }
  35. };
  36. //==============================================================================
  37. class ConfigTreeTab : public TreePanelBase
  38. {
  39. public:
  40. ConfigTreeTab (Project& project)
  41. : TreePanelBase ("settingsTreeViewState_" + project.getProjectUID())
  42. {
  43. tree.setMultiSelectEnabled (false);
  44. setRoot (createProjectConfigTreeViewRoot (project));
  45. if (tree.getNumSelectedItems() == 0)
  46. tree.getRootItem()->setSelected (true, true);
  47. #if JUCE_MAC || JUCE_WINDOWS
  48. addAndMakeVisible (&openProjectButton);
  49. openProjectButton.setCommandToTrigger (commandManager, CommandIDs::openInIDE, true);
  50. openProjectButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::openInIDE));
  51. openProjectButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
  52. addAndMakeVisible (&saveAndOpenButton);
  53. saveAndOpenButton.setCommandToTrigger (commandManager, CommandIDs::saveAndOpenInIDE, true);
  54. saveAndOpenButton.setButtonText (commandManager->getNameOfCommand (CommandIDs::saveAndOpenInIDE));
  55. saveAndOpenButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f));
  56. #endif
  57. }
  58. void resized()
  59. {
  60. Rectangle<int> r (getAvailableBounds());
  61. r.removeFromBottom (6);
  62. if (saveAndOpenButton.isVisible())
  63. saveAndOpenButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  64. if (openProjectButton.isVisible())
  65. openProjectButton.setBounds (r.removeFromBottom (28).reduced (20, 3));
  66. tree.setBounds (r);
  67. }
  68. TextButton openProjectButton, saveAndOpenButton;
  69. };
  70. //==============================================================================
  71. class LogoComponent : public Component
  72. {
  73. public:
  74. LogoComponent()
  75. {
  76. Rectangle<float> iconSize (getIcons().mainJuceLogo.getBounds());
  77. setSize (400, (int) (400 * iconSize.getWidth() / iconSize.getHeight()));
  78. }
  79. void paint (Graphics& g)
  80. {
  81. g.setColour (findColour (mainBackgroundColourId).contrasting (0.3f));
  82. g.fillPath (getIcons().mainJuceLogo,
  83. RectanglePlacement (RectanglePlacement::centred)
  84. .getTransformToFit (getIcons().mainJuceLogo.getBounds(),
  85. getLocalBounds().toFloat()));
  86. }
  87. };
  88. //==============================================================================
  89. ProjectContentComponent::ProjectContentComponent()
  90. : project (nullptr),
  91. currentDocument (nullptr),
  92. treeViewTabs (TabbedButtonBar::TabsAtTop)
  93. {
  94. setOpaque (true);
  95. setWantsKeyboardFocus (true);
  96. addAndMakeVisible (logo = new LogoComponent());
  97. treeSizeConstrainer.setMinimumWidth (200);
  98. treeSizeConstrainer.setMaximumWidth (500);
  99. treeViewTabs.setOutline (0);
  100. treeViewTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.3);
  101. JucerApplication::getApp().openDocumentManager.addListener (this);
  102. }
  103. ProjectContentComponent::~ProjectContentComponent()
  104. {
  105. JucerApplication::getApp().openDocumentManager.removeListener (this);
  106. logo = nullptr;
  107. setProject (nullptr);
  108. contentView = nullptr;
  109. removeChildComponent (&bubbleMessage);
  110. jassert (getNumChildComponents() <= 1);
  111. }
  112. void ProjectContentComponent::paint (Graphics& g)
  113. {
  114. g.fillAll (findColour (mainBackgroundColourId));
  115. }
  116. void ProjectContentComponent::paintOverChildren (Graphics& g)
  117. {
  118. if (resizerBar != nullptr)
  119. {
  120. const int shadowSize = 15;
  121. const int x = resizerBar->getRight();
  122. ColourGradient cg (Colours::black.withAlpha (0.25f), (float) x, 0,
  123. Colours::transparentBlack, (float) (x - shadowSize), 0, false);
  124. cg.addColour (0.4, Colours::black.withAlpha (0.07f));
  125. cg.addColour (0.6, Colours::black.withAlpha (0.02f));
  126. g.setGradientFill (cg);
  127. g.fillRect (x - shadowSize, 0, shadowSize, getHeight());
  128. }
  129. }
  130. void ProjectContentComponent::resized()
  131. {
  132. Rectangle<int> r (getLocalBounds());
  133. if (treeViewTabs.isVisible())
  134. treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth()));
  135. if (resizerBar != nullptr)
  136. resizerBar->setBounds (r.removeFromLeft (4));
  137. if (contentView != nullptr)
  138. {
  139. contentView->setBounds (r);
  140. logo->setVisible (false);
  141. }
  142. else
  143. {
  144. logo->setBounds (r.reduced (r.getWidth() / 4, r.getHeight() / 4));
  145. logo->setVisible (true);
  146. }
  147. }
  148. void ProjectContentComponent::lookAndFeelChanged()
  149. {
  150. const Colour tabColour (findColour (mainBackgroundColourId));
  151. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  152. treeViewTabs.setTabBackgroundColour (i, tabColour);
  153. repaint();
  154. }
  155. void ProjectContentComponent::childBoundsChanged (Component* child)
  156. {
  157. if (child == &treeViewTabs)
  158. resized();
  159. }
  160. void ProjectContentComponent::setProject (Project* newProject)
  161. {
  162. if (project != newProject)
  163. {
  164. PropertiesFile& settings = getAppProperties();
  165. if (project != nullptr)
  166. project->removeChangeListener (this);
  167. contentView = nullptr;
  168. resizerBar = nullptr;
  169. if (project != nullptr && treeViewTabs.isShowing())
  170. {
  171. if (treeViewTabs.getWidth() > 0)
  172. settings.setValue ("projectTreeviewWidth_" + project->getProjectUID(), treeViewTabs.getWidth());
  173. settings.setValue ("lastTab_" + project->getProjectUID(), treeViewTabs.getCurrentTabName());
  174. }
  175. treeViewTabs.clearTabs();
  176. project = newProject;
  177. if (project != nullptr)
  178. {
  179. addAndMakeVisible (&treeViewTabs);
  180. createProjectTabs();
  181. const String lastTabName (settings.getValue ("lastTab_" + project->getProjectUID()));
  182. int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName);
  183. if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs())
  184. lastTabIndex = 1;
  185. treeViewTabs.setCurrentTabIndex (lastTabIndex);
  186. int lastTreeWidth = settings.getValue ("projectTreeviewWidth_" + project->getProjectUID()).getIntValue();
  187. if (lastTreeWidth < 150)
  188. lastTreeWidth = 240;
  189. treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight());
  190. addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer,
  191. ResizableEdgeComponent::rightEdge));
  192. project->addChangeListener (this);
  193. updateMissingFileStatuses();
  194. }
  195. else
  196. {
  197. treeViewTabs.setVisible (false);
  198. }
  199. resized();
  200. }
  201. }
  202. void ProjectContentComponent::createProjectTabs()
  203. {
  204. jassert (project != nullptr);
  205. const Colour tabColour (findColour (mainBackgroundColourId));
  206. treeViewTabs.addTab ("Files", tabColour, new FileTreeTab (*project), true);
  207. treeViewTabs.addTab ("Config", tabColour, new ConfigTreeTab (*project), true);
  208. }
  209. TreeView* ProjectContentComponent::getFilesTreeView() const
  210. {
  211. FileTreeTab* ft = dynamic_cast<FileTreeTab*> (treeViewTabs.getTabContentComponent (0));
  212. return ft != nullptr ? &(ft->tree) : nullptr;
  213. }
  214. ProjectTreeViewBase* ProjectContentComponent::getFilesTreeRoot() const
  215. {
  216. TreeView* tv = getFilesTreeView();
  217. return tv != nullptr ? dynamic_cast <ProjectTreeViewBase*> (tv->getRootItem()) : nullptr;
  218. }
  219. void ProjectContentComponent::saveTreeViewState()
  220. {
  221. for (int i = treeViewTabs.getNumTabs(); --i >= 0;)
  222. {
  223. TreePanelBase* t = dynamic_cast<TreePanelBase*> (treeViewTabs.getTabContentComponent (i));
  224. if (t != nullptr)
  225. t->saveOpenness();
  226. }
  227. }
  228. void ProjectContentComponent::saveOpenDocumentList()
  229. {
  230. if (project != nullptr)
  231. {
  232. ScopedPointer<XmlElement> xml (recentDocumentList.createXML());
  233. if (xml != nullptr)
  234. getAppProperties().setValue ("lastDocs_" + project->getProjectUID(), xml);
  235. }
  236. }
  237. void ProjectContentComponent::reloadLastOpenDocuments()
  238. {
  239. if (project != nullptr)
  240. {
  241. ScopedPointer<XmlElement> xml (getAppProperties().getXmlValue ("lastDocs_" + project->getProjectUID()));
  242. if (xml != nullptr)
  243. {
  244. recentDocumentList.restoreFromXML (*project, *xml);
  245. showDocument (recentDocumentList.getCurrentDocument(), true);
  246. }
  247. }
  248. }
  249. void ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document)
  250. {
  251. hideDocument (document);
  252. }
  253. void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*)
  254. {
  255. updateMissingFileStatuses();
  256. }
  257. void ProjectContentComponent::updateMissingFileStatuses()
  258. {
  259. ProjectTreeViewBase* p = getFilesTreeRoot();
  260. if (p != nullptr)
  261. p->checkFileStatus();
  262. }
  263. bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus)
  264. {
  265. return getCurrentFile() == f
  266. || showDocument (JucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus);
  267. }
  268. File ProjectContentComponent::getCurrentFile() const
  269. {
  270. return currentDocument != nullptr ? currentDocument->getFile()
  271. : File::nonexistent;
  272. }
  273. bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  274. {
  275. if (doc == nullptr)
  276. return false;
  277. if (doc->hasFileBeenModifiedExternally())
  278. doc->reloadFromFile();
  279. if (doc == getCurrentDocument() && contentView != nullptr)
  280. {
  281. if (grabFocus)
  282. contentView->grabKeyboardFocus();
  283. return true;
  284. }
  285. recentDocumentList.newDocumentOpened (doc);
  286. bool opened = setEditorComponent (doc->createEditor(), doc);
  287. if (opened && grabFocus)
  288. contentView->grabKeyboardFocus();
  289. return opened;
  290. }
  291. void ProjectContentComponent::hideEditor()
  292. {
  293. currentDocument = nullptr;
  294. contentView = nullptr;
  295. updateMainWindowTitle();
  296. commandManager->commandStatusChanged();
  297. resized();
  298. }
  299. void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc)
  300. {
  301. if (doc == currentDocument)
  302. {
  303. OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc);
  304. if (replacement != nullptr)
  305. showDocument (replacement, true);
  306. else
  307. hideEditor();
  308. }
  309. }
  310. bool ProjectContentComponent::setEditorComponent (Component* editor,
  311. OpenDocumentManager::Document* doc)
  312. {
  313. if (editor != nullptr)
  314. {
  315. contentView = nullptr;
  316. contentView = editor;
  317. currentDocument = doc;
  318. addAndMakeVisible (editor);
  319. resized();
  320. updateMainWindowTitle();
  321. commandManager->commandStatusChanged();
  322. return true;
  323. }
  324. updateMainWindowTitle();
  325. return false;
  326. }
  327. bool ProjectContentComponent::goToPreviousFile()
  328. {
  329. OpenDocumentManager::Document* currentSourceDoc = recentDocumentList.getCurrentDocument();
  330. if (currentSourceDoc != nullptr && currentSourceDoc != getCurrentDocument())
  331. return showDocument (currentSourceDoc, true);
  332. else
  333. return showDocument (recentDocumentList.getPrevious(), true);
  334. }
  335. bool ProjectContentComponent::goToNextFile()
  336. {
  337. return showDocument (recentDocumentList.getNext(), true);
  338. }
  339. void ProjectContentComponent::updateMainWindowTitle()
  340. {
  341. MainWindow* mw = findParentComponentOfClass<MainWindow>();
  342. if (mw != nullptr)
  343. mw->updateTitle (currentDocument != nullptr ? currentDocument->getName() : String::empty);
  344. }
  345. ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget()
  346. {
  347. return findFirstTargetParentComponent();
  348. }
  349. void ProjectContentComponent::getAllCommands (Array <CommandID>& commands)
  350. {
  351. const CommandID ids[] = { CommandIDs::saveDocument,
  352. CommandIDs::closeDocument,
  353. CommandIDs::saveProject,
  354. CommandIDs::closeProject,
  355. CommandIDs::openInIDE,
  356. CommandIDs::saveAndOpenInIDE,
  357. CommandIDs::showFilePanel,
  358. CommandIDs::showConfigPanel,
  359. CommandIDs::goToPreviousDoc,
  360. CommandIDs::goToNextDoc,
  361. StandardApplicationCommandIDs::del };
  362. commands.addArray (ids, numElementsInArray (ids));
  363. }
  364. void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  365. {
  366. String documentName;
  367. if (currentDocument != nullptr)
  368. documentName = " '" + currentDocument->getName().substring (0, 32) + "'";
  369. switch (commandID)
  370. {
  371. case CommandIDs::saveProject:
  372. result.setInfo ("Save Project",
  373. "Saves the current project",
  374. CommandCategories::general, 0);
  375. result.setActive (project != nullptr);
  376. break;
  377. case CommandIDs::closeProject:
  378. result.setInfo ("Close Project",
  379. "Closes the current project",
  380. CommandCategories::general, 0);
  381. result.setActive (project != nullptr);
  382. break;
  383. case CommandIDs::saveDocument:
  384. result.setInfo ("Save" + documentName,
  385. "Saves the current document",
  386. CommandCategories::general, 0);
  387. result.setActive (currentDocument != nullptr || project != nullptr);
  388. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  389. break;
  390. case CommandIDs::closeDocument:
  391. result.setInfo ("Close" + documentName,
  392. "Closes the current document",
  393. CommandCategories::general, 0);
  394. result.setActive (currentDocument != nullptr);
  395. #if JUCE_MAC
  396. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  397. #else
  398. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  399. #endif
  400. break;
  401. case CommandIDs::goToPreviousDoc:
  402. result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0);
  403. result.setActive (recentDocumentList.canGoToPrevious());
  404. #if JUCE_MAC
  405. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  406. #else
  407. result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  408. #endif
  409. break;
  410. case CommandIDs::goToNextDoc:
  411. result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0);
  412. result.setActive (recentDocumentList.canGoToNext());
  413. #if JUCE_MAC
  414. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::commandModifier | ModifierKeys::ctrlModifier, 0));
  415. #else
  416. result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, ModifierKeys::ctrlModifier | ModifierKeys::shiftModifier, 0));
  417. #endif
  418. break;
  419. case CommandIDs::openInIDE:
  420. #if JUCE_MAC
  421. result.setInfo ("Open in XCode...",
  422. #elif JUCE_WINDOWS
  423. result.setInfo ("Open in Visual Studio...",
  424. #else
  425. result.setInfo ("Open as a Makefile...",
  426. #endif
  427. "Launches the project in an external IDE",
  428. CommandCategories::general, 0);
  429. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  430. break;
  431. case CommandIDs::saveAndOpenInIDE:
  432. #if JUCE_MAC
  433. result.setInfo ("Save Project and Open in XCode...",
  434. #elif JUCE_WINDOWS
  435. result.setInfo ("Save Project and Open in Visual Studio...",
  436. #else
  437. result.setInfo ("Save Project and Open as a Makefile...",
  438. #endif
  439. "Saves the project and launches it in an external IDE",
  440. CommandCategories::general, 0);
  441. result.setActive (ProjectExporter::canProjectBeLaunched (project));
  442. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  443. break;
  444. case CommandIDs::showFilePanel:
  445. result.setInfo ("Show File Panel",
  446. "Shows the tree of files for this project",
  447. CommandCategories::general, 0);
  448. result.setActive (project != nullptr);
  449. result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0));
  450. break;
  451. case CommandIDs::showConfigPanel:
  452. result.setInfo ("Show Config Panel",
  453. "Shows the build options for the project",
  454. CommandCategories::general, 0);
  455. result.setActive (project != nullptr);
  456. result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 0));
  457. break;
  458. case StandardApplicationCommandIDs::del:
  459. result.setInfo ("Delete Selected File", String::empty, CommandCategories::general, 0);
  460. result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0));
  461. result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0));
  462. result.setActive (dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent()) != nullptr);
  463. break;
  464. default:
  465. break;
  466. }
  467. }
  468. bool ProjectContentComponent::isCommandActive (const CommandID commandID)
  469. {
  470. return project != nullptr;
  471. }
  472. bool ProjectContentComponent::perform (const InvocationInfo& info)
  473. {
  474. switch (info.commandID)
  475. {
  476. case CommandIDs::saveProject:
  477. if (project != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  478. project->save (true, true);
  479. break;
  480. case CommandIDs::closeProject:
  481. {
  482. MainWindow* const mw = findParentComponentOfClass<MainWindow>();
  483. if (mw != nullptr && ! reinvokeCommandAfterClosingPropertyEditors (info))
  484. mw->closeCurrentProject();
  485. }
  486. break;
  487. case CommandIDs::saveDocument:
  488. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  489. {
  490. if (currentDocument != nullptr)
  491. currentDocument->save();
  492. else if (project != nullptr)
  493. project->save (true, true);
  494. }
  495. break;
  496. case CommandIDs::closeDocument:
  497. if (currentDocument != nullptr)
  498. JucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true);
  499. break;
  500. case CommandIDs::goToPreviousDoc:
  501. goToPreviousFile();
  502. break;
  503. case CommandIDs::goToNextDoc:
  504. goToNextFile();
  505. break;
  506. case CommandIDs::openInIDE:
  507. if (project != nullptr)
  508. {
  509. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  510. if (exporter != nullptr)
  511. exporter->launchProject();
  512. }
  513. break;
  514. case CommandIDs::saveAndOpenInIDE:
  515. if (project != nullptr)
  516. {
  517. if (! reinvokeCommandAfterClosingPropertyEditors (info))
  518. {
  519. if (project->save (true, true) == FileBasedDocument::savedOk)
  520. {
  521. ScopedPointer <ProjectExporter> exporter (ProjectExporter::createPlatformDefaultExporter (*project));
  522. if (exporter != nullptr)
  523. exporter->launchProject();
  524. }
  525. }
  526. }
  527. break;
  528. case CommandIDs::showFilePanel:
  529. treeViewTabs.setCurrentTabIndex (0);
  530. break;
  531. case CommandIDs::showConfigPanel:
  532. treeViewTabs.setCurrentTabIndex (1);
  533. break;
  534. case StandardApplicationCommandIDs::del:
  535. {
  536. TreePanelBase* const tree = dynamic_cast<TreePanelBase*> (treeViewTabs.getCurrentContentComponent());
  537. if (tree != nullptr)
  538. tree->deleteSelectedItems();
  539. }
  540. break;
  541. default:
  542. return false;
  543. }
  544. return true;
  545. }
  546. bool ProjectContentComponent::reinvokeCommandAfterClosingPropertyEditors (const InvocationInfo& info)
  547. {
  548. if (reinvokeCommandAfterCancellingModalComps (info))
  549. {
  550. grabKeyboardFocus(); // to force any open labels to close their text editors
  551. return true;
  552. }
  553. return false;
  554. }
  555. void ProjectContentComponent::showBubbleMessage (const Rectangle<int>& pos, const String& text)
  556. {
  557. addChildComponent (&bubbleMessage);
  558. bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f));
  559. bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f));
  560. bubbleMessage.setAlwaysOnTop (true);
  561. bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false);
  562. }