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.

349 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. class ErrorListComp : public TreePanelBase,
  20. private ChangeListener
  21. {
  22. public:
  23. ErrorListComp (ErrorList& el)
  24. : TreePanelBase (nullptr, String()),
  25. errorList (el)
  26. {
  27. setName ("Errors and Warnings");
  28. setEmptyTreeMessage ("(No Messages)");
  29. tree.setMultiSelectEnabled (false);
  30. tree.setRootItemVisible (false);
  31. setRoot (new ErrorRootTreeItem (errorList));
  32. errorList.addChangeListener (this);
  33. errorListChanged();
  34. }
  35. ~ErrorListComp()
  36. {
  37. errorList.removeChangeListener (this);
  38. }
  39. void errorListChanged()
  40. {
  41. static_cast<ErrorRootTreeItem*> (rootItem.get())->refreshSubItems();
  42. }
  43. void moveBy (const int delta)
  44. {
  45. if (delta < 0)
  46. if (TreeViewItem* selected = tree.getSelectedItem (0))
  47. if (selected->getRowNumberInTree() <= 1)
  48. return;
  49. tree.moveSelectedRow (delta);
  50. if (dynamic_cast<ErrorMessageTreeItem*> (tree.getSelectedItem (0)) == nullptr)
  51. tree.moveSelectedRow (delta);
  52. }
  53. void showNext() { moveBy (1); }
  54. void showPrevious() { moveBy (-1); }
  55. private:
  56. TreeView list;
  57. ErrorList& errorList;
  58. struct ErrorMessageTreeItem;
  59. void changeListenerCallback (ChangeBroadcaster*) override
  60. {
  61. errorListChanged();
  62. }
  63. static void limitNumberOfSubItems (TreeViewItem& item, const int maxSubItems)
  64. {
  65. while (item.getNumSubItems() > maxSubItems)
  66. item.removeSubItem (item.getNumSubItems() - 1);
  67. }
  68. //==============================================================================
  69. class ErrorRootTreeItem : public JucerTreeViewBase
  70. {
  71. public:
  72. ErrorRootTreeItem (ErrorList& el) : errorList (el) {}
  73. String getRenamingName() const override { return getDisplayName(); }
  74. String getDisplayName() const override { return "Errors and Warnings"; }
  75. void setName (const String&) override {}
  76. bool isMissing() const override { return false; }
  77. Icon getIcon() const override { return Icon (getIcons().bug, getContentColour (true)); }
  78. bool canBeSelected() const override { return true; }
  79. bool mightContainSubItems() override { return true; }
  80. String getUniqueName() const override { return "errors"; }
  81. void refreshSubItems()
  82. {
  83. Array<DiagnosticMessage> errors;
  84. errorList.takeCopy (errors);
  85. StringArray files;
  86. for (const auto& m : errors)
  87. {
  88. files.addIfNotAlreadyThere (m.mainFile);
  89. if (m.associatedDiagnostic != nullptr)
  90. files.addIfNotAlreadyThere (m.associatedDiagnostic->mainFile);
  91. }
  92. limitNumberOfSubItems (*this, files.size());
  93. int i = 0;
  94. for (const auto& f : files)
  95. {
  96. if (i >= getNumSubItems() || static_cast<CompileUnitTreeItem*> (getSubItem (i))->compileUnit != f)
  97. {
  98. limitNumberOfSubItems (*this, i);
  99. addSubItem (new CompileUnitTreeItem (f));
  100. }
  101. static_cast<CompileUnitTreeItem*> (getSubItem (i))->refresh (errors);
  102. ++i;
  103. }
  104. }
  105. private:
  106. ErrorList& errorList;
  107. };
  108. //==============================================================================
  109. struct CompileUnitTreeItem : public JucerTreeViewBase
  110. {
  111. CompileUnitTreeItem (const String& filename) : compileUnit (filename) {}
  112. void setName (const String&) override {}
  113. void addSubItems() override {}
  114. bool isMissing() const override { return false; }
  115. Icon getIcon() const override { return Icon (getIcons().bug, getContentColour (true)); }
  116. bool canBeSelected() const override { return true; }
  117. bool mightContainSubItems() override { return true; }
  118. String getUniqueName() const override { return String::toHexString (compileUnit.hashCode64()); }
  119. String getRenamingName() const override { return getDisplayName(); }
  120. String getDisplayName() const override
  121. {
  122. if (File::isAbsolutePath (compileUnit))
  123. {
  124. File f (compileUnit);
  125. return f.exists() ? f.getFileName() : compileUnit;
  126. }
  127. if (! compileUnit.isEmpty())
  128. return compileUnit;
  129. return String ("Global");
  130. }
  131. void showOverlays()
  132. {
  133. for (int i = 0; i < getNumSubItems(); ++i)
  134. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  135. e->showOverlays();
  136. }
  137. ErrorMessageTreeItem* getItemForError (const DiagnosticMessage& m) const
  138. {
  139. for (int i = 0; i < getNumSubItems(); ++i)
  140. if (auto* item = dynamic_cast<ErrorMessageTreeItem*> (getSubItem(i)))
  141. if (item->message == m)
  142. return item;
  143. return nullptr;
  144. }
  145. void refresh (const Array<DiagnosticMessage>& allErrors)
  146. {
  147. clearSubItems();
  148. for (const auto& error : allErrors)
  149. if (error.mainFile == compileUnit && error.associatedDiagnostic == nullptr)
  150. addSubItem (new ErrorMessageTreeItem (error));
  151. for (const auto& error : allErrors)
  152. if (error.mainFile == compileUnit && error.associatedDiagnostic != nullptr)
  153. if (ErrorMessageTreeItem* parent = getItemForError (*error.associatedDiagnostic))
  154. parent->addSubItem (new ErrorMessageTreeItem (error));
  155. }
  156. void showDocument() override
  157. {
  158. if (ProjectContentComponent* pcc = getProjectContentComponent())
  159. if (File::isAbsolutePath (compileUnit) && File (compileUnit).exists())
  160. pcc->showEditorForFile (File (compileUnit), true);
  161. }
  162. String compileUnit;
  163. };
  164. //==============================================================================
  165. struct ErrorMessageTreeItem : public JucerTreeViewBase
  166. {
  167. ErrorMessageTreeItem (const DiagnosticMessage& m)
  168. : message (m), itemHeight (25)
  169. {
  170. setOpenness (Openness::opennessClosed);
  171. uniqueID << message.message << ':' << message.range.toString();
  172. }
  173. ~ErrorMessageTreeItem()
  174. {
  175. overlay.deleteAndZero();
  176. }
  177. String getRenamingName() const override { return getDisplayName(); }
  178. String getDisplayName() const override { return message.message; }
  179. void setName (const String&) override {}
  180. bool isMissing() const override { return false; }
  181. Icon getIcon() const override { return Icon (message.isNote() ? getIcons().info
  182. : getIcons().warning, getContentColour (true)); }
  183. bool canBeSelected() const override { return true; }
  184. bool mightContainSubItems() override { return getNumSubItems() != 0; }
  185. String getUniqueName() const override { return uniqueID; }
  186. void paintContent (Graphics& g, const Rectangle<int>& area) override
  187. {
  188. jassert (area.getWidth() >= 0);
  189. AttributedString s (message.message);
  190. s.setFont (Font (12.0f));
  191. s.setColour (getContentColour (false));
  192. s.setJustification (Justification::centredLeft);
  193. text.createLayout (s, (float) area.getWidth());
  194. const auto newHeight = 2 + jmax (25, (int) text.getHeight());
  195. if (itemHeight != newHeight)
  196. {
  197. itemHeight = newHeight;
  198. treeHasChanged();
  199. }
  200. text.draw (g, area.toFloat());
  201. }
  202. Colour getContentColour (bool isIcon) const override
  203. {
  204. return message.isError() ? Colours::red
  205. : message.isWarning() ? Colours::yellow
  206. : getOwnerView()->findColour (isIcon ? treeIconColourId
  207. : defaultTextColourId);
  208. }
  209. void showPopupMenu() override
  210. {
  211. PopupMenu menu;
  212. menu.addItem (1, "Copy");
  213. launchPopupMenu (menu);
  214. }
  215. void handlePopupMenuResult (int resultCode) override
  216. {
  217. if (resultCode == 1)
  218. SystemClipboard::copyTextToClipboard (message.toString());
  219. }
  220. int getItemHeight() const override
  221. {
  222. return itemHeight;
  223. }
  224. SourceCodeEditor* getEditor()
  225. {
  226. if (ProjectContentComponent* pcc = getProjectContentComponent())
  227. {
  228. const File file (File::createFileWithoutCheckingPath (message.range.file));
  229. if (message.range.isValid() && file.exists() && pcc->showEditorForFile (file, false))
  230. {
  231. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  232. {
  233. return ed;
  234. }
  235. }
  236. }
  237. return nullptr;
  238. }
  239. void showDocument() override
  240. {
  241. if (SourceCodeEditor* ed = getEditor())
  242. {
  243. ed->grabKeyboardFocus();
  244. ed->highlight (message.range.range, false);
  245. if (auto cu = findCompileUnitParent())
  246. cu->showOverlays();
  247. }
  248. }
  249. CompileUnitTreeItem* findCompileUnitParent()
  250. {
  251. for (TreeViewItem* p = getParentItem(); p != nullptr; p = p->getParentItem())
  252. if (auto cu = dynamic_cast<CompileUnitTreeItem*> (p))
  253. return cu;
  254. return nullptr;
  255. }
  256. void showOverlays()
  257. {
  258. overlay.deleteAndZero();
  259. if (ProjectContentComponent* pcc = getProjectContentComponent())
  260. {
  261. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  262. {
  263. auto start = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getStart());
  264. auto end = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getEnd());
  265. if (auto* ce = dynamic_cast<LiveBuildCodeEditor*> (ed->editor.get()))
  266. overlay = ce->addDiagnosticOverlay (start, end, message.type);
  267. }
  268. }
  269. for (int i = 0; i < getNumSubItems(); ++i)
  270. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  271. e->showOverlays();
  272. }
  273. DiagnosticMessage message;
  274. private:
  275. String uniqueID;
  276. TextLayout text;
  277. int itemHeight;
  278. Component::SafePointer<Component> overlay;
  279. };
  280. };