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.

374 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. void setName (const String&) override {}
  111. void addSubItems() override {}
  112. bool isMissing() override { return false; }
  113. Icon getIcon() const override { return Icon (getIcons().bug, getContrastingColour (0.8f)); }
  114. bool canBeSelected() const override { return true; }
  115. bool mightContainSubItems() override { return true; }
  116. String getUniqueName() const override { return String::toHexString (compileUnit.hashCode64()); }
  117. String getRenamingName() const override { return getDisplayName(); }
  118. String getDisplayName() const override
  119. {
  120. if (File::isAbsolutePath(compileUnit))
  121. {
  122. File f (compileUnit);
  123. return f.exists() ? f.getFileName() : compileUnit;
  124. }
  125. return compileUnit;
  126. }
  127. void showOverlays()
  128. {
  129. for (int i = 0; i < getNumSubItems(); ++i)
  130. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  131. e->showOverlays();
  132. }
  133. ErrorMessageTreeItem* getItemForError (const DiagnosticMessage& m) const
  134. {
  135. for (int i = 0; i < getNumSubItems(); ++i)
  136. if (auto* item = dynamic_cast<ErrorMessageTreeItem*> (getSubItem(i)))
  137. if (item->message == m)
  138. return item;
  139. return nullptr;
  140. }
  141. void refresh (const Array<DiagnosticMessage>& allErrors)
  142. {
  143. clearSubItems();
  144. for (const auto& error : allErrors)
  145. if (error.mainFile == compileUnit && error.associatedDiagnostic == nullptr)
  146. addSubItem (new ErrorMessageTreeItem (error));
  147. for (const auto& error : allErrors)
  148. if (error.mainFile == compileUnit && error.associatedDiagnostic != nullptr)
  149. if (ErrorMessageTreeItem* parent = getItemForError (*error.associatedDiagnostic))
  150. parent->addSubItem (new ErrorMessageTreeItem (error));
  151. }
  152. void showDocument() override
  153. {
  154. if (ProjectContentComponent* pcc = getProjectContentComponent())
  155. if (File::isAbsolutePath (compileUnit) && File (compileUnit).exists())
  156. pcc->showEditorForFile (File (compileUnit), true);
  157. }
  158. String compileUnit;
  159. };
  160. //==============================================================================
  161. struct ErrorMessageTreeItem : public JucerTreeViewBase
  162. {
  163. ErrorMessageTreeItem (const DiagnosticMessage& m)
  164. : message (m), itemHeight (14)
  165. {
  166. setOpenness (Openness::opennessClosed);
  167. uniqueID << message.message << ':' << message.range.toString();
  168. }
  169. ~ErrorMessageTreeItem()
  170. {
  171. overlay.deleteAndZero();
  172. }
  173. String getRenamingName() const override { return getDisplayName(); }
  174. String getDisplayName() const override { return message.message; }
  175. void setName (const String&) override {}
  176. bool isMissing() override { return false; }
  177. Icon getIcon() const override { return Icon (message.isNote() ? getIcons().info
  178. : getIcons().warning, getTextColour()); }
  179. bool canBeSelected() const override { return true; }
  180. bool mightContainSubItems() override { return getNumSubItems() != 0; }
  181. String getUniqueName() const override { return uniqueID; }
  182. Component* createItemComponent() override { return new ErrorItemComponent (*this); }
  183. struct ErrorItemComponent : public TreeItemComponent
  184. {
  185. ErrorItemComponent (ErrorMessageTreeItem& e) : TreeItemComponent (e) {}
  186. void resized() override
  187. {
  188. TreeItemComponent::resized();
  189. const int width = getWidth();
  190. const int iconWidth = 25; // TODO: this shouldn't be a magic number
  191. if (width > iconWidth)
  192. static_cast<ErrorMessageTreeItem&> (item).updateTextLayout (getWidth() - 30 /* accounting for icon */);
  193. }
  194. void lookAndFeelChanged() override
  195. {
  196. resized();
  197. }
  198. };
  199. void showPopupMenu() override
  200. {
  201. PopupMenu menu;
  202. menu.addItem (1, "Copy");
  203. launchPopupMenu (menu);
  204. }
  205. void handlePopupMenuResult (int resultCode) override
  206. {
  207. if (resultCode == 1)
  208. SystemClipboard::copyTextToClipboard (message.toString());
  209. }
  210. void paintIcon (Graphics& g, Rectangle<int> area) override
  211. {
  212. getIcon().draw (g, area.toFloat(), isIconCrossedOut());
  213. }
  214. void paintContent (Graphics& g, const Rectangle<int>& area) override
  215. {
  216. text.draw (g, area.toFloat());
  217. }
  218. int getItemHeight() const override
  219. {
  220. return itemHeight;
  221. }
  222. Colour getTextColour() const
  223. {
  224. Colour bkg (getOwnerView()->findColour (mainBackgroundColourId));
  225. return bkg.contrasting (message.isError() ? Colours::darkred
  226. : message.isWarning() ? Colours::yellow.darker()
  227. : Colours::grey, 0.4f);
  228. }
  229. void updateTextLayout (int width)
  230. {
  231. jassert (width >= 0);
  232. AttributedString s (message.message);
  233. s.setFont (Font (12.0f));
  234. s.setColour (getTextColour());
  235. text.createLayout (s, (float) width);
  236. const int newHeight = 2 + jmax (14, (int) text.getHeight());
  237. if (itemHeight != newHeight)
  238. {
  239. itemHeight = newHeight;
  240. treeHasChanged();
  241. }
  242. }
  243. SourceCodeEditor* getEditor()
  244. {
  245. if (ProjectContentComponent* pcc = getProjectContentComponent())
  246. {
  247. const File file (File::createFileWithoutCheckingPath (message.range.file));
  248. if (message.range.isValid() && file.exists() && pcc->showEditorForFile (file, false))
  249. {
  250. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  251. {
  252. return ed;
  253. }
  254. }
  255. }
  256. return nullptr;
  257. }
  258. void showDocument() override
  259. {
  260. if (SourceCodeEditor* ed = getEditor())
  261. {
  262. ed->grabKeyboardFocus();
  263. ed->highlight (message.range.range, false);
  264. if (auto cu = findCompileUnitParent())
  265. cu->showOverlays();
  266. }
  267. }
  268. CompileUnitTreeItem* findCompileUnitParent()
  269. {
  270. for (TreeViewItem* p = getParentItem(); p != nullptr; p = p->getParentItem())
  271. if (auto cu = dynamic_cast<CompileUnitTreeItem*> (p))
  272. return cu;
  273. return nullptr;
  274. }
  275. void showOverlays()
  276. {
  277. overlay.deleteAndZero();
  278. if (ProjectContentComponent* pcc = getProjectContentComponent())
  279. {
  280. if (SourceCodeEditor* ed = dynamic_cast<SourceCodeEditor*> (pcc->getEditorComponent()))
  281. {
  282. auto start = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getStart());
  283. auto end = CodeDocument::Position (ed->editor->getDocument(), message.range.range.getEnd());
  284. if (auto* ce = dynamic_cast<LiveBuildCodeEditor*> (ed->editor.get()))
  285. overlay = ce->addDiagnosticOverlay (start, end, message.type);
  286. }
  287. }
  288. for (int i = 0; i < getNumSubItems(); ++i)
  289. if (auto* e = dynamic_cast<ErrorMessageTreeItem*> (getSubItem (i)))
  290. e->showOverlays();
  291. }
  292. DiagnosticMessage message;
  293. private:
  294. String uniqueID;
  295. TextLayout text;
  296. int itemHeight;
  297. Component::SafePointer<Component> overlay;
  298. };
  299. };