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.

364 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class ErrorListComp : public TreePanelBase,
  18. private ChangeListener
  19. {
  20. public:
  21. ErrorListComp (ErrorList& el)
  22. : TreePanelBase (nullptr, String()),
  23. errorList (el)
  24. {
  25. setName ("Errors and Warnings");
  26. setEmptyTreeMessage ("(No Messages)");
  27. tree.setMultiSelectEnabled (false);
  28. tree.setRootItemVisible (false);
  29. setRoot (new ErrorRootTreeItem (errorList));
  30. errorList.addChangeListener (this);
  31. errorListChanged();
  32. }
  33. ~ErrorListComp()
  34. {
  35. errorList.removeChangeListener (this);
  36. }
  37. void errorListChanged()
  38. {
  39. static_cast<ErrorRootTreeItem*> (rootItem.get())->refreshSubItems();
  40. }
  41. void moveBy (const int delta)
  42. {
  43. if (delta < 0)
  44. if (TreeViewItem* selected = tree.getSelectedItem (0))
  45. if (selected->getRowNumberInTree() <= 1)
  46. return;
  47. tree.moveSelectedRow (delta);
  48. if (dynamic_cast<ErrorMessageTreeItem*> (tree.getSelectedItem (0)) == nullptr)
  49. tree.moveSelectedRow (delta);
  50. }
  51. void showNext() { moveBy (1); }
  52. void showPrevious() { moveBy (-1); }
  53. private:
  54. TreeView list;
  55. ErrorList& errorList;
  56. struct ErrorMessageTreeItem;
  57. void changeListenerCallback (ChangeBroadcaster*) override
  58. {
  59. errorListChanged();
  60. }
  61. static void limitNumberOfSubItems (TreeViewItem& item, const int maxSubItems)
  62. {
  63. while (item.getNumSubItems() > maxSubItems)
  64. item.removeSubItem (item.getNumSubItems() - 1);
  65. }
  66. //==============================================================================
  67. class ErrorRootTreeItem : public JucerTreeViewBase
  68. {
  69. public:
  70. ErrorRootTreeItem (ErrorList& el) : errorList (el) {}
  71. String getRenamingName() const override { return getDisplayName(); }
  72. String getDisplayName() const override { return "Errors and Warnings"; }
  73. void setName (const String&) override {}
  74. bool isMissing() override { return false; }
  75. Icon getIcon() const override { return Icon (getIcons().bug, getContrastingColour (0.8f)); }
  76. bool canBeSelected() const override { return true; }
  77. bool mightContainSubItems() override { return true; }
  78. String getUniqueName() const override { return "errors"; }
  79. void refreshSubItems()
  80. {
  81. Array<DiagnosticMessage> errors;
  82. errorList.takeCopy (errors);
  83. StringArray files;
  84. for (const auto& m : errors)
  85. {
  86. files.addIfNotAlreadyThere (m.mainFile);
  87. if (m.associatedDiagnostic != nullptr)
  88. files.addIfNotAlreadyThere (m.associatedDiagnostic->mainFile);
  89. }
  90. limitNumberOfSubItems (*this, files.size());
  91. int i = 0;
  92. for (const auto& f : files)
  93. {
  94. if (i >= getNumSubItems() || static_cast<CompileUnitTreeItem*> (getSubItem (i))->compileUnit != f)
  95. {
  96. limitNumberOfSubItems (*this, i);
  97. addSubItem (new CompileUnitTreeItem (f));
  98. }
  99. static_cast<CompileUnitTreeItem*> (getSubItem (i))->refresh (errors);
  100. ++i;
  101. }
  102. }
  103. private:
  104. ErrorList& errorList;
  105. };
  106. //==============================================================================
  107. struct CompileUnitTreeItem : public JucerTreeViewBase
  108. {
  109. CompileUnitTreeItem (const String& filename) : compileUnit (filename) {}
  110. String getRenamingName() const override { return getDisplayName(); }
  111. String getDisplayName() const override { return File (compileUnit).exists() ? File (compileUnit).getFileName() : compileUnit; }
  112. void setName (const String&) override {}
  113. bool isMissing() override { return false; }
  114. Icon getIcon() const override { return Icon (getIcons().bug, getContrastingColour (0.8f)); }
  115. bool canBeSelected() const override { return true; }
  116. bool mightContainSubItems() override { return true; }
  117. String getUniqueName() const override { return String::toHexString (compileUnit.hashCode64()); }
  118. void addSubItems() override {}
  119. void showOverlays()
  120. {
  121. for (int i = 0; i < getNumSubItems(); ++i)
  122. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  123. e->showOverlays();
  124. }
  125. ErrorMessageTreeItem* getItemForError (const DiagnosticMessage& m) const
  126. {
  127. for (int i = 0; i < getNumSubItems(); ++i)
  128. if (auto* item = dynamic_cast<ErrorMessageTreeItem*> (getSubItem(i)))
  129. if (item->message == m)
  130. return item;
  131. return nullptr;
  132. }
  133. void refresh (const Array<DiagnosticMessage>& allErrors)
  134. {
  135. clearSubItems();
  136. for (const auto& error : allErrors)
  137. if (error.mainFile == compileUnit && error.associatedDiagnostic == nullptr)
  138. addSubItem (new ErrorMessageTreeItem (error));
  139. for (const auto& error : allErrors)
  140. if (error.mainFile == compileUnit && error.associatedDiagnostic != nullptr)
  141. if (ErrorMessageTreeItem* parent = getItemForError (*error.associatedDiagnostic))
  142. parent->addSubItem (new ErrorMessageTreeItem (error));
  143. }
  144. void showDocument() override
  145. {
  146. if (ProjectContentComponent* pcc = getProjectContentComponent())
  147. if (File (compileUnit).exists())
  148. pcc->showEditorForFile (File (compileUnit), true);
  149. }
  150. String compileUnit;
  151. };
  152. //==============================================================================
  153. struct ErrorMessageTreeItem : public JucerTreeViewBase
  154. {
  155. ErrorMessageTreeItem (const DiagnosticMessage& m)
  156. : message (m), itemHeight (14)
  157. {
  158. setOpenness (Openness::opennessClosed);
  159. uniqueID << message.message << ':' << message.range.toString();
  160. }
  161. ~ErrorMessageTreeItem()
  162. {
  163. overlay.deleteAndZero();
  164. }
  165. String getRenamingName() const override { return getDisplayName(); }
  166. String getDisplayName() const override { return message.message; }
  167. void setName (const String&) override {}
  168. bool isMissing() override { return false; }
  169. Icon getIcon() const override { return Icon (message.isNote() ? getIcons().info
  170. : getIcons().warning, getTextColour()); }
  171. bool canBeSelected() const override { return true; }
  172. bool mightContainSubItems() override { return getNumSubItems() != 0; }
  173. String getUniqueName() const override { return uniqueID; }
  174. Component* createItemComponent() override { return new ErrorItemComponent (*this); }
  175. struct ErrorItemComponent : public TreeItemComponent
  176. {
  177. ErrorItemComponent (ErrorMessageTreeItem& e) : TreeItemComponent (e) {}
  178. void resized() override
  179. {
  180. TreeItemComponent::resized();
  181. const int width = getWidth();
  182. const int iconWidth = 25; // TODO: this shouldn't be a magic number
  183. if (width > iconWidth)
  184. static_cast<ErrorMessageTreeItem&> (item).updateTextLayout (getWidth() - 30 /* accounting for icon */);
  185. }
  186. void lookAndFeelChanged() override
  187. {
  188. resized();
  189. }
  190. };
  191. void showPopupMenu() override
  192. {
  193. PopupMenu menu;
  194. menu.addItem (1, "Copy");
  195. launchPopupMenu (menu);
  196. }
  197. void handlePopupMenuResult (int resultCode) override
  198. {
  199. if (resultCode == 1)
  200. SystemClipboard::copyTextToClipboard (message.toString());
  201. }
  202. void paintIcon (Graphics& g, Rectangle<int> area) override
  203. {
  204. getIcon().draw (g, area.toFloat(), isIconCrossedOut());
  205. }
  206. void paintContent (Graphics& g, const Rectangle<int>& area) override
  207. {
  208. text.draw (g, area.toFloat());
  209. }
  210. int getItemHeight() const override
  211. {
  212. return itemHeight;
  213. }
  214. Colour getTextColour() const
  215. {
  216. Colour bkg (getOwnerView()->findColour (mainBackgroundColourId));
  217. return bkg.contrasting (message.isError() ? Colours::darkred
  218. : message.isWarning() ? Colours::yellow.darker()
  219. : Colours::grey, 0.4f);
  220. }
  221. void updateTextLayout (int width)
  222. {
  223. jassert (width >= 0);
  224. AttributedString s (message.message);
  225. s.setFont (Font (12.0f));
  226. s.setColour (getTextColour());
  227. text.createLayout (s, (float) width);
  228. const int newHeight = 2 + jmax (14, (int) text.getHeight());
  229. if (itemHeight != newHeight)
  230. {
  231. itemHeight = newHeight;
  232. treeHasChanged();
  233. }
  234. }
  235. SourceCodeEditor* getEditor()
  236. {
  237. if (ProjectContentComponent* pcc = getProjectContentComponent())
  238. {
  239. const File file (File::createFileWithoutCheckingPath (message.range.file));
  240. if (message.range.isValid() && file.exists() && pcc->showEditorForFile (file, false))
  241. {
  242. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  243. {
  244. return ed;
  245. }
  246. }
  247. }
  248. return nullptr;
  249. }
  250. void showDocument() override
  251. {
  252. if (SourceCodeEditor* ed = getEditor())
  253. {
  254. ed->grabKeyboardFocus();
  255. ed->highlight (message.range.range, false);
  256. if (auto cu = findCompileUnitParent())
  257. cu->showOverlays();
  258. }
  259. }
  260. CompileUnitTreeItem* findCompileUnitParent()
  261. {
  262. for (TreeViewItem* p = getParentItem(); p != nullptr; p = p->getParentItem())
  263. if (auto cu = dynamic_cast<CompileUnitTreeItem*> (p))
  264. return cu;
  265. return nullptr;
  266. }
  267. void showOverlays()
  268. {
  269. overlay.deleteAndZero();
  270. if (ProjectContentComponent* pcc = getProjectContentComponent())
  271. {
  272. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  273. {
  274. auto start = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getStart());
  275. auto end = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getEnd());
  276. if (auto* ce = dynamic_cast<LiveBuildCodeEditor*> (ed->editor.get()))
  277. overlay = ce->addDiagnosticOverlay (start, end, message.type);
  278. }
  279. }
  280. for (int i = 0; i < getNumSubItems(); ++i)
  281. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  282. e->showOverlays();
  283. }
  284. DiagnosticMessage message;
  285. private:
  286. String uniqueID;
  287. TextLayout text;
  288. int itemHeight;
  289. Component::SafePointer<Component> overlay;
  290. };
  291. };