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.

768 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_ComponentDocument.h"
  19. #include "Component Types/jucer_TextButton.h"
  20. #include "Component Types/jucer_ToggleButton.h"
  21. //==============================================================================
  22. static const char* const componentDocumentTag = "COMPONENT";
  23. static const char* const componentGroupTag = "COMPONENTS";
  24. static const char* const idProperty = "id";
  25. static const char* const compBoundsProperty = "position";
  26. static const char* const memberNameProperty = "memberName";
  27. static const char* const compNameProperty = "name";
  28. static const char* const metadataTagStart = "JUCER_" "COMPONENT_METADATA_START"; // written like this to avoid thinking this file is a component!
  29. static const char* const metadataTagEnd = "JUCER_" "COMPONENT_METADATA_END";
  30. //==============================================================================
  31. class ComponentBoundsEditor : public PropertyComponent,
  32. public ButtonListener,
  33. public Value::Listener
  34. {
  35. public:
  36. enum Type
  37. {
  38. left, top, right, bottom
  39. };
  40. //==============================================================================
  41. ComponentBoundsEditor (ComponentDocument& document_, const String& name, Type type_,
  42. const ValueTree& compState_, const Value& boundsValue_)
  43. : PropertyComponent (name, 40), document (document_), type (type_),
  44. compState (compState_), boundsValue (boundsValue_)
  45. {
  46. addAndMakeVisible (label = new Label (String::empty, String::empty));
  47. label->setEditable (true, true, false);
  48. label->setColour (Label::backgroundColourId, Colours::white);
  49. label->setColour (Label::outlineColourId, findColour (ComboBox::outlineColourId));
  50. label->getTextValue().referTo (Value (new BoundsCoordValueSource (boundsValue, type)));
  51. addAndMakeVisible (proportionButton = new TextButton ("%"));
  52. proportionButton->addButtonListener (this);
  53. addAndMakeVisible (anchorButton1 = new TextButton (String::empty));
  54. anchorButton1->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnTop | Button::ConnectedOnRight | Button::ConnectedOnBottom);
  55. anchorButton1->setTriggeredOnMouseDown (true);
  56. anchorButton1->addButtonListener (this);
  57. addAndMakeVisible (anchorButton2 = new TextButton (String::empty));
  58. anchorButton2->setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnTop | Button::ConnectedOnRight | Button::ConnectedOnBottom);
  59. anchorButton2->setTriggeredOnMouseDown (true);
  60. anchorButton2->addButtonListener (this);
  61. boundsValue.addListener (this);
  62. valueChanged (boundsValue);
  63. }
  64. ~ComponentBoundsEditor()
  65. {
  66. boundsValue.removeListener (this);
  67. deleteAllChildren();
  68. }
  69. void resized()
  70. {
  71. const Rectangle<int> r (getLookAndFeel().getPropertyComponentContentPosition (*this));
  72. label->setBounds (r.getX(), r.getY(), r.getWidth() / 2, r.getHeight() / 2);
  73. proportionButton->setBounds (r.getX() + r.getWidth() / 2, r.getY(),
  74. r.getWidth() / 2, r.getHeight() / 2);
  75. if (anchorButton2->isVisible())
  76. {
  77. anchorButton1->setBounds (r.getX(), r.getY() + r.getHeight() / 2, r.getWidth() / 2, r.getHeight() / 2);
  78. anchorButton2->setBounds (r.getX() + r.getWidth() / 2, r.getY() + r.getHeight() / 2, r.getWidth() / 2, r.getHeight() / 2);
  79. }
  80. else
  81. {
  82. anchorButton1->setBounds (r.getX(), r.getY() + r.getHeight() / 2, r.getWidth(), r.getHeight() / 2);
  83. }
  84. }
  85. void refresh()
  86. {
  87. }
  88. void buttonClicked (Button* button)
  89. {
  90. RectangleCoordinates r (boundsValue.toString());
  91. Coordinate& coord = getCoord (r);
  92. ScopedPointer<Coordinate::MarkerResolver> markers (document.createMarkerResolver (compState));
  93. if (button == proportionButton)
  94. {
  95. coord.toggleProportionality (*markers);
  96. boundsValue = r.toString();
  97. }
  98. else if (button == anchorButton1)
  99. {
  100. const String marker (pickMarker (anchorButton1, coord.getAnchor1()));
  101. if (marker.isNotEmpty())
  102. {
  103. coord.changeAnchor1 (marker, *markers);
  104. boundsValue = r.toString();
  105. }
  106. }
  107. else if (button == anchorButton2)
  108. {
  109. const String marker (pickMarker (anchorButton2, coord.getAnchor2()));
  110. if (marker.isNotEmpty())
  111. {
  112. coord.changeAnchor2 (marker, *markers);
  113. boundsValue = r.toString();
  114. }
  115. }
  116. }
  117. void valueChanged (Value&)
  118. {
  119. RectangleCoordinates r (boundsValue.toString());
  120. Coordinate& coord = getCoord (r);
  121. anchorButton1->setButtonText (coord.getAnchor1());
  122. anchorButton2->setVisible (coord.isProportional());
  123. anchorButton2->setButtonText (coord.getAnchor2());
  124. resized();
  125. }
  126. //==============================================================================
  127. class BoundsCoordValueSource : public Value::ValueSource,
  128. public Value::Listener
  129. {
  130. public:
  131. BoundsCoordValueSource (const Value& sourceValue_, Type type_)
  132. : sourceValue (sourceValue_), type (type_)
  133. {
  134. sourceValue.addListener (this);
  135. }
  136. ~BoundsCoordValueSource() {}
  137. const var getValue() const
  138. {
  139. RectangleCoordinates r (sourceValue.toString());
  140. Coordinate& coord = getCoord (r);
  141. if (coord.isProportional())
  142. return String (coord.getEditableValue()) + "%";
  143. return coord.getEditableValue();
  144. }
  145. void setValue (const var& newValue)
  146. {
  147. RectangleCoordinates r (sourceValue.toString());
  148. Coordinate& coord = getCoord (r);
  149. coord.setEditableValue ((double) newValue);
  150. const String newVal (r.toString());
  151. if (sourceValue != newVal)
  152. sourceValue = newVal;
  153. }
  154. void valueChanged (Value&)
  155. {
  156. sendChangeMessage (true);
  157. }
  158. //==============================================================================
  159. juce_UseDebuggingNewOperator
  160. protected:
  161. Value sourceValue;
  162. Type type;
  163. Coordinate& getCoord (RectangleCoordinates& r) const
  164. {
  165. return getCoordForType (type, r);
  166. }
  167. BoundsCoordValueSource (const BoundsCoordValueSource&);
  168. const BoundsCoordValueSource& operator= (const BoundsCoordValueSource&);
  169. };
  170. static Coordinate& getCoordForType (const Type type, RectangleCoordinates& r)
  171. {
  172. switch (type)
  173. {
  174. case left: return r.left;
  175. case right: return r.right;
  176. case top: return r.top;
  177. case bottom: return r.bottom;
  178. default: jassertfalse; break;
  179. }
  180. return r.left;
  181. }
  182. const String pickMarker (Component* button, const String& currentMarker)
  183. {
  184. const StringArray markers (document.getComponentMarkers (type == left || type == right));
  185. PopupMenu m;
  186. for (int i = 0; i < markers.size(); ++i)
  187. m.addItem (i + 1, markers[i], true, currentMarker == markers[i]);
  188. const int r = m.showAt (button);
  189. if (r > 0)
  190. return markers [r - 1];
  191. return String::empty;
  192. }
  193. private:
  194. ComponentDocument& document;
  195. Type type;
  196. ValueTree compState;
  197. Value boundsValue;
  198. Label* label;
  199. TextButton* proportionButton;
  200. TextButton* anchorButton1;
  201. TextButton* anchorButton2;
  202. Coordinate& getCoord (RectangleCoordinates& r)
  203. {
  204. return getCoordForType (type, r);
  205. }
  206. };
  207. //==============================================================================
  208. ComponentTypeHandler::ComponentTypeHandler (const String& name_, const String& xmlTag_,
  209. const String& memberNameRoot_)
  210. : name (name_), xmlTag (xmlTag_),
  211. memberNameRoot (memberNameRoot_)
  212. {
  213. }
  214. ComponentTypeHandler::~ComponentTypeHandler()
  215. {
  216. }
  217. Value ComponentTypeHandler::getValue (const var::identifier& name, ValueTree& state, ComponentDocument& document) const
  218. {
  219. return state.getPropertyAsValue (name, document.getUndoManager());
  220. }
  221. void ComponentTypeHandler::updateComponent (ComponentDocument& document, Component* comp, const ValueTree& state)
  222. {
  223. RectangleCoordinates pos (state [compBoundsProperty].toString());
  224. ScopedPointer<Coordinate::MarkerResolver> markers (document.createMarkerResolver (state));
  225. comp->setBounds (pos.resolve (*markers));
  226. comp->setName (state [compNameProperty]);
  227. }
  228. void ComponentTypeHandler::initialiseNewItem (ComponentDocument& document, ValueTree& state)
  229. {
  230. state.setProperty (compNameProperty, String::empty, 0);
  231. state.setProperty (memberNameProperty, document.getNonExistentMemberName (getMemberNameRoot()), 0);
  232. const Rectangle<int> bounds (getDefaultSize().withPosition (Point<int> (Random::getSystemRandom().nextInt (100) + 100,
  233. Random::getSystemRandom().nextInt (100) + 100)));
  234. state.setProperty (compBoundsProperty, RectangleCoordinates (bounds).toString(), 0);
  235. }
  236. void ComponentTypeHandler::createPropertyEditors (ComponentDocument& document, ValueTree& state, Array <PropertyComponent*>& props)
  237. {
  238. props.add (new ComponentBoundsEditor (document, "Left", ComponentBoundsEditor::left, state, getValue (compBoundsProperty, state, document)));
  239. props.add (new ComponentBoundsEditor (document, "Right", ComponentBoundsEditor::right, state, getValue (compBoundsProperty, state, document)));
  240. props.add (new ComponentBoundsEditor (document, "Top", ComponentBoundsEditor::top, state, getValue (compBoundsProperty, state, document)));
  241. props.add (new ComponentBoundsEditor (document, "Bottom", ComponentBoundsEditor::bottom, state, getValue (compBoundsProperty, state, document)));
  242. }
  243. //==============================================================================
  244. class ComponentTypeManager : public DeletedAtShutdown
  245. {
  246. public:
  247. ComponentTypeManager()
  248. {
  249. handlers.add (new TextButtonHandler());
  250. handlers.add (new ToggleButtonHandler());
  251. }
  252. ~ComponentTypeManager()
  253. {
  254. }
  255. juce_DeclareSingleton_SingleThreaded_Minimal (ComponentTypeManager);
  256. Component* createFromStoredType (ComponentDocument& document, const ValueTree& value)
  257. {
  258. ComponentTypeHandler* handler = getHandlerFor (value.getType());
  259. if (handler == 0)
  260. return 0;
  261. Component* c = handler->createComponent();
  262. if (c != 0)
  263. handler->updateComponent (document, c, value);
  264. return c;
  265. }
  266. ComponentTypeHandler* getHandlerFor (const String& type)
  267. {
  268. for (int i = handlers.size(); --i >= 0;)
  269. if (handlers.getUnchecked(i)->getXmlTag() == type)
  270. return handlers.getUnchecked(i);
  271. return 0;
  272. }
  273. const StringArray getTypeNames() const
  274. {
  275. StringArray s;
  276. for (int i = 0; i < handlers.size(); ++i)
  277. s.add (handlers.getUnchecked(i)->getName());
  278. return s;
  279. }
  280. int getNumHandlers() const { return handlers.size(); }
  281. ComponentTypeHandler* getHandler (const int index) const { return handlers[index]; }
  282. private:
  283. OwnedArray <ComponentTypeHandler> handlers;
  284. };
  285. juce_ImplementSingleton_SingleThreaded (ComponentTypeManager);
  286. //==============================================================================
  287. ComponentDocument::ComponentDocument (Project* project_, const File& cppFile_)
  288. : project (project_), cppFile (cppFile_), root (componentDocumentTag),
  289. changedSinceSaved (false)
  290. {
  291. reload();
  292. checkRootObject();
  293. root.addListener (this);
  294. }
  295. ComponentDocument::~ComponentDocument()
  296. {
  297. root.removeListener (this);
  298. }
  299. void ComponentDocument::beginNewTransaction()
  300. {
  301. undoManager.beginNewTransaction();
  302. }
  303. void ComponentDocument::valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  304. {
  305. changedSinceSaved = true;
  306. }
  307. void ComponentDocument::valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  308. {
  309. changedSinceSaved = true;
  310. }
  311. void ComponentDocument::valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  312. {
  313. changedSinceSaved = true;
  314. }
  315. bool ComponentDocument::isComponentFile (const File& file)
  316. {
  317. if (! file.hasFileExtension (".cpp"))
  318. return false;
  319. InputStream* in = file.createInputStream();
  320. if (in != 0)
  321. {
  322. BufferedInputStream buf (in, 8192, true);
  323. while (! buf.isExhausted())
  324. if (buf.readNextLine().contains (metadataTagStart))
  325. return true;
  326. }
  327. return false;
  328. }
  329. void ComponentDocument::writeCode (OutputStream& cpp, OutputStream& header)
  330. {
  331. cpp << "/** */"
  332. << newLine << newLine;
  333. header << "/** */"
  334. << newLine << newLine;
  335. }
  336. void ComponentDocument::writeMetadata (OutputStream& out)
  337. {
  338. out << "#if 0" << newLine
  339. << "/** Jucer-generated metadata section - Edit this data at own risk!" << newLine
  340. << metadataTagStart << newLine << newLine;
  341. ScopedPointer<XmlElement> xml (root.createXml());
  342. jassert (xml != 0);
  343. if (xml != 0)
  344. xml->writeToStream (out, String::empty, false, false);
  345. out << newLine
  346. << metadataTagEnd << " */" << newLine
  347. << "#endif" << newLine;
  348. }
  349. bool ComponentDocument::save()
  350. {
  351. MemoryOutputStream cpp, header;
  352. writeCode (cpp, header);
  353. writeMetadata (cpp);
  354. bool savedOk = overwriteFileWithNewDataIfDifferent (cppFile, cpp)
  355. && overwriteFileWithNewDataIfDifferent (cppFile.withFileExtension (".h"), header);
  356. if (savedOk)
  357. changedSinceSaved = false;
  358. return savedOk;
  359. }
  360. bool ComponentDocument::reload()
  361. {
  362. String xmlString;
  363. {
  364. InputStream* in = cppFile.createInputStream();
  365. if (in == 0)
  366. return false;
  367. BufferedInputStream buf (in, 8192, true);
  368. String::Concatenator xml (xmlString);
  369. while (! buf.isExhausted())
  370. {
  371. String line (buf.readNextLine());
  372. if (line.contains (metadataTagStart))
  373. {
  374. while (! buf.isExhausted())
  375. {
  376. line = buf.readNextLine();
  377. if (line.contains (metadataTagEnd))
  378. break;
  379. xml.append (line);
  380. xml.append (newLine);
  381. }
  382. break;
  383. }
  384. }
  385. }
  386. XmlDocument doc (xmlString);
  387. ScopedPointer<XmlElement> xml (doc.getDocumentElement());
  388. if (xml != 0 && xml->hasTagName (componentDocumentTag))
  389. {
  390. ValueTree newTree (ValueTree::fromXml (*xml));
  391. if (newTree.isValid())
  392. {
  393. root = newTree;
  394. checkRootObject();
  395. undoManager.clearUndoHistory();
  396. changedSinceSaved = false;
  397. return true;
  398. }
  399. }
  400. return false;
  401. }
  402. bool ComponentDocument::hasChangedSinceLastSave()
  403. {
  404. return changedSinceSaved;
  405. }
  406. void ComponentDocument::checkRootObject()
  407. {
  408. jassert (root.hasType (componentDocumentTag));
  409. if (! getComponentGroup().isValid())
  410. root.addChild (ValueTree (componentGroupTag), -1, 0);
  411. if (getClassName().toString().isEmpty())
  412. getClassName() = "NewComponent";
  413. if ((int) getCanvasWidth().getValue() <= 0)
  414. getCanvasWidth() = 640;
  415. if ((int) getCanvasHeight().getValue() <= 0)
  416. getCanvasHeight() = 480;
  417. }
  418. //==============================================================================
  419. const int menuItemOffset = 0x63451fa4;
  420. void ComponentDocument::addNewComponentMenuItems (PopupMenu& menu) const
  421. {
  422. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  423. for (int i = 0; i < typeNames.size(); ++i)
  424. menu.addItem (i + menuItemOffset, "New " + typeNames[i]);
  425. }
  426. void ComponentDocument::performNewComponentMenuItem (int menuResultCode)
  427. {
  428. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  429. if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeNames.size())
  430. {
  431. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandler (menuResultCode - menuItemOffset);
  432. jassert (handler != 0);
  433. if (handler != 0)
  434. {
  435. ValueTree state (handler->getXmlTag());
  436. state.setProperty (idProperty, createAlphaNumericUID(), 0);
  437. handler->initialiseNewItem (*this, state);
  438. getComponentGroup().addChild (state, -1, getUndoManager());
  439. }
  440. }
  441. }
  442. //==============================================================================
  443. ValueTree ComponentDocument::getComponentGroup() const
  444. {
  445. return root.getChildWithName (componentGroupTag);
  446. }
  447. int ComponentDocument::getNumComponents() const
  448. {
  449. return getComponentGroup().getNumChildren();
  450. }
  451. const ValueTree ComponentDocument::getComponent (int index) const
  452. {
  453. return getComponentGroup().getChild (index);
  454. }
  455. const ValueTree ComponentDocument::getComponentWithMemberName (const String& name) const
  456. {
  457. const ValueTree comps (getComponentGroup());
  458. for (int i = comps.getNumChildren(); --i >= 0;)
  459. {
  460. const ValueTree v (comps.getChild(i));
  461. if (v [memberNameProperty] == name)
  462. return v;
  463. }
  464. return ValueTree::invalid;
  465. }
  466. Component* ComponentDocument::createComponent (int index)
  467. {
  468. const ValueTree v (getComponentGroup().getChild (index));
  469. if (v.isValid())
  470. {
  471. Component* c = ComponentTypeManager::getInstance()->createFromStoredType (*this, v);
  472. c->getProperties().set (idProperty, v[idProperty]);
  473. jassert (c->getProperties()[idProperty].toString().isNotEmpty());
  474. return c;
  475. }
  476. return 0;
  477. }
  478. //==============================================================================
  479. class ComponentMarkerResolver : public Coordinate::MarkerResolver
  480. {
  481. public:
  482. ComponentMarkerResolver (ComponentDocument& doc, const ValueTree& state_, int parentWidth_, int parentHeight_)
  483. : owner (doc), state (state_),
  484. parentWidth (parentWidth_),
  485. parentHeight (parentHeight_)
  486. {}
  487. ~ComponentMarkerResolver() {}
  488. const Coordinate findMarker (const String& name, bool isHorizontal)
  489. {
  490. if (name == "left") return RectangleCoordinates (state [compBoundsProperty]).left;
  491. else if (name == "right") return RectangleCoordinates (state [compBoundsProperty]).right;
  492. else if (name == "top") return RectangleCoordinates (state [compBoundsProperty]).top;
  493. else if (name == "bottom") return RectangleCoordinates (state [compBoundsProperty]).bottom;
  494. else if (name == Coordinate::parentRightMarkerName) return Coordinate (parentWidth, isHorizontal);
  495. else if (name == Coordinate::parentBottomMarkerName) return Coordinate (parentHeight, isHorizontal);
  496. return Coordinate (isHorizontal);
  497. }
  498. private:
  499. ComponentDocument& owner;
  500. ValueTree state;
  501. int parentWidth, parentHeight;
  502. };
  503. const RectangleCoordinates ComponentDocument::getCoordsFor (const ValueTree& state) const
  504. {
  505. return RectangleCoordinates (state [compBoundsProperty]);
  506. }
  507. bool ComponentDocument::setCoordsFor (ValueTree& state, const RectangleCoordinates& pr)
  508. {
  509. const String newBoundsString (pr.toString());
  510. if (state[compBoundsProperty] == newBoundsString)
  511. return false;
  512. state.setProperty (compBoundsProperty, newBoundsString, getUndoManager());
  513. return true;
  514. }
  515. Coordinate::MarkerResolver* ComponentDocument::createMarkerResolver (const ValueTree& state)
  516. {
  517. return new ComponentMarkerResolver (*this, state, getCanvasWidth().getValue(), getCanvasHeight().getValue());
  518. }
  519. const StringArray ComponentDocument::getComponentMarkers (bool horizontal) const
  520. {
  521. StringArray s;
  522. if (horizontal)
  523. {
  524. s.add (Coordinate::parentLeftMarkerName);
  525. s.add (Coordinate::parentRightMarkerName);
  526. s.add ("left");
  527. s.add ("right");
  528. }
  529. else
  530. {
  531. s.add (Coordinate::parentTopMarkerName);
  532. s.add (Coordinate::parentBottomMarkerName);
  533. s.add ("top");
  534. s.add ("bottom");
  535. }
  536. return s;
  537. }
  538. void ComponentDocument::updateComponent (Component* comp)
  539. {
  540. const ValueTree v (getComponentState (comp));
  541. if (v.isValid())
  542. {
  543. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  544. jassert (handler != 0);
  545. if (handler != 0)
  546. handler->updateComponent (*this, comp, v);
  547. }
  548. }
  549. bool ComponentDocument::containsComponent (Component* comp) const
  550. {
  551. const ValueTree comps (getComponentGroup());
  552. for (int i = 0; i < comps.getNumChildren(); ++i)
  553. if (isStateForComponent (comps.getChild(i), comp))
  554. return true;
  555. return false;
  556. }
  557. const ValueTree ComponentDocument::getComponentState (Component* comp) const
  558. {
  559. jassert (comp != 0);
  560. const ValueTree comps (getComponentGroup());
  561. for (int i = 0; i < comps.getNumChildren(); ++i)
  562. if (isStateForComponent (comps.getChild(i), comp))
  563. return comps.getChild(i);
  564. jassertfalse;
  565. return ValueTree::invalid;
  566. }
  567. void ComponentDocument::getComponentProperties (Array <PropertyComponent*>& props, Component* comp)
  568. {
  569. ValueTree v (getComponentState (comp));
  570. if (v.isValid())
  571. {
  572. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  573. jassert (handler != 0);
  574. if (handler != 0)
  575. handler->createPropertyEditors (*this, v, props);
  576. }
  577. }
  578. bool ComponentDocument::isStateForComponent (const ValueTree& storedState, Component* comp) const
  579. {
  580. jassert (comp != 0);
  581. jassert (! storedState [idProperty].isVoid());
  582. return storedState [idProperty] == comp->getProperties() [idProperty];
  583. }
  584. const String ComponentDocument::getNonExistentMemberName (String suggestedName)
  585. {
  586. suggestedName = makeValidCppIdentifier (suggestedName, false, true, false);
  587. const String original (suggestedName);
  588. int num = 1;
  589. while (getComponentWithMemberName (suggestedName).isValid())
  590. {
  591. suggestedName = original;
  592. while (String ("0123456789").containsChar (suggestedName.getLastCharacter()))
  593. suggestedName = suggestedName.dropLastCharacters (1);
  594. suggestedName << num++;
  595. }
  596. return suggestedName;
  597. }
  598. //==============================================================================
  599. UndoManager* ComponentDocument::getUndoManager()
  600. {
  601. return &undoManager;
  602. }