/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #include "../jucer_Headers.h" #include "jucer_ProjectContentComponent.h" #include "jucer_Module.h" #include "../Application/jucer_MainWindow.h" #include "../Application/jucer_Application.h" #include "../Application/jucer_DownloadCompileEngineThread.h" #include "../Code Editor/jucer_SourceCodeEditor.h" #include "../Utility/jucer_FilePathPropertyComponent.h" #include "jucer_TreeItemTypes.h" //============================================================================== class FileTreePanel : public TreePanelBase { public: FileTreePanel (Project& p) : TreePanelBase (&p, "fileTreeState") { tree.setMultiSelectEnabled (true); setRoot (new FileTreeItemTypes::GroupItem (p.getMainGroup())); } void updateMissingFileStatuses() { if (FileTreeItemTypes::ProjectTreeItemBase* p = dynamic_cast (rootItem.get())) p->checkFileStatus(); } }; //============================================================================== class ConfigTreePanel : public TreePanelBase { public: ConfigTreePanel (Project& p) : TreePanelBase (&p, "settingsTreeState") { tree.setMultiSelectEnabled (false); setRoot (new ConfigTreeItemTypes::RootItem (p)); if (tree.getNumSelectedItems() == 0) tree.getRootItem()->setSelected (true, true); #if JUCE_MAC || JUCE_WINDOWS ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager(); addAndMakeVisible (createExporterButton); createExporterButton.setCommandToTrigger (&commandManager, CommandIDs::createNewExporter, true); createExporterButton.setButtonText (commandManager.getNameOfCommand (CommandIDs::createNewExporter)); createExporterButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f)); addAndMakeVisible (openProjectButton); openProjectButton.setCommandToTrigger (&commandManager, CommandIDs::openInIDE, true); openProjectButton.setButtonText (commandManager.getNameOfCommand (CommandIDs::openInIDE)); openProjectButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f)); addAndMakeVisible (saveAndOpenButton); saveAndOpenButton.setCommandToTrigger (&commandManager, CommandIDs::saveAndOpenInIDE, true); saveAndOpenButton.setButtonText (commandManager.getNameOfCommand (CommandIDs::saveAndOpenInIDE)); saveAndOpenButton.setColour (TextButton::buttonColourId, Colours::white.withAlpha (0.5f)); #endif } void resized() override { Rectangle r (getAvailableBounds()); r.removeFromBottom (6); if (saveAndOpenButton.isVisible()) saveAndOpenButton.setBounds (r.removeFromBottom (30).reduced (16, 4)); if (openProjectButton.isVisible()) openProjectButton.setBounds (r.removeFromBottom (30).reduced (16, 4)); if (createExporterButton.isVisible()) { r.removeFromBottom (10); createExporterButton.setBounds (r.removeFromBottom (30).reduced (16, 4)); } tree.setBounds (r); } static void reselect (TreeViewItem& item) { item.setSelected (false, true); item.setSelected (true, true); } void showProjectSettings() { if (ConfigTreeItemTypes::ConfigTreeItemBase* root = dynamic_cast (rootItem.get())) if (root->isProjectSettings()) reselect (*root); } void showModules() { if (ConfigTreeItemTypes::ConfigTreeItemBase* mods = getModulesItem()) reselect (*mods); } void showModule (const String& moduleID) { if (ConfigTreeItemTypes::ConfigTreeItemBase* mods = getModulesItem()) { mods->setOpen (true); for (int i = mods->getNumSubItems(); --i >= 0;) if (ConfigTreeItemTypes::ModuleItem* m = dynamic_cast (mods->getSubItem (i))) if (m->moduleID == moduleID) reselect (*m); } } TextButton createExporterButton, openProjectButton, saveAndOpenButton; private: ConfigTreeItemTypes::ConfigTreeItemBase* getModulesItem() { if (ConfigTreeItemTypes::ConfigTreeItemBase* root = dynamic_cast (rootItem.get())) if (root->isProjectSettings()) if (ConfigTreeItemTypes::ConfigTreeItemBase* mods = dynamic_cast (root->getSubItem (0))) if (mods->isModulesList()) return mods; return nullptr; } }; //============================================================================== struct LogoComponent : public Component { LogoComponent() { ScopedPointer svg (XmlDocument::parse (BinaryData::background_logo_svg)); logo = Drawable::createFromSVG (*svg); } void paint (Graphics& g) override { g.setColour (findColour (mainBackgroundColourId).contrasting (0.3f)); Rectangle r (getLocalBounds()); g.setFont (15.0f); g.drawFittedText (getVersionInfo(), r.removeFromBottom (50), Justification::centredBottom, 3); logo->drawWithin (g, r.withTrimmedBottom (r.getHeight() / 4).toFloat(), RectanglePlacement (RectanglePlacement::centred), 1.0f); } static String getVersionInfo() { return SystemStats::getJUCEVersion() + newLine + ProjucerApplication::getApp().getVersionDescription(); } ScopedPointer logo; }; //============================================================================== ProjectContentComponent::ProjectContentComponent() : project (nullptr), currentDocument (nullptr), treeViewTabs (TabbedButtonBar::TabsAtTop) { setOpaque (true); setWantsKeyboardFocus (true); addAndMakeVisible (logo = new LogoComponent()); treeSizeConstrainer.setMinimumWidth (200); treeSizeConstrainer.setMaximumWidth (500); treeViewTabs.setOutline (0); treeViewTabs.getTabbedButtonBar().setMinimumTabScaleFactor (0.3); ProjucerApplication::getApp().openDocumentManager.addListener (this); Desktop::getInstance().addFocusChangeListener (this); startTimer (1600); } ProjectContentComponent::~ProjectContentComponent() { Desktop::getInstance().removeFocusChangeListener (this); killChildProcess(); ProjucerApplication::getApp().openDocumentManager.removeListener (this); logo = nullptr; setProject (nullptr); contentView = nullptr; removeChildComponent (&bubbleMessage); jassert (getNumChildComponents() <= 1); } void ProjectContentComponent::paint (Graphics& g) { ProjucerLookAndFeel::fillWithBackgroundTexture (*this, g); } void ProjectContentComponent::paintOverChildren (Graphics& g) { if (resizerBar != nullptr) { const int shadowSize = 15; const int x = resizerBar->getX(); ColourGradient cg (Colours::black.withAlpha (0.25f), (float) x, 0, Colours::transparentBlack, (float) (x - shadowSize), 0, false); cg.addColour (0.4, Colours::black.withAlpha (0.07f)); cg.addColour (0.6, Colours::black.withAlpha (0.02f)); g.setGradientFill (cg); g.fillRect (x - shadowSize, 0, shadowSize, getHeight()); } } void ProjectContentComponent::resized() { Rectangle r (getLocalBounds()); if (treeViewTabs.isVisible()) treeViewTabs.setBounds (r.removeFromLeft (treeViewTabs.getWidth())); if (resizerBar != nullptr) resizerBar->setBounds (r.withWidth (4)); if (contentView != nullptr) contentView->setBounds (r); if (logo != nullptr) logo->setBounds (r.reduced (r.getWidth() / 4, r.getHeight() / 4)); } void ProjectContentComponent::lookAndFeelChanged() { repaint(); } void ProjectContentComponent::childBoundsChanged (Component* child) { if (child == &treeViewTabs) resized(); } void ProjectContentComponent::setProject (Project* newProject) { if (project != newProject) { lastCrashMessage = String(); killChildProcess(); if (project != nullptr) project->removeChangeListener (this); contentView = nullptr; resizerBar = nullptr; deleteProjectTabs(); project = newProject; rebuildProjectTabs(); } } void ProjectContentComponent::rebuildProjectTabs() { deleteProjectTabs(); if (project != nullptr) { addAndMakeVisible (treeViewTabs); createProjectTabs(); PropertiesFile& settings = project->getStoredProperties(); const String lastTabName (settings.getValue ("lastTab")); int lastTabIndex = treeViewTabs.getTabNames().indexOf (lastTabName); if (lastTabIndex < 0 || lastTabIndex > treeViewTabs.getNumTabs()) lastTabIndex = 1; treeViewTabs.setCurrentTabIndex (lastTabIndex); int lastTreeWidth = settings.getValue ("projectPanelWidth").getIntValue(); if (lastTreeWidth < 150) lastTreeWidth = 240; treeViewTabs.setBounds (0, 0, lastTreeWidth, getHeight()); addAndMakeVisible (resizerBar = new ResizableEdgeComponent (&treeViewTabs, &treeSizeConstrainer, ResizableEdgeComponent::rightEdge)); resizerBar->setAlwaysOnTop (true); project->addChangeListener (this); updateMissingFileStatuses(); } else { treeViewTabs.setVisible (false); } resized(); } //============================================================================== struct BuildTabComponent : public ConcertinaPanel { BuildTabComponent (CompileEngineChildProcess* child, ProjucerAppClasses::ErrorListComp* errorList) : errorListComp (errorList) { CurrentActivitiesComp* activities = new CurrentActivitiesComp (child->activityList); ComponentListComp* comps = new ComponentListComp (*child); addPanel (-1, errorList, true); addPanel (-1, comps, true); addPanel (-1, activities, true); setMaximumPanelSize (activities, CurrentActivitiesComp::getMaxPanelHeight()); setPanelSize (errorList, 200, false); setPanelSize (comps, 300, false); } Component::SafePointer errorListComp; }; struct ProjucerDisabledComp : public Component, private Button::Listener { ProjucerDisabledComp (String message, bool loggedIn, bool showSubscribeButton, bool showSignInButton, bool showSwitchAccountButton, bool showDownloadButton) : isLoggedIn (loggedIn) { infoLabel.setColour (Label::textColourId, findColour (mainBackgroundColourId).contrasting (0.7f)); infoLabel.setJustificationType (Justification::centred); infoLabel.setText (message, dontSendNotification); addAndMakeVisible (infoLabel); if (showSubscribeButton) { subscribeButton = new TextButton (String ( "Subscribe...")); addAndMakeVisible (*subscribeButton); subscribeButton->addListener (this); } if (showSignInButton) { signInButton = new TextButton (String ( "Sign in...")); addAndMakeVisible (*signInButton); signInButton->addListener (this); } if (showSwitchAccountButton) { switchAccountButton = new TextButton (String ("Switch account...")); addAndMakeVisible (*switchAccountButton); switchAccountButton->addListener (this); } if (showDownloadButton) { downloadButton = new TextButton (String ("Download live-build engine")); addAndMakeVisible (*downloadButton); downloadButton->addListener (this); } } void resized() override { int infoWidth = proportionOfWidth (0.9f); int infoHeight = 100; infoLabel.centreWithSize (infoWidth, infoHeight); int buttonWidth = jmin (getWidth() - 10, 150); int buttonHeight = 22; int itemDistance = 10; int buttonCenterX = infoLabel.getBounds().getCentreX(); int buttonCenterY = infoLabel.getBottom() + itemDistance + buttonHeight / 2; if (subscribeButton.get() != nullptr) { subscribeButton->setSize (buttonWidth, buttonHeight); subscribeButton->setCentrePosition (buttonCenterX, buttonCenterY); buttonCenterY += itemDistance + buttonHeight; } if (signInButton.get() != nullptr) { signInButton->setSize (buttonWidth, buttonHeight); signInButton->setCentrePosition (buttonCenterX, buttonCenterY); buttonCenterY += itemDistance + buttonHeight; } if (switchAccountButton.get() != nullptr) { switchAccountButton->setSize (buttonWidth, buttonHeight); switchAccountButton->setCentrePosition (buttonCenterX, buttonCenterY); buttonCenterY += itemDistance + buttonHeight; } if (downloadButton.get() != nullptr) { downloadButton->setSize (buttonWidth, buttonHeight); downloadButton->setCentrePosition (buttonCenterX, buttonCenterY); } } void buttonClicked (Button* btn) override { if (btn == subscribeButton.get()) { URL ("http://www.juce.com/get-juce#indie").launchInDefaultBrowser(); } else if (btn == signInButton.get()) { ProjucerApplication::getApp().showLoginForm(); } else if (btn == switchAccountButton.get()) { ProjucerApplication::getApp().showLoginForm(); } else if (btn == downloadButton.get()) { if (DownloadCompileEngineThread::downloadAndInstall()) { if (! ProjucerLicenses::getInstance()->retryLoadDll()) { AlertWindow::showMessageBox(AlertWindow::WarningIcon, "Download and install", "Loading the live-build engine failed"); return; } // async! ProjucerApplication::getApp().showLoginForm(); // if sign in successful project tabs update, otherwise they were not auto parent = findParentComponentOfClass(); parent->rebuildProjectTabs(); } } } bool isLoggedIn; private: Label infoLabel { "info", String() }; ScopedPointer subscribeButton; ScopedPointer signInButton; ScopedPointer switchAccountButton; ScopedPointer downloadButton; }; struct EnableBuildComp : public Component { EnableBuildComp() { addAndMakeVisible (&enableButton); enableButton.setCommandToTrigger (&ProjucerApplication::getCommandManager(), CommandIDs::enableBuild, true); } void resized() override { enableButton.centreWithSize (jmin (getWidth() - 10, 150), 22); } void paint (Graphics& g) override { if (ProjectContentComponent* ppc = findParentComponentOfClass()) { g.setColour (findColour (mainBackgroundColourId).contrasting (0.7f)); g.setFont (13.0f); g.drawFittedText (ppc->lastCrashMessage, getLocalBounds().reduced (8).withBottom (enableButton.getY() - 20), Justification::centredBottom, 10); } } TextButton enableButton { "Restart Compiler" }; }; //============================================================================== Component* ProjectContentComponent::createBuildTab (CompileEngineChildProcess* child) { if (child != nullptr) { child->crashHandler = [this] (const String& m) { this->handleCrash (m); }; return new BuildTabComponent (child, new ProjucerAppClasses::ErrorListComp (child->errorList)); } jassert (project != nullptr); const auto osType = SystemStats::getOperatingSystemType(); const bool isMac = (osType & SystemStats::MacOSX) != 0; const bool isWin = (osType & SystemStats::Windows) != 0; const bool isLinux = (osType & SystemStats::Linux) != 0; if (! isMac && ! isWin && ! isLinux) return createDisabledBuildTabInfoOnly ( "Live-build features are not supported on your system.\n\n" "Please check supported platforms at www.juce.com!"); if (isLinux) return createDisabledBuildTabInfoOnly ( "Live-build features for Linux are under development.\n\n" "Please check for updates at www.juce.com!"); if (isMac) if (osType < SystemStats::MacOSX_10_9) return createDisabledBuildTabInfoOnly ( "Live-build features are available only on MacOSX 10.9 or higher."); if (isWin) if (! SystemStats::isOperatingSystem64Bit() || osType < SystemStats::Windows8_0) return createDisabledBuildTabInfoOnly ( "Live-build features are available only on 64-Bit Windows 8 or higher."); const auto& unlockStatus = *ProjucerLicenses::getInstance(); if (! unlockStatus.isLoggedIn()) return createDisabledBuildTabSubscribe ("Sign in with your ROLI account", false, unlockStatus.isDLLPresent()); if (! unlockStatus.hasLiveCodingLicence()) return createDisabledBuildTabSubscribe ("Subscribe to JUCE Pro or Indie", true, unlockStatus.isDLLPresent()); jassert (unlockStatus.isLoggedIn()); jassert (unlockStatus.isDLLPresent()); return new EnableBuildComp(); } Component* ProjectContentComponent::createDisabledBuildTabSubscribe (String textPrefix, bool loggedIn, bool dllPresent) { bool showSubscribeButton = true; bool showSignInButton = dllPresent && ! loggedIn; bool showSwitchAccountButton = dllPresent && loggedIn; bool showDownloadButton = ! dllPresent; return new ProjucerDisabledComp ( textPrefix + " to use the Projucer's live-build features:", loggedIn, showSubscribeButton, showSignInButton, showSwitchAccountButton, showDownloadButton); } Component* ProjectContentComponent::createDisabledBuildTabInfoOnly(const char* message) { return new ProjucerDisabledComp (message, false, false, false, false, false); } //============================================================================== BuildTabComponent* findBuildTab (const TabbedComponent& tabs) { return dynamic_cast (tabs.getTabContentComponent (2)); } bool ProjectContentComponent::isBuildTabEnabled() const { return findBuildTab (treeViewTabs) != nullptr; } bool ProjectContentComponent::isBuildTabSuitableForLoggedInUser() const { return isBuildTabEnabled() || isBuildTabLoggedInWithoutLicense() || dynamic_cast (treeViewTabs.getTabContentComponent (2)) != nullptr; } bool ProjectContentComponent::isBuildTabLoggedInWithoutLicense() const { if (auto* c = dynamic_cast (treeViewTabs.getTabContentComponent (2))) return c->isLoggedIn; return false; } void ProjectContentComponent::createProjectTabs() { jassert (project != nullptr); const Colour tabColour (Colours::transparentBlack); treeViewTabs.addTab ("Files", tabColour, new FileTreePanel (*project), true); treeViewTabs.addTab ("Config", tabColour, new ConfigTreePanel (*project), true); const CompileEngineChildProcess::Ptr childProc (getChildProcess()); treeViewTabs.addTab ("Build", Colours::transparentBlack, createBuildTab (childProc), true); if (childProc != nullptr) treeViewTabs.getTabbedButtonBar().getTabButton (2) ->setExtraComponent (new BuildStatusTabComp (childProc->errorList, childProc->activityList), TabBarButton::afterText); } void ProjectContentComponent::deleteProjectTabs() { if (project != nullptr && treeViewTabs.isShowing()) { PropertiesFile& settings = project->getStoredProperties(); if (treeViewTabs.getWidth() > 0) settings.setValue ("projectPanelWidth", treeViewTabs.getWidth()); if (treeViewTabs.getNumTabs() > 0) settings.setValue ("lastTab", treeViewTabs.getCurrentTabName()); } treeViewTabs.clearTabs(); } void ProjectContentComponent::saveTreeViewState() { for (int i = treeViewTabs.getNumTabs(); --i >= 0;) if (TreePanelBase* t = dynamic_cast (treeViewTabs.getTabContentComponent (i))) t->saveOpenness(); } void ProjectContentComponent::saveOpenDocumentList() { if (project != nullptr) { ScopedPointer xml (recentDocumentList.createXML()); if (xml != nullptr) project->getStoredProperties().setValue ("lastDocs", xml); } } void ProjectContentComponent::reloadLastOpenDocuments() { if (project != nullptr) { ScopedPointer xml (project->getStoredProperties().getXmlValue ("lastDocs")); if (xml != nullptr) { recentDocumentList.restoreFromXML (*project, *xml); showDocument (recentDocumentList.getCurrentDocument(), true); } } } bool ProjectContentComponent::documentAboutToClose (OpenDocumentManager::Document* document) { hideDocument (document); return true; } void ProjectContentComponent::changeListenerCallback (ChangeBroadcaster*) { updateMissingFileStatuses(); } void ProjectContentComponent::updateMissingFileStatuses() { if (FileTreePanel* tree = dynamic_cast (treeViewTabs.getTabContentComponent (0))) tree->updateMissingFileStatuses(); } bool ProjectContentComponent::showEditorForFile (const File& f, bool grabFocus) { return getCurrentFile() == f || showDocument (ProjucerApplication::getApp().openDocumentManager.openFile (project, f), grabFocus); } bool ProjectContentComponent::hasFileInRecentList (const File& f) const { return recentDocumentList.contains (f); } File ProjectContentComponent::getCurrentFile() const { return currentDocument != nullptr ? currentDocument->getFile() : File(); } bool ProjectContentComponent::showDocument (OpenDocumentManager::Document* doc, bool grabFocus) { if (doc == nullptr) return false; if (doc->hasFileBeenModifiedExternally()) doc->reloadFromFile(); if (doc == getCurrentDocument() && contentView != nullptr) { if (grabFocus) contentView->grabKeyboardFocus(); return true; } recentDocumentList.newDocumentOpened (doc); bool opened = setEditorComponent (doc->createEditor(), doc); if (opened && grabFocus) contentView->grabKeyboardFocus(); return opened; } void ProjectContentComponent::hideEditor() { currentDocument = nullptr; contentView = nullptr; updateMainWindowTitle(); ProjucerApplication::getCommandManager().commandStatusChanged(); resized(); } void ProjectContentComponent::hideDocument (OpenDocumentManager::Document* doc) { if (doc == currentDocument) { if (OpenDocumentManager::Document* replacement = recentDocumentList.getClosestPreviousDocOtherThan (doc)) showDocument (replacement, true); else hideEditor(); } } bool ProjectContentComponent::setEditorComponent (Component* editor, OpenDocumentManager::Document* doc) { if (editor != nullptr) { contentView = nullptr; contentView = editor; currentDocument = doc; addAndMakeVisible (editor); resized(); updateMainWindowTitle(); ProjucerApplication::getCommandManager().commandStatusChanged(); return true; } updateMainWindowTitle(); return false; } void ProjectContentComponent::closeDocument() { if (currentDocument != nullptr) ProjucerApplication::getApp().openDocumentManager.closeDocument (currentDocument, true); else if (contentView != nullptr) if (! goToPreviousFile()) hideEditor(); } static void showSaveWarning (OpenDocumentManager::Document* currentDocument) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Save failed!"), TRANS("Couldn't save the file:") + "\n" + currentDocument->getFile().getFullPathName()); } void ProjectContentComponent::saveDocument() { if (currentDocument != nullptr) { if (! currentDocument->save()) showSaveWarning (currentDocument); } else saveProject(); updateMainWindowTitle(); } void ProjectContentComponent::saveAs() { if (currentDocument != nullptr && ! currentDocument->saveAs()) showSaveWarning (currentDocument); } bool ProjectContentComponent::goToPreviousFile() { OpenDocumentManager::Document* doc = recentDocumentList.getCurrentDocument(); if (doc == nullptr || doc == getCurrentDocument()) doc = recentDocumentList.getPrevious(); return showDocument (doc, true); } bool ProjectContentComponent::goToNextFile() { return showDocument (recentDocumentList.getNext(), true); } bool ProjectContentComponent::canGoToCounterpart() const { return currentDocument != nullptr && currentDocument->getCounterpartFile().exists(); } bool ProjectContentComponent::goToCounterpart() { if (currentDocument != nullptr) { const File file (currentDocument->getCounterpartFile()); if (file.exists()) return showEditorForFile (file, true); } return false; } bool ProjectContentComponent::saveProject() { return project != nullptr && project->save (true, true) == FileBasedDocument::savedOk; } void ProjectContentComponent::closeProject() { if (MainWindow* const mw = findParentComponentOfClass()) mw->closeCurrentProject(); } void ProjectContentComponent::showFilesTab() { treeViewTabs.setCurrentTabIndex (0); } void ProjectContentComponent::showConfigTab() { treeViewTabs.setCurrentTabIndex (1); } void ProjectContentComponent::showProjectSettings() { showConfigTab(); if (ConfigTreePanel* const tree = dynamic_cast (treeViewTabs.getCurrentContentComponent())) tree->showProjectSettings(); } void ProjectContentComponent::showModules() { showConfigTab(); if (ConfigTreePanel* const tree = dynamic_cast (treeViewTabs.getCurrentContentComponent())) tree->showModules(); } void ProjectContentComponent::showModule (const String& moduleID) { showConfigTab(); if (ConfigTreePanel* const tree = dynamic_cast (treeViewTabs.getCurrentContentComponent())) tree->showModule (moduleID); } StringArray ProjectContentComponent::getExportersWhichCanLaunch() const { StringArray s; if (project != nullptr) for (Project::ExporterIterator exporter (*project); exporter.next();) if (exporter->canLaunchProject()) s.add (exporter->getName()); return s; } void ProjectContentComponent::openInIDE (int exporterIndex, bool saveFirst) { if (saveFirst) saveProject(); int i = 0; if (project != nullptr) for (Project::ExporterIterator exporter (*project); exporter.next();) if (exporter->canLaunchProject()) if (i++ == exporterIndex && exporter->launchProject()) break; } static void openIDEMenuCallback (int result, ProjectContentComponent* comp, bool saveFirst) { if (comp != nullptr && result > 0) comp->openInIDE (result - 1, saveFirst); } void ProjectContentComponent::openInIDE (bool saveFirst) { if (project != nullptr) { StringArray possibleExporters = getExportersWhichCanLaunch(); if (possibleExporters.size() > 1) { PopupMenu menu; for (int i = 0; i < possibleExporters.size(); ++i) menu.addItem (i + 1, possibleExporters[i]); menu.showMenuAsync (PopupMenu::Options(), ModalCallbackFunction::forComponent (openIDEMenuCallback, this, saveFirst)); } else { openInIDE (0, saveFirst); } } } static void newExporterMenuCallback (int result, ProjectContentComponent* comp) { if (comp != nullptr && result > 0) { if (Project* p = comp->getProject()) { String exporterName (ProjectExporter::getExporterNames() [result - 1]); if (exporterName.isNotEmpty()) p->addNewExporter (exporterName); } } } void ProjectContentComponent::showNewExporterMenu() { if (project != nullptr) { PopupMenu menu; menu.addSectionHeader ("Create a new export target:"); Array exporters (ProjectExporter::getExporterTypes()); for (int i = 0; i < exporters.size(); ++i) { const ProjectExporter::ExporterTypeInfo& type = exporters.getReference(i); menu.addItem (i + 1, type.name, true, false, type.getIcon()); } menu.showMenuAsync (PopupMenu::Options(), ModalCallbackFunction::forComponent (newExporterMenuCallback, this)); } } void ProjectContentComponent::deleteSelectedTreeItems() { if (TreePanelBase* const tree = dynamic_cast (treeViewTabs.getCurrentContentComponent())) tree->deleteSelectedItems(); } void ProjectContentComponent::updateMainWindowTitle() { if (MainWindow* mw = findParentComponentOfClass()) { String title; File file; bool edited = false; if (currentDocument != nullptr) { title = currentDocument->getName(); edited = currentDocument->needsSaving(); file = currentDocument->getFile(); } if (ComponentPeer* peer = mw->getPeer()) { if (! peer->setDocumentEditedStatus (edited)) if (edited) title << "*"; peer->setRepresentedFile (file); } mw->updateTitle (title); } } void ProjectContentComponent::showBubbleMessage (Rectangle pos, const String& text) { addChildComponent (bubbleMessage); bubbleMessage.setColour (BubbleComponent::backgroundColourId, Colours::white.withAlpha (0.7f)); bubbleMessage.setColour (BubbleComponent::outlineColourId, Colours::black.withAlpha (0.8f)); bubbleMessage.setAlwaysOnTop (true); bubbleMessage.showAt (pos, AttributedString (text), 3000, true, false); } //============================================================================== void ProjectContentComponent::showTranslationTool() { if (translationTool != nullptr) { translationTool->toFront (true); } else if (project != nullptr) { new FloatingToolWindow ("Translation File Builder", "transToolWindowPos", new TranslationToolComponent(), translationTool, 600, 700, 600, 400, 10000, 10000); } } //============================================================================== struct AsyncCommandRetrier : public Timer { AsyncCommandRetrier (const ApplicationCommandTarget::InvocationInfo& i) : info (i) { info.originatingComponent = nullptr; startTimer (500); } void timerCallback() override { stopTimer(); ProjucerApplication::getCommandManager().invoke (info, true); delete this; } ApplicationCommandTarget::InvocationInfo info; JUCE_DECLARE_NON_COPYABLE (AsyncCommandRetrier) }; bool reinvokeCommandAfterCancellingModalComps (const ApplicationCommandTarget::InvocationInfo& info) { if (ModalComponentManager::getInstance()->cancelAllModalComponents()) { new AsyncCommandRetrier (info); return true; } return false; } //============================================================================== ApplicationCommandTarget* ProjectContentComponent::getNextCommandTarget() { return findFirstTargetParentComponent(); } void ProjectContentComponent::getAllCommands (Array & commands) { const CommandID ids[] = { CommandIDs::saveDocument, CommandIDs::saveDocumentAs, CommandIDs::closeDocument, CommandIDs::saveProject, CommandIDs::closeProject, CommandIDs::openInIDE, CommandIDs::saveAndOpenInIDE, CommandIDs::createNewExporter, CommandIDs::showFilePanel, CommandIDs::showConfigPanel, CommandIDs::showProjectSettings, CommandIDs::showProjectModules, CommandIDs::goToPreviousDoc, CommandIDs::goToNextDoc, CommandIDs::goToCounterpart, CommandIDs::deleteSelectedItem, CommandIDs::showTranslationTool, CommandIDs::showBuildTab, CommandIDs::cleanAll, CommandIDs::enableBuild, CommandIDs::buildNow, CommandIDs::toggleContinuousBuild, CommandIDs::showWarnings, CommandIDs::reinstantiateComp, CommandIDs::launchApp, CommandIDs::killApp, CommandIDs::nextError, CommandIDs::prevError }; commands.addArray (ids, numElementsInArray (ids)); } void ProjectContentComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) { String documentName; if (currentDocument != nullptr) documentName = " '" + currentDocument->getName().substring (0, 32) + "'"; #if JUCE_MAC const ModifierKeys cmdCtrl (ModifierKeys::ctrlModifier | ModifierKeys::commandModifier); #else const ModifierKeys cmdCtrl (ModifierKeys::ctrlModifier | ModifierKeys::altModifier); #endif switch (commandID) { case CommandIDs::saveProject: result.setInfo ("Save Project", "Saves the current project", CommandCategories::general, 0); result.setActive (project != nullptr); break; case CommandIDs::closeProject: result.setInfo ("Close Project", "Closes the current project", CommandCategories::general, 0); result.setActive (project != nullptr); break; case CommandIDs::saveDocument: result.setInfo ("Save" + documentName, "Saves the current document", CommandCategories::general, 0); result.setActive (currentDocument != nullptr || project != nullptr); result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0)); break; case CommandIDs::saveDocumentAs: result.setInfo ("Save As...", "Saves the current document to a new location", CommandCategories::general, 0); result.setActive (currentDocument != nullptr); result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::closeDocument: result.setInfo ("Close" + documentName, "Closes the current document", CommandCategories::general, 0); result.setActive (contentView != nullptr); result.defaultKeypresses.add (KeyPress ('w', cmdCtrl, 0)); break; case CommandIDs::goToPreviousDoc: result.setInfo ("Previous Document", "Go to previous document", CommandCategories::general, 0); result.setActive (recentDocumentList.canGoToPrevious()); result.defaultKeypresses.add (KeyPress (KeyPress::leftKey, cmdCtrl, 0)); break; case CommandIDs::goToNextDoc: result.setInfo ("Next Document", "Go to next document", CommandCategories::general, 0); result.setActive (recentDocumentList.canGoToNext()); result.defaultKeypresses.add (KeyPress (KeyPress::rightKey, cmdCtrl, 0)); break; case CommandIDs::goToCounterpart: result.setInfo ("Open corresponding header or cpp file", "Open counterpart file", CommandCategories::general, 0); result.setActive (canGoToCounterpart()); result.defaultKeypresses.add (KeyPress (KeyPress::upKey, cmdCtrl, 0)); break; case CommandIDs::openInIDE: result.setInfo ("Open in IDE...", "Launches the project in an external IDE", CommandCategories::general, 0); result.setActive (ProjectExporter::canProjectBeLaunched (project)); break; case CommandIDs::saveAndOpenInIDE: result.setInfo ("Save Project and Open in IDE...", "Saves the project and launches it in an external IDE", CommandCategories::general, 0); result.setActive (ProjectExporter::canProjectBeLaunched (project)); result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::createNewExporter: result.setInfo ("Create New Exporter...", "Creates a new exporter for a compiler type", CommandCategories::general, 0); result.setActive (project != nullptr); break; case CommandIDs::showFilePanel: result.setInfo ("Show File Panel", "Shows the tree of files for this project", CommandCategories::general, 0); result.setActive (project != nullptr); result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier, 0)); break; case CommandIDs::showConfigPanel: result.setInfo ("Show Config Panel", "Shows the build options for the project", CommandCategories::general, 0); result.setActive (project != nullptr); result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier, 0)); break; case CommandIDs::showProjectSettings: result.setInfo ("Show Project Settings", "Shows the main project options page", CommandCategories::general, 0); result.setActive (project != nullptr); result.defaultKeypresses.add (KeyPress ('i', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::showProjectModules: result.setInfo ("Show Project Modules", "Shows the project's list of modules", CommandCategories::general, 0); result.setActive (project != nullptr); result.defaultKeypresses.add (KeyPress ('m', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::deleteSelectedItem: result.setInfo ("Delete Selected File", String(), CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress (KeyPress::deleteKey, 0, 0)); result.defaultKeypresses.add (KeyPress (KeyPress::backspaceKey, 0, 0)); result.setActive (dynamic_cast (treeViewTabs.getCurrentContentComponent()) != nullptr); break; case CommandIDs::showTranslationTool: result.setInfo ("Translation File Builder", "Shows the translation file helper tool", CommandCategories::general, 0); break; case CommandIDs::showBuildTab: result.setInfo ("Show Build Panel", "Shows the build panel", CommandCategories::general, 0); //result.defaultKeypresses.add (KeyPress ('b', ModifierKeys::commandModifier, 0)); break; case CommandIDs::cleanAll: result.setInfo ("Clean All", "Cleans all intermediate files", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('k', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); result.setActive (project != nullptr); break; case CommandIDs::enableBuild: result.setInfo ("Enable Compilation", "Enables/disables the compiler", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('b', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); result.setActive (project != nullptr); result.setTicked (childProcess != nullptr); break; case CommandIDs::buildNow: result.setInfo ("Build Now", "Recompiles any out-of-date files and updates the JIT engine", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('b', ModifierKeys::commandModifier, 0)); result.setActive (childProcess != nullptr); break; case CommandIDs::toggleContinuousBuild: result.setInfo ("Enable Continuous Recompiling", "Continuously recompiles any changes made in code editors", CommandCategories::general, 0); result.setActive (childProcess != nullptr); result.setTicked (isContinuousRebuildEnabled()); break; case CommandIDs::showWarnings: result.setInfo ("Show Warnings", "Shows or hides compilation warnings", CommandCategories::general, 0); result.setActive (project != nullptr); result.setTicked (areWarningsEnabled()); break; case CommandIDs::launchApp: result.setInfo ("Launch Application", "Invokes the app's main() function", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('r', ModifierKeys::commandModifier, 0)); result.setActive (childProcess != nullptr && childProcess->canLaunchApp()); break; case CommandIDs::killApp: result.setInfo ("Stop Application", "Kills the app if it's running", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('.', ModifierKeys::commandModifier, 0)); result.setActive (childProcess != nullptr && childProcess->canKillApp()); break; case CommandIDs::reinstantiateComp: result.setInfo ("Re-instantiate Components", "Re-loads any component editors that are open", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('r', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); result.setActive (childProcess != nullptr); break; case CommandIDs::nextError: result.setInfo ("Highlight next error", "Jumps to the next error or warning", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('e', ModifierKeys::commandModifier, 0)); result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty()); break; case CommandIDs::prevError: result.setInfo ("Highlight previous error", "Jumps to the last error or warning", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('e', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); result.setActive (childProcess != nullptr && ! childProcess->errorList.isEmpty()); break; default: break; } } bool ProjectContentComponent::perform (const InvocationInfo& info) { switch (info.commandID) { case CommandIDs::saveProject: case CommandIDs::closeProject: case CommandIDs::saveDocument: case CommandIDs::saveDocumentAs: case CommandIDs::closeDocument: case CommandIDs::goToPreviousDoc: case CommandIDs::goToNextDoc: case CommandIDs::goToCounterpart: case CommandIDs::saveAndOpenInIDE: if (reinvokeCommandAfterCancellingModalComps (info)) { grabKeyboardFocus(); // to force any open labels to close their text editors return true; } break; default: break; } if (isCurrentlyBlockedByAnotherModalComponent()) return false; switch (info.commandID) { case CommandIDs::saveProject: saveProject(); break; case CommandIDs::closeProject: closeProject(); break; case CommandIDs::saveDocument: saveDocument(); break; case CommandIDs::saveDocumentAs: saveAs(); break; case CommandIDs::closeDocument: closeDocument(); break; case CommandIDs::goToPreviousDoc: goToPreviousFile(); break; case CommandIDs::goToNextDoc: goToNextFile(); break; case CommandIDs::goToCounterpart: goToCounterpart(); break; case CommandIDs::showFilePanel: showFilesTab(); break; case CommandIDs::showConfigPanel: showConfigTab(); break; case CommandIDs::showProjectSettings: showProjectSettings(); break; case CommandIDs::showProjectModules: showModules(); break; case CommandIDs::openInIDE: openInIDE (false); break; case CommandIDs::saveAndOpenInIDE: openInIDE (true); break; case CommandIDs::createNewExporter: showNewExporterMenu(); break; case CommandIDs::deleteSelectedItem: deleteSelectedTreeItems(); break; case CommandIDs::showTranslationTool: showTranslationTool(); break; case CommandIDs::showBuildTab: showBuildTab(); break; case CommandIDs::cleanAll: cleanAll(); break; case CommandIDs::enableBuild: setBuildEnabled (! isBuildEnabled()); break; case CommandIDs::buildNow: rebuildNow(); break; case CommandIDs::toggleContinuousBuild: setContinuousRebuildEnabled (! isContinuousRebuildEnabled()); break; case CommandIDs::launchApp: launchApp(); break; case CommandIDs::killApp: killApp(); break; case CommandIDs::reinstantiateComp: reinstantiateLivePreviewWindows(); break; case CommandIDs::showWarnings: toggleWarnings(); break; case CommandIDs::nextError: showNextError(); break; case CommandIDs::prevError: showPreviousError(); break; default: return false; } return true; } void ProjectContentComponent::getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails, OwnedArray& selectedNodes) { FileTreeItemTypes::ProjectTreeItemBase::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes); } //============================================================================== void ProjectContentComponent::killChildProcess() { if (childProcess != nullptr) { deleteProjectTabs(); childProcess = nullptr; ProjucerApplication::getApp().childProcessCache->removeOrphans(); } } void ProjectContentComponent::setBuildEnabled (bool b) { if (project != nullptr && b != isBuildEnabled()) { LiveBuildProjectSettings::setBuildDisabled (*project, ! b); killChildProcess(); refreshTabsIfBuildStatusChanged(); } } void ProjectContentComponent::showBuildTab() { WeakReference currentFocus (Component::getCurrentlyFocusedComponent()); treeViewTabs.setCurrentTabIndex (2); if (currentFocus != nullptr) currentFocus->grabKeyboardFocus(); } void ProjectContentComponent::cleanAll() { lastCrashMessage = String(); if (childProcess != nullptr) childProcess->cleanAll(); else if (Project* p = getProject()) CompileEngineChildProcess::cleanAllCachedFilesForProject (*p); } void ProjectContentComponent::handleCrash (const String& message) { lastCrashMessage = message.isEmpty() ? TRANS("JIT process stopped responding!") : (TRANS("JIT process crashed!") + ":\n\n" + message); if (project != nullptr) { setBuildEnabled (false); showBuildTab(); } } bool ProjectContentComponent::isBuildEnabled() const { return project != nullptr && ! LiveBuildProjectSettings::isBuildDisabled (*project) && ProjucerLicenses::getInstance()->hasLiveCodingLicence() && ProjucerLicenses::getInstance()->isLoggedIn(); } void ProjectContentComponent::refreshTabsIfBuildStatusChanged() { if (project != nullptr && (treeViewTabs.getNumTabs() < 3 || isBuildEnabled() != isBuildTabEnabled() || ProjucerLicenses::getInstance()->isLoggedIn() != isBuildTabSuitableForLoggedInUser())) rebuildProjectTabs(); } bool ProjectContentComponent::areWarningsEnabled() const { return project != nullptr && ! LiveBuildProjectSettings::areWarningsDisabled (*project); } void ProjectContentComponent::updateWarningState() { if (childProcess != nullptr) childProcess->errorList.setWarningsEnabled (areWarningsEnabled()); } void ProjectContentComponent::toggleWarnings() { if (project != nullptr) { LiveBuildProjectSettings::setWarningsDisabled (*project, areWarningsEnabled()); updateWarningState(); } } static ProjucerAppClasses::ErrorListComp* findErrorListComp (const TabbedComponent& tabs) { if (BuildTabComponent* bt = findBuildTab (tabs)) return bt->errorListComp; return nullptr; } void ProjectContentComponent::showNextError() { if (ProjucerAppClasses::ErrorListComp* el = findErrorListComp (treeViewTabs)) { showBuildTab(); el->showNext(); } } void ProjectContentComponent::showPreviousError() { if (ProjucerAppClasses::ErrorListComp* el = findErrorListComp (treeViewTabs)) { showBuildTab(); el->showPrevious(); } } void ProjectContentComponent::reinstantiateLivePreviewWindows() { if (childProcess != nullptr) childProcess->reinstantiatePreviews(); } void ProjectContentComponent::launchApp() { if (childProcess != nullptr) childProcess->launchApp(); } void ProjectContentComponent::killApp() { if (childProcess != nullptr) childProcess->killApp(); } void ProjectContentComponent::rebuildNow() { if (childProcess != nullptr) childProcess->flushEditorChanges(); } void ProjectContentComponent::globalFocusChanged (Component* focusedComponent) { const bool nowForeground = (Process::isForegroundProcess() && (focusedComponent == this || isParentOf (focusedComponent))); if (nowForeground != isForeground) { isForeground = nowForeground; if (childProcess != nullptr) childProcess->processActivationChanged (isForeground); } } void ProjectContentComponent::timerCallback() { if (! isBuildEnabled()) killChildProcess(); refreshTabsIfBuildStatusChanged(); } ReferenceCountedObjectPtr ProjectContentComponent::getChildProcess() { if (childProcess == nullptr && isBuildEnabled()) { childProcess = ProjucerApplication::getApp().childProcessCache->getOrCreate (*project); if (childProcess != nullptr) childProcess->setContinuousRebuild (isContinuousRebuildEnabled()); } return childProcess; } void ProjectContentComponent::handleMissingSystemHeaders() { #if JUCE_MAC const String tabMessage = "Compiler not available due to missing system headers\nPlease install a recent version of Xcode"; const String alertWindowMessage = "Missing system headers\nPlease install a recent version of Xcode"; #elif JUCE_WINDOWS const String tabMessage = "Compiler not available due to missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK"; const String alertWindowMessage = "Missing system headers\nPlease install a recent version of Visual Studio and the Windows Desktop SDK"; #elif JUCE_LINUX const String tabMessage = "Compiler not available due to missing system headers\nPlease do a sudo apt-get install ..."; const String alertWindowMessage = "Missing system headers\nPlease do sudo apt-get install ..."; #endif setBuildEnabled (false); deleteProjectTabs(); createProjectTabs(); bool isLoggedIn = ProjucerLicenses::getInstance()->isLoggedIn(); ProjucerDisabledComp* buildTab = new ProjucerDisabledComp (tabMessage, isLoggedIn, false, false, false, false); treeViewTabs.addTab ("Build", Colours::transparentBlack, buildTab, true); showBuildTab(); AlertWindow::showMessageBox (AlertWindow::AlertIconType::WarningIcon, "Missing system headers", alertWindowMessage); }