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.

753 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 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. #ifndef __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__
  19. #define __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__
  20. #include "jucer_DrawableEditor.h"
  21. #include "../../model/Drawable/jucer_DrawableTypeHandler.h"
  22. //==============================================================================
  23. class DrawableEditorCanvas : public EditorCanvasBase,
  24. public FileDragAndDropTarget,
  25. public Timer
  26. {
  27. public:
  28. //==============================================================================
  29. DrawableEditorCanvas (DrawableEditor& editor_)
  30. : editor (editor_)
  31. {
  32. initialise();
  33. getDocument().getRoot().addListener (this);
  34. }
  35. ~DrawableEditorCanvas()
  36. {
  37. getDocument().getRoot().removeListener (this);
  38. shutdown();
  39. }
  40. //==============================================================================
  41. UndoManager& getUndoManager() throw() { return *getDocument().getUndoManager(); }
  42. DrawableEditor& getEditor() throw() { return editor; }
  43. DrawableDocument& getDocument() throw() { return editor.getDocument(); }
  44. Component* createComponentHolder()
  45. {
  46. return new DrawableComponent (this);
  47. }
  48. void documentChanged()
  49. {
  50. DrawableDocument& doc = getDocument();
  51. if (drawable == 0)
  52. {
  53. Drawable* newDrawable = Drawable::createFromValueTree (doc.getRootDrawableNode().getState(), &doc);
  54. drawable = dynamic_cast <DrawableComposite*> (newDrawable);
  55. jassert (drawable != 0);
  56. getComponentHolder()->repaint();
  57. }
  58. else
  59. {
  60. const Rectangle<float> damage (drawable->refreshFromValueTree (doc.getRootDrawableNode().getState(), &doc));
  61. getComponentHolder()->repaint (objectSpaceToScreenSpace (damage.getSmallestIntegerContainer()));
  62. }
  63. startTimer (500);
  64. }
  65. const Rectangle<int> getCanvasBounds()
  66. {
  67. return drawable->getBounds().getSmallestIntegerContainer();
  68. }
  69. void setCanvasBounds (const Rectangle<int>& newBounds) {}
  70. bool canResizeCanvas() const { return false; }
  71. //==============================================================================
  72. const ValueTree getObjectState (const String& objectId)
  73. {
  74. return getDocument().findDrawableState (objectId, false);
  75. }
  76. const SelectedItems::ItemType findObjectIdAt (const Point<int>& position)
  77. {
  78. if (drawable != 0)
  79. {
  80. for (int i = drawable->getNumDrawables(); --i >= 0;)
  81. {
  82. Drawable* d = drawable->getDrawable (i);
  83. if (d->hitTest ((float) position.getX(), (float) position.getY()))
  84. return d->getName();
  85. }
  86. }
  87. return String::empty;
  88. }
  89. void showPopupMenu (bool isClickOnSelectedObject)
  90. {
  91. PopupMenu m;
  92. if (isClickOnSelectedObject)
  93. {
  94. m.addCommandItem (commandManager, CommandIDs::toFront);
  95. m.addCommandItem (commandManager, CommandIDs::toBack);
  96. m.addSeparator();
  97. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  98. const int r = m.show();
  99. (void) r;
  100. }
  101. else
  102. {
  103. editor.showNewShapeMenu (0);
  104. }
  105. }
  106. void objectDoubleClicked (const MouseEvent& e, const ValueTree& state)
  107. {
  108. if (state.hasType (DrawablePath::valueTreeType)
  109. || state.hasType (DrawableImage::valueTreeType)
  110. || state.hasType (DrawableText::valueTreeType))
  111. {
  112. enableControlPointMode (state);
  113. }
  114. else if (state.hasType (DrawableComposite::valueTreeType))
  115. {
  116. // xxx
  117. }
  118. }
  119. bool hasSizeGuides() const { return false; }
  120. void getObjectPositionDependencies (const ValueTree& state, Array<ValueTree>& deps)
  121. {
  122. DrawableDocument& doc = getDocument();
  123. DrawableTypeInstance item (doc, state);
  124. OwnedArray <ControlPoint> points;
  125. item.getAllControlPoints (points);
  126. StringArray anchors;
  127. for (int i = 0; i < points.size(); ++i)
  128. {
  129. const RelativePoint p (points.getUnchecked(i)->getPosition());
  130. anchors.addIfNotAlreadyThere (p.x.getAnchorName1());
  131. anchors.addIfNotAlreadyThere (p.x.getAnchorName2());
  132. anchors.addIfNotAlreadyThere (p.y.getAnchorName1());
  133. anchors.addIfNotAlreadyThere (p.y.getAnchorName2());
  134. }
  135. for (int i = 0; i < anchors.size(); ++i)
  136. {
  137. const String anchor (anchors[i]);
  138. if (anchor.isNotEmpty() && ! anchor.startsWith ("parent."))
  139. {
  140. const ValueTree v (doc.findDrawableState (anchor.upToFirstOccurrenceOf (".", false, false), false));
  141. if (v.isValid())
  142. deps.add (v);
  143. }
  144. }
  145. }
  146. const Rectangle<float> getObjectPositionFloat (const ValueTree& state)
  147. {
  148. if (drawable != 0)
  149. {
  150. Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID());
  151. if (d != 0)
  152. return d->getBounds();
  153. }
  154. return Rectangle<float>();
  155. }
  156. void setObjectPositionFloat (const ValueTree& state, const Rectangle<float>& newPos)
  157. {
  158. if (drawable != 0)
  159. {
  160. Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID());
  161. if (d != 0)
  162. {
  163. d->refreshFromValueTree (state, &getDocument());
  164. DrawableTypeInstance di (getDocument(), state);
  165. di.setBounds (d, newPos);
  166. }
  167. }
  168. }
  169. const Rectangle<int> getObjectPosition (const ValueTree& state)
  170. {
  171. return getObjectPositionFloat (state).getSmallestIntegerContainer();
  172. }
  173. void transformObject (ValueTree& state, const AffineTransform& transform)
  174. {
  175. if (drawable != 0)
  176. {
  177. Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (state).getID());
  178. if (d != 0)
  179. {
  180. d->refreshFromValueTree (state, &getDocument());
  181. DrawableTypeInstance di (getDocument(), state);
  182. di.applyTransform (d, transform);
  183. }
  184. }
  185. }
  186. RelativeRectangle getObjectCoords (const ValueTree& state)
  187. {
  188. return RelativeRectangle();
  189. }
  190. //==============================================================================
  191. class ControlPointComponent : public OverlayItemComponent
  192. {
  193. public:
  194. ControlPointComponent (DrawableEditorCanvas* canvas, const ValueTree& drawableState_, int controlPointNum_)
  195. : OverlayItemComponent (canvas), drawableState (drawableState_),
  196. controlPointNum (controlPointNum_), isDragging (false), mouseDownResult (false), selected (false),
  197. sizeNormal (7), sizeOver (11)
  198. {
  199. setRepaintsOnMouseActivity (true);
  200. }
  201. ~ControlPointComponent()
  202. {
  203. }
  204. void paint (Graphics& g)
  205. {
  206. Rectangle<int> r (getLocalBounds());
  207. if (! isMouseOverOrDragging())
  208. r = r.reduced ((sizeOver - sizeNormal) / 2, (sizeOver - sizeNormal) / 2);
  209. g.setColour (Colour (selected ? 0xaaaaaaaa : 0xaa333333));
  210. g.drawRect (r);
  211. g.setColour (Colour (selected ? 0xaa000000 : 0x99ffffff));
  212. g.fillRect (r.reduced (1, 1));
  213. }
  214. bool hitTest (int x, int y)
  215. {
  216. if (isMouseOverOrDragging())
  217. return true;
  218. return getLocalBounds().reduced ((sizeOver - sizeNormal) / 2, (sizeOver - sizeNormal) / 2).contains (x, y);
  219. }
  220. void mouseDown (const MouseEvent& e)
  221. {
  222. isDragging = false;
  223. if (e.mods.isPopupMenu())
  224. {
  225. canvas->showPopupMenu (true);
  226. }
  227. else
  228. {
  229. mouseDownResult = canvas->getSelection().addToSelectionOnMouseDown (selectionId, e.mods);
  230. }
  231. }
  232. void mouseDrag (const MouseEvent& e)
  233. {
  234. if (! (isDragging || e.mouseWasClicked() || e.mods.isPopupMenu()))
  235. {
  236. canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, true, mouseDownResult);
  237. isDragging = true;
  238. canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()),
  239. ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre), false, Point<float>());
  240. }
  241. if (isDragging)
  242. {
  243. canvas->continueDrag (e.getEventRelativeTo (getParentComponent()));
  244. autoScrollForMouseEvent (e);
  245. }
  246. }
  247. void mouseUp (const MouseEvent& e)
  248. {
  249. if (! e.mods.isPopupMenu())
  250. {
  251. if (isDragging)
  252. canvas->endDrag (e.getEventRelativeTo (getParentComponent()));
  253. else
  254. canvas->getSelection().addToSelectionOnMouseUp (selectionId, e.mods, false, mouseDownResult);
  255. }
  256. }
  257. void mouseDoubleClick (const MouseEvent& e)
  258. {
  259. }
  260. class LineComponent : public OverlayItemComponent
  261. {
  262. public:
  263. LineComponent (EditorCanvasBase* canvas)
  264. : OverlayItemComponent (canvas)
  265. {}
  266. ~LineComponent() {}
  267. void setLine (const Line<float>& newLine)
  268. {
  269. if (line != newLine)
  270. {
  271. line = newLine;
  272. setBoundsInTargetSpace (Rectangle<float> (line.getStart(), line.getEnd())
  273. .getSmallestIntegerContainer().expanded (2, 2));
  274. repaint();
  275. }
  276. }
  277. void paint (Graphics& g)
  278. {
  279. g.setColour (Colours::black.withAlpha (0.6f));
  280. g.drawLine (Line<float> (pointToLocalSpace (line.getStart()),
  281. pointToLocalSpace (line.getEnd())), 1.0f);
  282. }
  283. bool hitTest (int, int)
  284. {
  285. return false;
  286. }
  287. private:
  288. Line<float> line;
  289. };
  290. void updatePosition (ControlPoint& point, RelativeCoordinate::NamedCoordinateFinder* nameFinder)
  291. {
  292. selectionId = point.getID();
  293. const Point<float> p (point.getPosition().resolve (nameFinder));
  294. setBoundsInTargetSpace (Rectangle<int> (roundToInt (p.getX()) - sizeOver / 2,
  295. roundToInt (p.getY()) - sizeOver / 2,
  296. sizeOver, sizeOver));
  297. const bool nowSelected = canvas->getSelection().isSelected (selectionId);
  298. if (selected != nowSelected)
  299. {
  300. selected = nowSelected;
  301. repaint();
  302. }
  303. if (point.hasLine())
  304. {
  305. if (line == 0)
  306. {
  307. line = new LineComponent (canvas);
  308. getParentComponent()->addAndMakeVisible (line, 0);
  309. }
  310. line->setLine (Line<float> (p, point.getEndOfLine().resolve (nameFinder)));
  311. }
  312. else
  313. {
  314. line = 0;
  315. }
  316. }
  317. private:
  318. ValueTree drawableState;
  319. int controlPointNum;
  320. bool isDragging, mouseDownResult, selected;
  321. String selectionId;
  322. ScopedPointer <LineComponent> line;
  323. const int sizeNormal, sizeOver;
  324. };
  325. void updateControlPointComponents (Component* parent, OwnedArray<OverlayItemComponent>& comps)
  326. {
  327. if (drawable == 0)
  328. {
  329. comps.clear();
  330. return;
  331. }
  332. DrawableTypeInstance item (getDocument(), controlPointEditingTarget);
  333. OwnedArray <ControlPoint> points;
  334. item.getVisibleControlPoints (points, getSelection());
  335. Drawable* d = drawable->getDrawableWithName (Drawable::ValueTreeWrapperBase (controlPointEditingTarget).getID());
  336. DrawableComposite* parentDrawable = d->getParent();
  337. comps.removeRange (points.size(), comps.size());
  338. BigInteger requiredIndexes;
  339. requiredIndexes.setRange (0, points.size(), true);
  340. for (int i = 0; i < points.size(); ++i)
  341. {
  342. ControlPointComponent* c = dynamic_cast <ControlPointComponent*> (comps[i]);
  343. if (c == 0)
  344. {
  345. c = new ControlPointComponent (this, controlPointEditingTarget, i);
  346. comps.set (i, c);
  347. parent->addAndMakeVisible (c);
  348. }
  349. c->updatePosition (*points.getUnchecked(i), parentDrawable);
  350. }
  351. }
  352. //==============================================================================
  353. MarkerListBase& getMarkerList (bool isX)
  354. {
  355. return getDocument().getMarkerList (isX);
  356. }
  357. double limitMarkerPosition (double pos)
  358. {
  359. return pos;
  360. }
  361. //==============================================================================
  362. SelectedItems& getSelection()
  363. {
  364. return editor.getSelection();
  365. }
  366. void deselectNonDraggableObjects()
  367. {
  368. }
  369. void findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& area)
  370. {
  371. const Rectangle<float> floatArea (area.toFloat());
  372. if (drawable != 0)
  373. {
  374. if (isControlPointMode())
  375. {
  376. DrawableTypeInstance item (getDocument(), controlPointEditingTarget);
  377. OwnedArray <ControlPoint> points;
  378. item.getVisibleControlPoints (points, getSelection());
  379. const Rectangle<float> floatArea (area.toFloat());
  380. for (int i = 0; i < points.size(); ++i)
  381. {
  382. const Point<float> p (points.getUnchecked(i)->getPosition().resolve (drawable));
  383. if (floatArea.contains (p))
  384. itemsFound.add (points.getUnchecked(i)->getID());
  385. }
  386. }
  387. else
  388. {
  389. for (int i = drawable->getNumDrawables(); --i >= 0;)
  390. {
  391. Drawable* d = drawable->getDrawable (i);
  392. if (d->getBounds().intersects (floatArea))
  393. itemsFound.add (d->getName());
  394. }
  395. }
  396. }
  397. }
  398. bool isControlPointId (const String& itemId)
  399. {
  400. return itemId.containsChar ('/');
  401. }
  402. //==============================================================================
  403. class ObjectDragOperation : public EditorDragOperation
  404. {
  405. public:
  406. ObjectDragOperation (DrawableEditorCanvas* canvas_, const Point<int>& mousePos,
  407. Component* snapGuideParentComp_, const ResizableBorderComponent::Zone& zone_, bool isRotating)
  408. : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, isRotating),
  409. drawableCanvas (canvas_)
  410. {
  411. }
  412. ~ObjectDragOperation() {}
  413. protected:
  414. DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); }
  415. void getSnapPointsX (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); }
  416. void getSnapPointsY (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); }
  417. UndoManager& getUndoManager() { return *getDocument().getUndoManager(); }
  418. void getObjectDependencies (const ValueTree& state, Array<ValueTree>& deps)
  419. {
  420. drawableCanvas->getObjectPositionDependencies (state, deps);
  421. }
  422. const Rectangle<float> getObjectPosition (const ValueTree& state)
  423. {
  424. return drawableCanvas->getObjectPositionFloat (state);
  425. }
  426. void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds)
  427. {
  428. drawableCanvas->setObjectPositionFloat (state, newBounds);
  429. }
  430. void transformObject (ValueTree& state, const AffineTransform& transform)
  431. {
  432. drawableCanvas->transformObject (state, transform);
  433. }
  434. float getMarkerPosition (const ValueTree& marker, bool isX)
  435. {
  436. return 0;
  437. }
  438. private:
  439. DrawableEditorCanvas* drawableCanvas;
  440. };
  441. //==============================================================================
  442. class ControlPointDragOperation : public EditorDragOperation
  443. {
  444. public:
  445. ControlPointDragOperation (DrawableEditorCanvas* canvas_,
  446. const DrawableTypeInstance& drawableItem_,
  447. DrawableComposite* drawable_,
  448. const Point<int>& mousePos,
  449. Component* snapGuideParentComp_,
  450. const ResizableBorderComponent::Zone& zone_)
  451. : EditorDragOperation (canvas_, mousePos, snapGuideParentComp_, zone_, false),
  452. drawableCanvas (canvas_), drawableItem (drawableItem_), drawable (drawable_)
  453. {
  454. drawableItem.getVisibleControlPoints (points, canvas_->getSelection());
  455. }
  456. ~ControlPointDragOperation() {}
  457. OwnedArray <ControlPoint> points;
  458. protected:
  459. DrawableDocument& getDocument() throw() { return drawableCanvas->getDocument(); }
  460. void getSnapPointsX (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); }
  461. void getSnapPointsY (Array<float>& points, bool /*includeCentre*/) { points.add (0.0f); }
  462. UndoManager& getUndoManager() { return *getDocument().getUndoManager(); }
  463. void getObjectDependencies (const ValueTree& state, Array<ValueTree>& deps)
  464. {
  465. drawableCanvas->getObjectPositionDependencies (drawableItem.getState(), deps);
  466. }
  467. const Rectangle<float> getObjectPosition (const ValueTree& state)
  468. {
  469. int index = state [Ids::id_];
  470. ControlPoint* cp = points[index];
  471. if (cp == 0)
  472. return Rectangle<float>();
  473. Point<float> p (cp->getPosition().resolve (drawable));
  474. return Rectangle<float> (p, p);
  475. }
  476. void setObjectPosition (ValueTree& state, const Rectangle<float>& newBounds)
  477. {
  478. int index = state [Ids::id_];
  479. ControlPoint* cp = points[index];
  480. if (cp != 0)
  481. {
  482. RelativePoint p (cp->getPosition());
  483. p.moveToAbsolute (newBounds.getPosition(), drawable);
  484. cp->setPosition (p, getDocument().getUndoManager());
  485. }
  486. }
  487. void transformObject (ValueTree& state, const AffineTransform& transform)
  488. {
  489. }
  490. float getMarkerPosition (const ValueTree& marker, bool isX)
  491. {
  492. return 0;
  493. }
  494. private:
  495. DrawableEditorCanvas* drawableCanvas;
  496. DrawableTypeInstance drawableItem;
  497. DrawableComposite* drawable;
  498. };
  499. //==============================================================================
  500. bool canRotate() const { return true; }
  501. DragOperation* createDragOperation (const Point<int>& mouseDownPos, Component* snapGuideParentComponent,
  502. const ResizableBorderComponent::Zone& zone, bool isRotating)
  503. {
  504. Array<ValueTree> selected, unselected;
  505. EditorDragOperation* drag = 0;
  506. if (isControlPointMode())
  507. {
  508. DrawableTypeInstance item (getDocument(), controlPointEditingTarget);
  509. ControlPointDragOperation* cpd = new ControlPointDragOperation (this, item, drawable, mouseDownPos, snapGuideParentComponent, zone);
  510. drag = cpd;
  511. for (int i = 0; i < cpd->points.size(); ++i)
  512. {
  513. const String pointId (cpd->points.getUnchecked(i)->getID());
  514. ValueTree v (Ids::controlPoint);
  515. v.setProperty (Ids::id_, i, 0);
  516. if (editor.getSelection().isSelected (pointId))
  517. selected.add (v);
  518. else
  519. unselected.add (v);
  520. }
  521. }
  522. else
  523. {
  524. DrawableComposite::ValueTreeWrapper mainGroup (getDocument().getRootDrawableNode());
  525. drag = new ObjectDragOperation (this, mouseDownPos, snapGuideParentComponent, zone, isRotating);
  526. for (int i = mainGroup.getNumDrawables(); --i >= 0;)
  527. {
  528. const ValueTree v (mainGroup.getDrawableState (i));
  529. if (editor.getSelection().isSelected (v[Drawable::ValueTreeWrapperBase::idProperty]))
  530. selected.add (v);
  531. else
  532. unselected.add (v);
  533. }
  534. }
  535. drag->initialise (selected, unselected);
  536. return drag;
  537. }
  538. void timerCallback()
  539. {
  540. stopTimer();
  541. if (! Component::isMouseButtonDownAnywhere())
  542. getUndoManager().beginNewTransaction();
  543. }
  544. //==============================================================================
  545. bool isInterestedInFileDrag (const StringArray& files)
  546. {
  547. for (int i = files.size(); --i >= 0;)
  548. if (File (files[i]).hasFileExtension ("svg;jpg;jpeg;gif;png"))
  549. return true;
  550. return false;
  551. }
  552. void filesDropped (const StringArray& files, int x, int y)
  553. {
  554. for (int i = files.size(); --i >= 0;)
  555. {
  556. const File f (files[i]);
  557. if (f.hasFileExtension ("svg"))
  558. {
  559. ValueTree newItem (getDocument().insertSVG (f, screenSpaceToObjectSpace (Point<int> (x, y).toFloat())));
  560. if (newItem.isValid())
  561. getSelection().selectOnly (Drawable::ValueTreeWrapperBase (newItem).getID());
  562. }
  563. else if (f.hasFileExtension ("jpg;jpeg;gif;png"))
  564. {
  565. }
  566. }
  567. }
  568. //==============================================================================
  569. class DrawableComponent : public Component
  570. {
  571. public:
  572. DrawableComponent (DrawableEditorCanvas* canvas_)
  573. : canvas (canvas_)
  574. {
  575. setOpaque (true);
  576. }
  577. ~DrawableComponent()
  578. {
  579. }
  580. void updateDrawable()
  581. {
  582. repaint();
  583. }
  584. void paint (Graphics& g)
  585. {
  586. canvas->handleUpdateNowIfNeeded();
  587. g.fillAll (Colours::white);
  588. const Point<int> origin (canvas->getOrigin());
  589. g.setOrigin (origin.getX(), origin.getY());
  590. if (origin.getX() > 0)
  591. {
  592. g.setColour (Colour::greyLevel (0.87f));
  593. g.drawVerticalLine (0, -10000.0f, 10000.0f);
  594. }
  595. if (origin.getY() > 0)
  596. {
  597. g.setColour (Colour::greyLevel (0.87f));
  598. g.drawHorizontalLine (0, -10000.0f, 10000.0f);
  599. }
  600. canvas->drawable->draw (g, 1.0f);
  601. }
  602. private:
  603. DrawableEditorCanvas* canvas;
  604. DrawableEditor& getEditor() const { return canvas->getEditor(); }
  605. };
  606. ScopedPointer<DrawableComposite> drawable;
  607. private:
  608. //==============================================================================
  609. DrawableEditor& editor;
  610. };
  611. #endif // __JUCER_DRAWABLEOBJECTCOMPONENT_JUCEHEADER__