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.

684 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 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. class SliderHandler : public ComponentTypeHandler
  19. {
  20. public:
  21. SliderHandler()
  22. : ComponentTypeHandler ("Slider", "Slider", typeid (Slider), 150, 24)
  23. {
  24. registerColour (Slider::backgroundColourId, "background", "bkgcol");
  25. registerColour (Slider::thumbColourId, "thumb", "thumbcol");
  26. registerColour (Slider::trackColourId, "track", "trackcol");
  27. registerColour (Slider::rotarySliderFillColourId, "rotary fill", "rotarysliderfill");
  28. registerColour (Slider::rotarySliderOutlineColourId, "rotary outln", "rotaryslideroutline");
  29. registerColour (Slider::textBoxTextColourId, "textbox text", "textboxtext");
  30. registerColour (Slider::textBoxBackgroundColourId, "textbox bkgd", "textboxbkgd");
  31. registerColour (Slider::textBoxHighlightColourId, "textbox highlt", "textboxhighlight");
  32. registerColour (Slider::textBoxOutlineColourId, "textbox outln", "textboxoutline");
  33. }
  34. Component* createNewComponent (JucerDocument*)
  35. {
  36. return new Slider ("new slider");
  37. }
  38. XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout)
  39. {
  40. XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
  41. Slider* const s = dynamic_cast <Slider*> (comp);
  42. e->setAttribute ("min", s->getMinimum());
  43. e->setAttribute ("max", s->getMaximum());
  44. e->setAttribute ("int", s->getInterval());
  45. e->setAttribute ("style", sliderStyleToString (s->getSliderStyle()));
  46. e->setAttribute ("textBoxPos", textBoxPosToString (s->getTextBoxPosition()));
  47. e->setAttribute ("textBoxEditable", s->isTextBoxEditable());
  48. e->setAttribute ("textBoxWidth", s->getTextBoxWidth());
  49. e->setAttribute ("textBoxHeight", s->getTextBoxHeight());
  50. e->setAttribute ("skewFactor", s->getSkewFactor());
  51. return e;
  52. }
  53. bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout)
  54. {
  55. if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
  56. return false;
  57. Slider* const s = dynamic_cast <Slider*> (comp);
  58. s->setRange (xml.getDoubleAttribute ("min", 0.0),
  59. xml.getDoubleAttribute ("max", 10.0),
  60. xml.getDoubleAttribute ("int", 0.0));
  61. s->setSliderStyle (sliderStringToStyle (xml.getStringAttribute ("style", "LinearHorizontal")));
  62. s->setTextBoxStyle (stringToTextBoxPos (xml.getStringAttribute ("textBoxPos", "TextBoxLeft")),
  63. ! xml.getBoolAttribute ("textBoxEditable", true),
  64. xml.getIntAttribute ("textBoxWidth", 80),
  65. xml.getIntAttribute ("textBoxHeight", 20));
  66. s->setSkewFactor (xml.getDoubleAttribute ("skewFactor", 1.0));
  67. return true;
  68. }
  69. String getCreationParameters (Component* component)
  70. {
  71. return quotedString (component->getName());
  72. }
  73. void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName)
  74. {
  75. ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
  76. Slider* const s = dynamic_cast <Slider*> (component);
  77. String r;
  78. r << memberVariableName << "->setRange ("
  79. << s->getMinimum() << ", " << s->getMaximum() << ", " << s->getInterval()
  80. << ");\n"
  81. << memberVariableName << "->setSliderStyle (Slider::"
  82. << sliderStyleToString (s->getSliderStyle()) << ");\n"
  83. << memberVariableName << "->setTextBoxStyle (Slider::"
  84. << textBoxPosToString (s->getTextBoxPosition())
  85. << ", " << CodeHelpers::boolLiteral (! s->isTextBoxEditable())
  86. << ", " << s->getTextBoxWidth() << ", " << s->getTextBoxHeight() << ");\n"
  87. << getColourIntialisationCode (component, memberVariableName);
  88. if (needsCallback (component))
  89. r << memberVariableName << "->addListener (this);\n";
  90. if (s->getSkewFactor() != 1.0)
  91. r << memberVariableName << "->setSkewFactor (" << s->getSkewFactor() << ");\n";
  92. r << '\n';
  93. code.constructorCode += r;
  94. }
  95. void fillInGeneratedCode (Component* component, GeneratedCode& code)
  96. {
  97. ComponentTypeHandler::fillInGeneratedCode (component, code);
  98. if (needsCallback (component))
  99. {
  100. String& callback = code.getCallbackCode ("public SliderListener",
  101. "void",
  102. "sliderValueChanged (Slider* sliderThatWasMoved)",
  103. true);
  104. if (callback.isNotEmpty())
  105. callback << "else ";
  106. const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
  107. const String userCodeComment ("UserSliderCode_" + memberVariableName);
  108. callback
  109. << "if (sliderThatWasMoved == " << memberVariableName
  110. << ")\n{\n //[" << userCodeComment << "] -- add your slider handling code here..\n //[/" << userCodeComment << "]\n}\n";
  111. }
  112. }
  113. void getEditableProperties (Component* component, JucerDocument& document, Array <PropertyComponent*>& properties)
  114. {
  115. ComponentTypeHandler::getEditableProperties (component, document, properties);
  116. Slider* s = dynamic_cast <Slider*> (component);
  117. jassert (s != 0);
  118. properties.add (new SliderRangeProperty (s, document, "minimum", 0));
  119. properties.add (new SliderRangeProperty (s, document, "maximum", 1));
  120. properties.add (new SliderRangeProperty (s, document, "interval", 2));
  121. properties.add (new SliderTypeProperty (s, document));
  122. properties.add (new SliderTextboxProperty (s, document));
  123. properties.add (new SliderTextboxEditableProperty (s, document));
  124. properties.add (new SliderTextboxSizeProperty (s, document, true));
  125. properties.add (new SliderTextboxSizeProperty (s, document, false));
  126. properties.add (new SliderSkewProperty (s, document));
  127. addColourProperties (component, document, properties);
  128. }
  129. static bool needsCallback (Component*)
  130. {
  131. return true; //xxx should be a property
  132. }
  133. private:
  134. //==============================================================================
  135. class SliderTypeProperty : public ComponentChoiceProperty <Slider>
  136. {
  137. public:
  138. SliderTypeProperty (Slider* slider, JucerDocument& document)
  139. : ComponentChoiceProperty <Slider> ("type", slider, document)
  140. {
  141. choices.add ("Linear Horizontal");
  142. choices.add ("Linear Vertical");
  143. choices.add ("Linear Bar");
  144. choices.add ("Rotary");
  145. choices.add ("Rotary HorizontalDrag");
  146. choices.add ("Rotary VerticalDrag");
  147. choices.add ("Rotary HorizontalVerticalDrag");
  148. choices.add ("Inc/Dec Buttons");
  149. choices.add ("Two Value Horizontal");
  150. choices.add ("Two Value Vertical");
  151. choices.add ("Three Value Horizontal");
  152. choices.add ("Three Value Vertical");
  153. }
  154. void setIndex (int newIndex)
  155. {
  156. const Slider::SliderStyle types[] = { Slider::LinearHorizontal,
  157. Slider::LinearVertical,
  158. Slider::LinearBar,
  159. Slider::Rotary,
  160. Slider::RotaryHorizontalDrag,
  161. Slider::RotaryVerticalDrag,
  162. Slider::RotaryHorizontalVerticalDrag,
  163. Slider::IncDecButtons,
  164. Slider::TwoValueHorizontal,
  165. Slider::TwoValueVertical,
  166. Slider::ThreeValueHorizontal,
  167. Slider::ThreeValueVertical };
  168. if (newIndex >= 0 && newIndex < numElementsInArray (types))
  169. {
  170. document.perform (new SliderTypeChangeAction (component, *document.getComponentLayout(), types [newIndex]),
  171. "Change Slider style");
  172. }
  173. }
  174. int getIndex() const
  175. {
  176. const Slider::SliderStyle types[] = { Slider::LinearHorizontal,
  177. Slider::LinearVertical,
  178. Slider::LinearBar,
  179. Slider::Rotary,
  180. Slider::RotaryHorizontalDrag,
  181. Slider::RotaryVerticalDrag,
  182. Slider::RotaryHorizontalVerticalDrag,
  183. Slider::IncDecButtons,
  184. Slider::TwoValueHorizontal,
  185. Slider::TwoValueVertical,
  186. Slider::ThreeValueHorizontal,
  187. Slider::ThreeValueVertical };
  188. for (int i = 0; i < numElementsInArray (types); ++i)
  189. if (types [i] == dynamic_cast <Slider*> (component)->getSliderStyle())
  190. return i;
  191. return -1;
  192. }
  193. private:
  194. class SliderTypeChangeAction : public ComponentUndoableAction <Slider>
  195. {
  196. public:
  197. SliderTypeChangeAction (Slider* const comp, ComponentLayout& layout, const Slider::SliderStyle newState_)
  198. : ComponentUndoableAction <Slider> (comp, layout),
  199. newState (newState_)
  200. {
  201. oldState = comp->getSliderStyle();
  202. }
  203. bool perform()
  204. {
  205. showCorrectTab();
  206. getComponent()->setSliderStyle (newState);
  207. changed();
  208. return true;
  209. }
  210. bool undo()
  211. {
  212. showCorrectTab();
  213. getComponent()->setSliderStyle (oldState);
  214. changed();
  215. return true;
  216. }
  217. Slider::SliderStyle newState, oldState;
  218. };
  219. };
  220. //==============================================================================
  221. class SliderTextboxProperty : public ComponentChoiceProperty <Slider>
  222. {
  223. public:
  224. SliderTextboxProperty (Slider* slider, JucerDocument& document)
  225. : ComponentChoiceProperty <Slider> ("text position", slider, document)
  226. {
  227. choices.add ("No text box");
  228. choices.add ("Text box on left");
  229. choices.add ("Text box on right");
  230. choices.add ("Text box above");
  231. choices.add ("Text box below");
  232. }
  233. void setIndex (int newIndex)
  234. {
  235. const Slider::TextEntryBoxPosition types[] = { Slider::NoTextBox,
  236. Slider::TextBoxLeft,
  237. Slider::TextBoxRight,
  238. Slider::TextBoxAbove,
  239. Slider::TextBoxBelow };
  240. if (newIndex >= 0 && newIndex < numElementsInArray (types))
  241. {
  242. document.perform (new SliderTextBoxChangeAction (component, *document.getComponentLayout(), types [newIndex]),
  243. "Change Slider textbox");
  244. }
  245. }
  246. int getIndex() const
  247. {
  248. const Slider::TextEntryBoxPosition types[] = { Slider::NoTextBox,
  249. Slider::TextBoxLeft,
  250. Slider::TextBoxRight,
  251. Slider::TextBoxAbove,
  252. Slider::TextBoxBelow };
  253. for (int i = 0; i < numElementsInArray (types); ++i)
  254. if (types [i] == component->getTextBoxPosition())
  255. return i;
  256. return -1;
  257. }
  258. private:
  259. class SliderTextBoxChangeAction : public ComponentUndoableAction <Slider>
  260. {
  261. public:
  262. SliderTextBoxChangeAction (Slider* const comp, ComponentLayout& layout, const Slider::TextEntryBoxPosition newState_)
  263. : ComponentUndoableAction <Slider> (comp, layout),
  264. newState (newState_)
  265. {
  266. oldState = comp->getTextBoxPosition();
  267. }
  268. bool perform()
  269. {
  270. showCorrectTab();
  271. getComponent()->setTextBoxStyle (newState,
  272. ! getComponent()->isTextBoxEditable(),
  273. getComponent()->getTextBoxWidth(),
  274. getComponent()->getTextBoxHeight());
  275. changed();
  276. return true;
  277. }
  278. bool undo()
  279. {
  280. showCorrectTab();
  281. getComponent()->setTextBoxStyle (oldState,
  282. ! getComponent()->isTextBoxEditable(),
  283. getComponent()->getTextBoxWidth(),
  284. getComponent()->getTextBoxHeight());
  285. changed();
  286. return true;
  287. }
  288. Slider::TextEntryBoxPosition newState, oldState;
  289. };
  290. };
  291. //==============================================================================
  292. class SliderTextboxEditableProperty : public ComponentBooleanProperty <Slider>
  293. {
  294. public:
  295. SliderTextboxEditableProperty (Slider* slider, JucerDocument& document)
  296. : ComponentBooleanProperty <Slider> ("text box mode", "Editable", "Editable", slider, document)
  297. {
  298. }
  299. void setState (bool newState)
  300. {
  301. document.perform (new SliderEditableChangeAction (component, *document.getComponentLayout(), newState),
  302. "Change Slider editability");
  303. }
  304. bool getState() const
  305. {
  306. return component->isTextBoxEditable();
  307. }
  308. private:
  309. class SliderEditableChangeAction : public ComponentUndoableAction <Slider>
  310. {
  311. public:
  312. SliderEditableChangeAction (Slider* const comp, ComponentLayout& layout, const bool newState_)
  313. : ComponentUndoableAction <Slider> (comp, layout),
  314. newState (newState_)
  315. {
  316. oldState = comp->isTextBoxEditable();
  317. }
  318. bool perform()
  319. {
  320. showCorrectTab();
  321. getComponent()->setTextBoxIsEditable (newState);
  322. changed();
  323. return true;
  324. }
  325. bool undo()
  326. {
  327. showCorrectTab();
  328. getComponent()->setTextBoxIsEditable (oldState);
  329. changed();
  330. return true;
  331. }
  332. bool newState, oldState;
  333. };
  334. };
  335. //==============================================================================
  336. class SliderTextboxSizeProperty : public ComponentTextProperty <Slider>
  337. {
  338. public:
  339. SliderTextboxSizeProperty (Slider* slider, JucerDocument& document, const bool isWidth_)
  340. : ComponentTextProperty <Slider> (isWidth_ ? "text box width" : "text box height",
  341. 12, false, slider, document),
  342. isWidth (isWidth_)
  343. {
  344. }
  345. void setText (const String& newText)
  346. {
  347. document.perform (new SliderBoxSizeChangeAction (component, *document.getComponentLayout(), isWidth, newText.getIntValue()),
  348. "Change Slider textbox size");
  349. }
  350. String getText() const
  351. {
  352. return String (isWidth ? component->getTextBoxWidth()
  353. : component->getTextBoxHeight());
  354. }
  355. private:
  356. const bool isWidth;
  357. class SliderBoxSizeChangeAction : public ComponentUndoableAction <Slider>
  358. {
  359. public:
  360. SliderBoxSizeChangeAction (Slider* const comp, ComponentLayout& layout, const bool isWidth_, int newSize_)
  361. : ComponentUndoableAction <Slider> (comp, layout),
  362. isWidth (isWidth_),
  363. newSize (newSize_)
  364. {
  365. oldSize = isWidth ? comp->getTextBoxWidth()
  366. : comp->getTextBoxHeight();
  367. }
  368. bool perform()
  369. {
  370. showCorrectTab();
  371. Slider& c = *getComponent();
  372. if (isWidth)
  373. c.setTextBoxStyle (c.getTextBoxPosition(),
  374. ! c.isTextBoxEditable(),
  375. newSize,
  376. c.getTextBoxHeight());
  377. else
  378. c.setTextBoxStyle (c.getTextBoxPosition(),
  379. ! c.isTextBoxEditable(),
  380. c.getTextBoxWidth(),
  381. newSize);
  382. changed();
  383. return true;
  384. }
  385. bool undo()
  386. {
  387. showCorrectTab();
  388. Slider& c = *getComponent();
  389. if (isWidth)
  390. c.setTextBoxStyle (c.getTextBoxPosition(),
  391. ! c.isTextBoxEditable(),
  392. oldSize,
  393. c.getTextBoxHeight());
  394. else
  395. c.setTextBoxStyle (c.getTextBoxPosition(),
  396. ! c.isTextBoxEditable(),
  397. c.getTextBoxWidth(),
  398. oldSize);
  399. changed();
  400. return true;
  401. }
  402. bool isWidth;
  403. int newSize, oldSize;
  404. };
  405. };
  406. //==============================================================================
  407. class SliderRangeProperty : public ComponentTextProperty <Slider>
  408. {
  409. public:
  410. SliderRangeProperty (Slider* slider, JucerDocument& document,
  411. const String& name, const int rangeParam_)
  412. : ComponentTextProperty <Slider> (name, 15, false, slider, document),
  413. rangeParam (rangeParam_)
  414. {
  415. }
  416. void setText (const String& newText)
  417. {
  418. double state [3];
  419. state [0] = component->getMinimum();
  420. state [1] = component->getMaximum();
  421. state [2] = component->getInterval();
  422. state [rangeParam] = newText.getDoubleValue();
  423. document.perform (new SliderRangeChangeAction (component, *document.getComponentLayout(), state),
  424. "Change Slider range");
  425. }
  426. String getText() const
  427. {
  428. Slider* s = dynamic_cast <Slider*> (component);
  429. jassert (s != nullptr);
  430. switch (rangeParam)
  431. {
  432. case 0: return String (s->getMinimum());
  433. case 1: return String (s->getMaximum());
  434. case 2: return String (s->getInterval());
  435. default: jassertfalse; break;
  436. }
  437. return String::empty;
  438. }
  439. private:
  440. const int rangeParam;
  441. class SliderRangeChangeAction : public ComponentUndoableAction <Slider>
  442. {
  443. public:
  444. SliderRangeChangeAction (Slider* const comp, ComponentLayout& layout, const double newState_[3])
  445. : ComponentUndoableAction <Slider> (comp, layout)
  446. {
  447. newState [0] = newState_ [0];
  448. newState [1] = newState_ [1];
  449. newState [2] = newState_ [2];
  450. oldState [0] = comp->getMinimum();
  451. oldState [1] = comp->getMaximum();
  452. oldState [2] = comp->getInterval();
  453. }
  454. bool perform()
  455. {
  456. showCorrectTab();
  457. getComponent()->setRange (newState[0], newState[1], newState[2]);
  458. changed();
  459. return true;
  460. }
  461. bool undo()
  462. {
  463. showCorrectTab();
  464. getComponent()->setRange (oldState[0], oldState[1], oldState[2]);
  465. changed();
  466. return true;
  467. }
  468. double newState[3], oldState[3];
  469. };
  470. };
  471. //==============================================================================
  472. class SliderSkewProperty : public ComponentTextProperty <Slider>
  473. {
  474. public:
  475. SliderSkewProperty (Slider* slider, JucerDocument& document)
  476. : ComponentTextProperty <Slider> ("skew factor", 12, false, slider, document)
  477. {
  478. }
  479. void setText (const String& newText)
  480. {
  481. const double skew = jlimit (0.001, 1000.0, newText.getDoubleValue());
  482. document.perform (new SliderSkewChangeAction (component, *document.getComponentLayout(), skew),
  483. "Change Slider skew");
  484. }
  485. String getText() const
  486. {
  487. Slider* s = dynamic_cast <Slider*> (component);
  488. jassert (s != 0);
  489. return String (s->getSkewFactor());
  490. }
  491. private:
  492. class SliderSkewChangeAction : public ComponentUndoableAction <Slider>
  493. {
  494. public:
  495. SliderSkewChangeAction (Slider* const comp, ComponentLayout& layout, const double newValue_)
  496. : ComponentUndoableAction <Slider> (comp, layout)
  497. {
  498. newValue = newValue_;
  499. oldValue = comp->getSkewFactor();
  500. }
  501. bool perform()
  502. {
  503. showCorrectTab();
  504. getComponent()->setSkewFactor (newValue);
  505. changed();
  506. return true;
  507. }
  508. bool undo()
  509. {
  510. showCorrectTab();
  511. getComponent()->setSkewFactor (oldValue);
  512. changed();
  513. return true;
  514. }
  515. double newValue, oldValue;
  516. };
  517. };
  518. //==============================================================================
  519. static String sliderStyleToString (Slider::SliderStyle style)
  520. {
  521. switch (style)
  522. {
  523. case Slider::LinearHorizontal: return "LinearHorizontal";
  524. case Slider::LinearVertical: return "LinearVertical";
  525. case Slider::LinearBar: return "LinearBar";
  526. case Slider::Rotary: return "Rotary";
  527. case Slider::RotaryHorizontalDrag: return "RotaryHorizontalDrag";
  528. case Slider::RotaryVerticalDrag: return "RotaryVerticalDrag";
  529. case Slider::RotaryHorizontalVerticalDrag: return "RotaryHorizontalVerticalDrag";
  530. case Slider::IncDecButtons: return "IncDecButtons";
  531. case Slider::TwoValueHorizontal: return "TwoValueHorizontal";
  532. case Slider::TwoValueVertical: return "TwoValueVertical";
  533. case Slider::ThreeValueHorizontal: return "ThreeValueHorizontal";
  534. case Slider::ThreeValueVertical: return "ThreeValueVertical";
  535. default: jassertfalse; break;
  536. }
  537. return String::empty;
  538. }
  539. static Slider::SliderStyle sliderStringToStyle (const String& s)
  540. {
  541. if (s == "LinearHorizontal") return Slider::LinearHorizontal;
  542. if (s == "LinearVertical") return Slider::LinearVertical;
  543. if (s == "LinearBar") return Slider::LinearBar;
  544. if (s == "Rotary") return Slider::Rotary;
  545. if (s == "RotaryHorizontalDrag") return Slider::RotaryHorizontalDrag;
  546. if (s == "RotaryVerticalDrag") return Slider::RotaryVerticalDrag;
  547. if (s == "RotaryHorizontalVerticalDrag") return Slider::RotaryHorizontalVerticalDrag;
  548. if (s == "IncDecButtons") return Slider::IncDecButtons;
  549. if (s.startsWithIgnoreCase ("TwoValueHoriz")) return Slider::TwoValueHorizontal;
  550. if (s.startsWithIgnoreCase ("TwoValueVert")) return Slider::TwoValueVertical;
  551. if (s.startsWithIgnoreCase ("ThreeValueHoriz")) return Slider::ThreeValueHorizontal;
  552. if (s.startsWithIgnoreCase ("ThreeValueVert")) return Slider::ThreeValueVertical;
  553. jassertfalse;
  554. return Slider::LinearHorizontal;
  555. }
  556. static String textBoxPosToString (const Slider::TextEntryBoxPosition pos)
  557. {
  558. switch (pos)
  559. {
  560. case Slider::NoTextBox: return "NoTextBox";
  561. case Slider::TextBoxLeft: return "TextBoxLeft";
  562. case Slider::TextBoxRight: return "TextBoxRight";
  563. case Slider::TextBoxAbove: return "TextBoxAbove";
  564. case Slider::TextBoxBelow: return "TextBoxBelow";
  565. default: jassertfalse; break;
  566. }
  567. return String::empty;
  568. }
  569. static Slider::TextEntryBoxPosition stringToTextBoxPos (const String& s)
  570. {
  571. if (s == "NoTextBox") return Slider::NoTextBox;
  572. if (s == "TextBoxLeft") return Slider::TextBoxLeft;
  573. if (s == "TextBoxRight") return Slider::TextBoxRight;
  574. if (s == "TextBoxAbove") return Slider::TextBoxAbove;
  575. if (s == "TextBoxBelow") return Slider::TextBoxBelow;
  576. jassertfalse;
  577. return Slider::TextBoxLeft;
  578. }
  579. };