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.

698 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../Application/jucer_Headers.h"
  19. #include "jucer_SourceCodeEditor.h"
  20. #include "../Application/jucer_Application.h"
  21. //==============================================================================
  22. SourceCodeDocument::SourceCodeDocument (Project* p, const File& f)
  23. : modDetector (f), project (p)
  24. {
  25. }
  26. CodeDocument& SourceCodeDocument::getCodeDocument()
  27. {
  28. if (codeDoc == nullptr)
  29. {
  30. codeDoc.reset (new CodeDocument());
  31. reloadInternal();
  32. codeDoc->clearUndoHistory();
  33. }
  34. return *codeDoc;
  35. }
  36. std::unique_ptr<Component> SourceCodeDocument::createEditor()
  37. {
  38. auto e = std::make_unique<SourceCodeEditor> (this, getCodeDocument());
  39. applyLastState (*(e->editor));
  40. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
  41. return std::move (e);
  42. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  43. }
  44. void SourceCodeDocument::reloadFromFile()
  45. {
  46. getCodeDocument();
  47. reloadInternal();
  48. }
  49. void SourceCodeDocument::reloadInternal()
  50. {
  51. jassert (codeDoc != nullptr);
  52. modDetector.updateHash();
  53. auto fileContent = getFile().loadFileAsString();
  54. auto lineFeed = getLineFeedForFile (fileContent);
  55. if (lineFeed.isEmpty())
  56. {
  57. if (project != nullptr)
  58. lineFeed = project->getProjectLineFeed();
  59. else
  60. lineFeed = "\r\n";
  61. }
  62. codeDoc->setNewLineCharacters (lineFeed);
  63. codeDoc->applyChanges (fileContent);
  64. codeDoc->setSavePoint();
  65. }
  66. static bool writeCodeDocToFile (const File& file, CodeDocument& doc)
  67. {
  68. TemporaryFile temp (file);
  69. {
  70. FileOutputStream fo (temp.getFile());
  71. if (! (fo.openedOk() && doc.writeToStream (fo)))
  72. return false;
  73. }
  74. return temp.overwriteTargetFileWithTemporary();
  75. }
  76. bool SourceCodeDocument::saveSyncWithoutAsking()
  77. {
  78. if (writeCodeDocToFile (getFile(), getCodeDocument()))
  79. {
  80. getCodeDocument().setSavePoint();
  81. modDetector.updateHash();
  82. return true;
  83. }
  84. return false;
  85. }
  86. void SourceCodeDocument::saveAsync (std::function<void (bool)> callback)
  87. {
  88. callback (saveSyncWithoutAsking());
  89. }
  90. void SourceCodeDocument::saveAsAsync (std::function<void (bool)> callback)
  91. {
  92. chooser = std::make_unique<FileChooser> (TRANS ("Save As..."), getFile(), "*");
  93. auto flags = FileBrowserComponent::saveMode
  94. | FileBrowserComponent::canSelectFiles
  95. | FileBrowserComponent::warnAboutOverwriting;
  96. chooser->launchAsync (flags, [this, callback] (const FileChooser& fc)
  97. {
  98. if (fc.getResult() == File{})
  99. {
  100. callback (true);
  101. return;
  102. }
  103. callback (writeCodeDocToFile (fc.getResult(), getCodeDocument()));
  104. });
  105. }
  106. void SourceCodeDocument::updateLastState (CodeEditorComponent& editor)
  107. {
  108. lastState.reset (new CodeEditorComponent::State (editor));
  109. }
  110. void SourceCodeDocument::applyLastState (CodeEditorComponent& editor) const
  111. {
  112. if (lastState != nullptr)
  113. lastState->restoreState (editor);
  114. }
  115. //==============================================================================
  116. SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, CodeDocument& codeDocument)
  117. : DocumentEditorComponent (doc)
  118. {
  119. GenericCodeEditorComponent* ed = nullptr;
  120. auto file = document->getFile();
  121. if (fileNeedsCppSyntaxHighlighting (file))
  122. {
  123. ed = new CppCodeEditorComponent (file, codeDocument);
  124. }
  125. else
  126. {
  127. CodeTokeniser* tokeniser = nullptr;
  128. if (file.hasFileExtension ("xml;svg"))
  129. {
  130. static XmlTokeniser xmlTokeniser;
  131. tokeniser = &xmlTokeniser;
  132. }
  133. if (file.hasFileExtension ("lua"))
  134. {
  135. static LuaTokeniser luaTokeniser;
  136. tokeniser = &luaTokeniser;
  137. }
  138. ed = new GenericCodeEditorComponent (file, codeDocument, tokeniser);
  139. }
  140. setEditor (ed);
  141. }
  142. SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, GenericCodeEditorComponent* ed)
  143. : DocumentEditorComponent (doc)
  144. {
  145. setEditor (ed);
  146. }
  147. SourceCodeEditor::~SourceCodeEditor()
  148. {
  149. if (editor != nullptr)
  150. editor->getDocument().removeListener (this);
  151. getAppSettings().appearance.settings.removeListener (this);
  152. if (auto* doc = dynamic_cast<SourceCodeDocument*> (getDocument()))
  153. doc->updateLastState (*editor);
  154. }
  155. void SourceCodeEditor::setEditor (GenericCodeEditorComponent* newEditor)
  156. {
  157. if (editor != nullptr)
  158. editor->getDocument().removeListener (this);
  159. editor.reset (newEditor);
  160. addAndMakeVisible (newEditor);
  161. editor->setFont (AppearanceSettings::getDefaultCodeFont());
  162. editor->setTabSize (4, true);
  163. updateColourScheme();
  164. getAppSettings().appearance.settings.addListener (this);
  165. editor->getDocument().addListener (this);
  166. }
  167. void SourceCodeEditor::scrollToKeepRangeOnScreen (Range<int> range)
  168. {
  169. auto space = jmin (10, editor->getNumLinesOnScreen() / 3);
  170. const CodeDocument::Position start (editor->getDocument(), range.getStart());
  171. const CodeDocument::Position end (editor->getDocument(), range.getEnd());
  172. editor->scrollToKeepLinesOnScreen ({ start.getLineNumber() - space, end.getLineNumber() + space });
  173. }
  174. void SourceCodeEditor::highlight (Range<int> range, bool cursorAtStart)
  175. {
  176. scrollToKeepRangeOnScreen (range);
  177. if (cursorAtStart)
  178. {
  179. editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getEnd()), false);
  180. editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getStart()), true);
  181. }
  182. else
  183. {
  184. editor->setHighlightedRegion (range);
  185. }
  186. }
  187. void SourceCodeEditor::resized()
  188. {
  189. editor->setBounds (getLocalBounds());
  190. }
  191. void SourceCodeEditor::updateColourScheme()
  192. {
  193. getAppSettings().appearance.applyToCodeEditor (*editor);
  194. }
  195. void SourceCodeEditor::checkSaveState()
  196. {
  197. setEditedState (getDocument()->needsSaving());
  198. }
  199. void SourceCodeEditor::lookAndFeelChanged()
  200. {
  201. updateColourScheme();
  202. }
  203. void SourceCodeEditor::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateColourScheme(); }
  204. void SourceCodeEditor::valueTreeChildAdded (ValueTree&, ValueTree&) { updateColourScheme(); }
  205. void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&, int) { updateColourScheme(); }
  206. void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&, int, int) { updateColourScheme(); }
  207. void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); }
  208. void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); }
  209. void SourceCodeEditor::codeDocumentTextInserted (const String&, int) { checkSaveState(); }
  210. void SourceCodeEditor::codeDocumentTextDeleted (int, int) { checkSaveState(); }
  211. //==============================================================================
  212. GenericCodeEditorComponent::GenericCodeEditorComponent (const File& f, CodeDocument& codeDocument,
  213. CodeTokeniser* tokeniser)
  214. : CodeEditorComponent (codeDocument, tokeniser), file (f)
  215. {
  216. setScrollbarThickness (6);
  217. setCommandManager (&ProjucerApplication::getCommandManager());
  218. }
  219. GenericCodeEditorComponent::~GenericCodeEditorComponent() {}
  220. enum
  221. {
  222. showInFinderID = 0x2fe821e3,
  223. insertComponentID = 0x2fe821e4
  224. };
  225. void GenericCodeEditorComponent::addPopupMenuItems (PopupMenu& menu, const MouseEvent* e)
  226. {
  227. menu.addItem (showInFinderID,
  228. #if JUCE_MAC
  229. "Reveal " + file.getFileName() + " in Finder");
  230. #else
  231. "Reveal " + file.getFileName() + " in Explorer");
  232. #endif
  233. menu.addSeparator();
  234. CodeEditorComponent::addPopupMenuItems (menu, e);
  235. }
  236. void GenericCodeEditorComponent::performPopupMenuAction (int menuItemID)
  237. {
  238. if (menuItemID == showInFinderID)
  239. file.revealToUser();
  240. else
  241. CodeEditorComponent::performPopupMenuAction (menuItemID);
  242. }
  243. void GenericCodeEditorComponent::getAllCommands (Array <CommandID>& commands)
  244. {
  245. CodeEditorComponent::getAllCommands (commands);
  246. const CommandID ids[] = { CommandIDs::showFindPanel,
  247. CommandIDs::findSelection,
  248. CommandIDs::findNext,
  249. CommandIDs::findPrevious };
  250. commands.addArray (ids, numElementsInArray (ids));
  251. }
  252. void GenericCodeEditorComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  253. {
  254. auto anythingSelected = isHighlightActive();
  255. switch (commandID)
  256. {
  257. case CommandIDs::showFindPanel:
  258. result.setInfo (TRANS ("Find"), TRANS ("Searches for text in the current document."), "Editing", 0);
  259. result.defaultKeypresses.add (KeyPress ('f', ModifierKeys::commandModifier, 0));
  260. break;
  261. case CommandIDs::findSelection:
  262. result.setInfo (TRANS ("Find Selection"), TRANS ("Searches for the currently selected text."), "Editing", 0);
  263. result.setActive (anythingSelected);
  264. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  265. break;
  266. case CommandIDs::findNext:
  267. result.setInfo (TRANS ("Find Next"), TRANS ("Searches for the next occurrence of the current search-term."), "Editing", 0);
  268. result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier, 0));
  269. break;
  270. case CommandIDs::findPrevious:
  271. result.setInfo (TRANS ("Find Previous"), TRANS ("Searches for the previous occurrence of the current search-term."), "Editing", 0);
  272. result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  273. result.defaultKeypresses.add (KeyPress ('d', ModifierKeys::commandModifier, 0));
  274. break;
  275. default:
  276. CodeEditorComponent::getCommandInfo (commandID, result);
  277. break;
  278. }
  279. }
  280. bool GenericCodeEditorComponent::perform (const InvocationInfo& info)
  281. {
  282. switch (info.commandID)
  283. {
  284. case CommandIDs::showFindPanel: showFindPanel(); return true;
  285. case CommandIDs::findSelection: findSelection(); return true;
  286. case CommandIDs::findNext: findNext (true, true); return true;
  287. case CommandIDs::findPrevious: findNext (false, false); return true;
  288. default: break;
  289. }
  290. return CodeEditorComponent::perform (info);
  291. }
  292. void GenericCodeEditorComponent::addListener (GenericCodeEditorComponent::Listener* listener)
  293. {
  294. listeners.add (listener);
  295. }
  296. void GenericCodeEditorComponent::removeListener (GenericCodeEditorComponent::Listener* listener)
  297. {
  298. listeners.remove (listener);
  299. }
  300. //==============================================================================
  301. class GenericCodeEditorComponent::FindPanel final : public Component
  302. {
  303. public:
  304. FindPanel()
  305. {
  306. editor.setColour (CaretComponent::caretColourId, Colours::black);
  307. addAndMakeVisible (editor);
  308. label.setColour (Label::textColourId, Colours::white);
  309. label.attachToComponent (&editor, false);
  310. addAndMakeVisible (caseButton);
  311. caseButton.setColour (ToggleButton::textColourId, Colours::white);
  312. caseButton.setToggleState (isCaseSensitiveSearch(), dontSendNotification);
  313. caseButton.onClick = [this] { setCaseSensitiveSearch (caseButton.getToggleState()); };
  314. findPrev.setConnectedEdges (Button::ConnectedOnRight);
  315. findNext.setConnectedEdges (Button::ConnectedOnLeft);
  316. addAndMakeVisible (findPrev);
  317. addAndMakeVisible (findNext);
  318. setWantsKeyboardFocus (false);
  319. setFocusContainerType (FocusContainerType::keyboardFocusContainer);
  320. findPrev.setWantsKeyboardFocus (false);
  321. findNext.setWantsKeyboardFocus (false);
  322. editor.setText (getSearchString());
  323. editor.onTextChange = [this] { changeSearchString(); };
  324. editor.onReturnKey = [] { ProjucerApplication::getCommandManager().invokeDirectly (CommandIDs::findNext, true); };
  325. editor.onEscapeKey = [this]
  326. {
  327. if (auto* ed = getOwner())
  328. ed->hideFindPanel();
  329. };
  330. }
  331. void setCommandManager (ApplicationCommandManager* cm)
  332. {
  333. findPrev.setCommandToTrigger (cm, CommandIDs::findPrevious, true);
  334. findNext.setCommandToTrigger (cm, CommandIDs::findNext, true);
  335. }
  336. void paint (Graphics& g) override
  337. {
  338. Path outline;
  339. outline.addRoundedRectangle (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 8.0f);
  340. g.setColour (Colours::black.withAlpha (0.6f));
  341. g.fillPath (outline);
  342. g.setColour (Colours::white.withAlpha (0.8f));
  343. g.strokePath (outline, PathStrokeType (1.0f));
  344. }
  345. void resized() override
  346. {
  347. int y = 30;
  348. editor.setBounds (10, y, getWidth() - 20, 24);
  349. y += 30;
  350. caseButton.setBounds (10, y, getWidth() / 2 - 10, 22);
  351. findNext.setBounds (getWidth() - 40, y, 30, 22);
  352. findPrev.setBounds (getWidth() - 70, y, 30, 22);
  353. }
  354. void changeSearchString()
  355. {
  356. setSearchString (editor.getText());
  357. if (auto* ed = getOwner())
  358. ed->findNext (true, false);
  359. }
  360. GenericCodeEditorComponent* getOwner() const
  361. {
  362. return findParentComponentOfClass <GenericCodeEditorComponent>();
  363. }
  364. TextEditor editor;
  365. Label label { {}, "Find:" };
  366. ToggleButton caseButton { "Case-sensitive" };
  367. TextButton findPrev { "<" },
  368. findNext { ">" };
  369. };
  370. void GenericCodeEditorComponent::resized()
  371. {
  372. CodeEditorComponent::resized();
  373. if (findPanel != nullptr)
  374. {
  375. findPanel->setSize (jmin (260, getWidth() - 32), 100);
  376. findPanel->setTopRightPosition (getWidth() - 16, 8);
  377. }
  378. }
  379. void GenericCodeEditorComponent::showFindPanel()
  380. {
  381. if (findPanel == nullptr)
  382. {
  383. findPanel.reset (new FindPanel());
  384. findPanel->setCommandManager (&ProjucerApplication::getCommandManager());
  385. addAndMakeVisible (findPanel.get());
  386. resized();
  387. }
  388. if (findPanel != nullptr)
  389. {
  390. findPanel->editor.grabKeyboardFocus();
  391. findPanel->editor.selectAll();
  392. }
  393. }
  394. void GenericCodeEditorComponent::hideFindPanel()
  395. {
  396. findPanel.reset();
  397. }
  398. void GenericCodeEditorComponent::findSelection()
  399. {
  400. auto selected = getTextInRange (getHighlightedRegion());
  401. if (selected.isNotEmpty())
  402. {
  403. setSearchString (selected);
  404. findNext (true, true);
  405. }
  406. }
  407. void GenericCodeEditorComponent::findNext (bool forwards, bool skipCurrentSelection)
  408. {
  409. auto highlight = getHighlightedRegion();
  410. const CodeDocument::Position startPos (getDocument(), skipCurrentSelection ? highlight.getEnd()
  411. : highlight.getStart());
  412. auto lineNum = startPos.getLineNumber();
  413. auto linePos = startPos.getIndexInLine();
  414. auto totalLines = getDocument().getNumLines();
  415. auto searchText = getSearchString();
  416. auto caseSensitive = isCaseSensitiveSearch();
  417. for (auto linesToSearch = totalLines; --linesToSearch >= 0;)
  418. {
  419. auto line = getDocument().getLine (lineNum);
  420. int index;
  421. if (forwards)
  422. {
  423. index = caseSensitive ? line.indexOf (linePos, searchText)
  424. : line.indexOfIgnoreCase (linePos, searchText);
  425. }
  426. else
  427. {
  428. if (linePos >= 0)
  429. line = line.substring (0, linePos);
  430. index = caseSensitive ? line.lastIndexOf (searchText)
  431. : line.lastIndexOfIgnoreCase (searchText);
  432. }
  433. if (index >= 0)
  434. {
  435. const CodeDocument::Position p (getDocument(), lineNum, index);
  436. selectRegion (p, p.movedBy (searchText.length()));
  437. break;
  438. }
  439. if (forwards)
  440. {
  441. linePos = 0;
  442. lineNum = (lineNum + 1) % totalLines;
  443. }
  444. else
  445. {
  446. if (--lineNum < 0)
  447. lineNum = totalLines - 1;
  448. linePos = -1;
  449. }
  450. }
  451. }
  452. void GenericCodeEditorComponent::handleEscapeKey()
  453. {
  454. CodeEditorComponent::handleEscapeKey();
  455. hideFindPanel();
  456. }
  457. void GenericCodeEditorComponent::editorViewportPositionChanged()
  458. {
  459. CodeEditorComponent::editorViewportPositionChanged();
  460. listeners.call ([this] (Listener& l) { l.codeEditorViewportMoved (*this); });
  461. }
  462. //==============================================================================
  463. static CPlusPlusCodeTokeniser cppTokeniser;
  464. CppCodeEditorComponent::CppCodeEditorComponent (const File& f, CodeDocument& doc)
  465. : GenericCodeEditorComponent (f, doc, &cppTokeniser)
  466. {
  467. }
  468. CppCodeEditorComponent::~CppCodeEditorComponent() {}
  469. void CppCodeEditorComponent::handleReturnKey()
  470. {
  471. GenericCodeEditorComponent::handleReturnKey();
  472. auto pos = getCaretPos();
  473. String blockIndent, lastLineIndent;
  474. CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent);
  475. auto remainderOfBrokenLine = pos.getLineText();
  476. auto numLeadingWSChars = CodeHelpers::getLeadingWhitespace (remainderOfBrokenLine).length();
  477. if (numLeadingWSChars > 0)
  478. getDocument().deleteSection (pos, pos.movedBy (numLeadingWSChars));
  479. if (remainderOfBrokenLine.trimStart().startsWithChar ('}'))
  480. insertTextAtCaret (blockIndent);
  481. else
  482. insertTextAtCaret (lastLineIndent);
  483. auto previousLine = pos.movedByLines (-1).getLineText();
  484. auto trimmedPreviousLine = previousLine.trim();
  485. if ((trimmedPreviousLine.startsWith ("if ")
  486. || trimmedPreviousLine.startsWith ("if(")
  487. || trimmedPreviousLine.startsWith ("for ")
  488. || trimmedPreviousLine.startsWith ("for(")
  489. || trimmedPreviousLine.startsWith ("while(")
  490. || trimmedPreviousLine.startsWith ("while "))
  491. && trimmedPreviousLine.endsWithChar (')'))
  492. {
  493. insertTabAtCaret();
  494. }
  495. }
  496. void CppCodeEditorComponent::insertTextAtCaret (const String& newText)
  497. {
  498. if (getHighlightedRegion().isEmpty())
  499. {
  500. auto pos = getCaretPos();
  501. if ((newText == "{" || newText == "}")
  502. && pos.getLineNumber() > 0
  503. && pos.getLineText().trim().isEmpty())
  504. {
  505. moveCaretToStartOfLine (true);
  506. String blockIndent, lastLineIndent;
  507. if (CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent))
  508. {
  509. GenericCodeEditorComponent::insertTextAtCaret (blockIndent);
  510. if (newText == "{")
  511. insertTabAtCaret();
  512. }
  513. }
  514. }
  515. GenericCodeEditorComponent::insertTextAtCaret (newText);
  516. }
  517. void CppCodeEditorComponent::addPopupMenuItems (PopupMenu& menu, const MouseEvent* e)
  518. {
  519. GenericCodeEditorComponent::addPopupMenuItems (menu, e);
  520. menu.addSeparator();
  521. menu.addItem (insertComponentID, TRANS ("Insert code for a new Component class..."));
  522. }
  523. void CppCodeEditorComponent::performPopupMenuAction (int menuItemID)
  524. {
  525. if (menuItemID == insertComponentID)
  526. insertComponentClass();
  527. GenericCodeEditorComponent::performPopupMenuAction (menuItemID);
  528. }
  529. void CppCodeEditorComponent::insertComponentClass()
  530. {
  531. asyncAlertWindow = std::make_unique<AlertWindow> (TRANS ("Insert a new Component class"),
  532. TRANS ("Please enter a name for the new class"),
  533. MessageBoxIconType::NoIcon,
  534. nullptr);
  535. const String classNameField { "Class Name" };
  536. asyncAlertWindow->addTextEditor (classNameField, String(), String(), false);
  537. asyncAlertWindow->addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey));
  538. asyncAlertWindow->addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey));
  539. asyncAlertWindow->enterModalState (true,
  540. ModalCallbackFunction::create ([parent = SafePointer<CppCodeEditorComponent> { this }, classNameField] (int result)
  541. {
  542. if (parent == nullptr)
  543. return;
  544. auto& aw = *(parent->asyncAlertWindow);
  545. aw.exitModalState (result);
  546. aw.setVisible (false);
  547. if (result == 0)
  548. return;
  549. auto className = aw.getTextEditorContents (classNameField).trim();
  550. if (className == build_tools::makeValidIdentifier (className, false, true, false))
  551. {
  552. String code (BinaryData::jucer_InlineComponentTemplate_h);
  553. code = code.replace ("%%component_class%%", className);
  554. parent->insertTextAtCaret (code);
  555. return;
  556. }
  557. parent->insertComponentClass();
  558. }));
  559. }