/* ============================================================================== 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. ============================================================================== */ class ErrorListComp : public TreePanelBase, private ChangeListener { public: ErrorListComp (ErrorList& el) : TreePanelBase (nullptr, String()), errorList (el) { setName ("Errors and Warnings"); setEmptyTreeMessage ("(No Messages)"); tree.setMultiSelectEnabled (false); tree.setRootItemVisible (false); setRoot (new ErrorRootTreeItem (errorList)); errorList.addChangeListener (this); errorListChanged(); } ~ErrorListComp() { errorList.removeChangeListener (this); } void errorListChanged() { static_cast (rootItem.get())->refreshSubItems(); } void moveBy (const int delta) { if (delta < 0) if (TreeViewItem* selected = tree.getSelectedItem (0)) if (selected->getRowNumberInTree() <= 1) return; tree.moveSelectedRow (delta); if (dynamic_cast (tree.getSelectedItem (0)) == nullptr) tree.moveSelectedRow (delta); } void showNext() { moveBy (1); } void showPrevious() { moveBy (-1); } private: TreeView list; ErrorList& errorList; struct ErrorMessageTreeItem; void changeListenerCallback (ChangeBroadcaster*) override { errorListChanged(); } static void limitNumberOfSubItems (TreeViewItem& item, const int maxSubItems) { while (item.getNumSubItems() > maxSubItems) item.removeSubItem (item.getNumSubItems() - 1); } //============================================================================== class ErrorRootTreeItem : public JucerTreeViewBase { public: ErrorRootTreeItem (ErrorList& el) : errorList (el) {} String getRenamingName() const override { return getDisplayName(); } String getDisplayName() const override { return "Errors and Warnings"; } void setName (const String&) override {} bool isMissing() const override { return false; } Icon getIcon() const override { return Icon (getIcons().bug, getContentColour (true)); } bool canBeSelected() const override { return true; } bool mightContainSubItems() override { return true; } String getUniqueName() const override { return "errors"; } void refreshSubItems() { Array errors; errorList.takeCopy (errors); StringArray files; for (const auto& m : errors) { files.addIfNotAlreadyThere (m.mainFile); if (m.associatedDiagnostic != nullptr) files.addIfNotAlreadyThere (m.associatedDiagnostic->mainFile); } limitNumberOfSubItems (*this, files.size()); int i = 0; for (const auto& f : files) { if (i >= getNumSubItems() || static_cast (getSubItem (i))->compileUnit != f) { limitNumberOfSubItems (*this, i); addSubItem (new CompileUnitTreeItem (f)); } static_cast (getSubItem (i))->refresh (errors); ++i; } } private: ErrorList& errorList; }; //============================================================================== struct CompileUnitTreeItem : public JucerTreeViewBase { CompileUnitTreeItem (const String& filename) : compileUnit (filename) {} void setName (const String&) override {} void addSubItems() override {} bool isMissing() const override { return false; } Icon getIcon() const override { return Icon (getIcons().bug, getContentColour (true)); } bool canBeSelected() const override { return true; } bool mightContainSubItems() override { return true; } String getUniqueName() const override { return String::toHexString (compileUnit.hashCode64()); } String getRenamingName() const override { return getDisplayName(); } String getDisplayName() const override { if (File::isAbsolutePath (compileUnit)) { File f (compileUnit); return f.exists() ? f.getFileName() : compileUnit; } if (! compileUnit.isEmpty()) return compileUnit; return String ("Global"); } void showOverlays() { for (int i = 0; i < getNumSubItems(); ++i) if (auto* e = dynamic_cast (getSubItem (i))) e->showOverlays(); } ErrorMessageTreeItem* getItemForError (const DiagnosticMessage& m) const { for (int i = 0; i < getNumSubItems(); ++i) if (auto* item = dynamic_cast (getSubItem(i))) if (item->message == m) return item; return nullptr; } void refresh (const Array& allErrors) { clearSubItems(); for (const auto& error : allErrors) if (error.mainFile == compileUnit && error.associatedDiagnostic == nullptr) addSubItem (new ErrorMessageTreeItem (error)); for (const auto& error : allErrors) if (error.mainFile == compileUnit && error.associatedDiagnostic != nullptr) if (ErrorMessageTreeItem* parent = getItemForError (*error.associatedDiagnostic)) parent->addSubItem (new ErrorMessageTreeItem (error)); } void showDocument() override { if (ProjectContentComponent* pcc = getProjectContentComponent()) if (File::isAbsolutePath (compileUnit) && File (compileUnit).exists()) pcc->showEditorForFile (File (compileUnit), true); } String compileUnit; }; //============================================================================== struct ErrorMessageTreeItem : public JucerTreeViewBase { ErrorMessageTreeItem (const DiagnosticMessage& m) : message (m), itemHeight (25) { setOpenness (Openness::opennessClosed); uniqueID << message.message << ':' << message.range.toString(); } ~ErrorMessageTreeItem() { overlay.deleteAndZero(); } String getRenamingName() const override { return getDisplayName(); } String getDisplayName() const override { return message.message; } void setName (const String&) override {} bool isMissing() const override { return false; } Icon getIcon() const override { return Icon (message.isNote() ? getIcons().info : getIcons().warning, getContentColour (true)); } bool canBeSelected() const override { return true; } bool mightContainSubItems() override { return getNumSubItems() != 0; } String getUniqueName() const override { return uniqueID; } void paintContent (Graphics& g, const Rectangle& area) override { jassert (area.getWidth() >= 0); AttributedString s (message.message); s.setFont (Font (12.0f)); s.setColour (getContentColour (false)); s.setJustification (Justification::centredLeft); text.createLayout (s, (float) area.getWidth()); const auto newHeight = 2 + jmax (25, (int) text.getHeight()); if (itemHeight != newHeight) { itemHeight = newHeight; treeHasChanged(); } text.draw (g, area.toFloat()); } Colour getContentColour (bool isIcon) const override { return message.isError() ? Colours::red : message.isWarning() ? Colours::yellow : getOwnerView()->findColour (isIcon ? treeIconColourId : defaultTextColourId); } void showPopupMenu() override { PopupMenu menu; menu.addItem (1, "Copy"); launchPopupMenu (menu); } void handlePopupMenuResult (int resultCode) override { if (resultCode == 1) SystemClipboard::copyTextToClipboard (message.toString()); } int getItemHeight() const override { return itemHeight; } SourceCodeEditor* getEditor() { if (ProjectContentComponent* pcc = getProjectContentComponent()) { const File file (File::createFileWithoutCheckingPath (message.range.file)); if (message.range.isValid() && file.exists() && pcc->showEditorForFile (file, false)) { if (SourceCodeEditor* ed = dynamic_cast (pcc->getEditorComponent())) { return ed; } } } return nullptr; } void showDocument() override { if (SourceCodeEditor* ed = getEditor()) { ed->grabKeyboardFocus(); ed->highlight (message.range.range, false); if (auto cu = findCompileUnitParent()) cu->showOverlays(); } } CompileUnitTreeItem* findCompileUnitParent() { for (TreeViewItem* p = getParentItem(); p != nullptr; p = p->getParentItem()) if (auto cu = dynamic_cast (p)) return cu; return nullptr; } void showOverlays() { overlay.deleteAndZero(); if (ProjectContentComponent* pcc = getProjectContentComponent()) { if (SourceCodeEditor* ed = dynamic_cast (pcc->getEditorComponent())) { auto start = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getStart()); auto end = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getEnd()); if (auto* ce = dynamic_cast (ed->editor.get())) overlay = ce->addDiagnosticOverlay (start, end, message.type); } } for (int i = 0; i < getNumSubItems(); ++i) if (auto* e = dynamic_cast (getSubItem (i))) e->showOverlays(); } DiagnosticMessage message; private: String uniqueID; TextLayout text; int itemHeight; Component::SafePointer overlay; }; };