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.

770 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. #include "../jucer_Headers.h"
  19. //==============================================================================
  20. int64 calculateStreamHashCode (InputStream& in)
  21. {
  22. int64 t = 0;
  23. const int bufferSize = 4096;
  24. HeapBlock <uint8> buffer;
  25. buffer.malloc (bufferSize);
  26. for (;;)
  27. {
  28. const int num = in.read (buffer, bufferSize);
  29. if (num <= 0)
  30. break;
  31. for (int i = 0; i < num; ++i)
  32. t = t * 65599 + buffer[i];
  33. }
  34. return t;
  35. }
  36. int64 calculateFileHashCode (const File& file)
  37. {
  38. ScopedPointer <FileInputStream> stream (file.createInputStream());
  39. return stream != 0 ? calculateStreamHashCode (*stream) : 0;
  40. }
  41. bool areFilesIdentical (const File& file1, const File& file2)
  42. {
  43. return file1.getSize() == file2.getSize()
  44. && calculateFileHashCode (file1) == calculateFileHashCode (file2);
  45. }
  46. bool overwriteFileWithNewDataIfDifferent (const File& file, const char* data, int numBytes)
  47. {
  48. if (file.getSize() == numBytes)
  49. {
  50. MemoryInputStream newStream (data, numBytes, false);
  51. if (calculateStreamHashCode (newStream) == calculateFileHashCode (file))
  52. return true;
  53. }
  54. TemporaryFile temp (file);
  55. return temp.getFile().appendData (data, numBytes)
  56. && temp.overwriteTargetFileWithTemporary();
  57. }
  58. bool overwriteFileWithNewDataIfDifferent (const File& file, const MemoryOutputStream& newData)
  59. {
  60. return overwriteFileWithNewDataIfDifferent (file, newData.getData(), newData.getDataSize());
  61. }
  62. bool overwriteFileWithNewDataIfDifferent (const File& file, const String& newData)
  63. {
  64. return overwriteFileWithNewDataIfDifferent (file, newData.toUTF8(), strlen ((const char*) newData.toUTF8()));
  65. }
  66. bool containsAnyNonHiddenFiles (const File& folder)
  67. {
  68. DirectoryIterator di (folder, false);
  69. while (di.next())
  70. if (! di.getFile().isHidden())
  71. return true;
  72. return false;
  73. }
  74. //==============================================================================
  75. const int64 hashCode64 (const String& s)
  76. {
  77. return s.hashCode64() + s.length() * s.hashCode() + s.toUpperCase().hashCode();
  78. }
  79. const String createAlphaNumericUID()
  80. {
  81. String uid;
  82. static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  83. Random r (Random::getSystemRandom().nextInt64());
  84. for (int i = 9; --i >= 0;)
  85. {
  86. r.setSeedRandomly();
  87. uid << (juce_wchar) chars [r.nextInt (sizeof (chars))];
  88. }
  89. return uid;
  90. }
  91. const String randomHexString (Random& random, int numChars)
  92. {
  93. String s;
  94. const char hexChars[] = "0123456789ABCDEF";
  95. while (--numChars >= 0)
  96. s << hexChars [random.nextInt (16)];
  97. return s;
  98. }
  99. const String hexString8Digits (int value)
  100. {
  101. return String::toHexString (value).paddedLeft ('0', 8);
  102. }
  103. const String createGUID (const String& seed)
  104. {
  105. String guid;
  106. Random r (hashCode64 (seed + "_jucersalt"));
  107. guid << "{" << randomHexString (r, 8); // (written as separate statements to enforce the order of execution)
  108. guid << "-" << randomHexString (r, 4);
  109. guid << "-" << randomHexString (r, 4);
  110. guid << "-" << randomHexString (r, 4);
  111. guid << "-" << randomHexString (r, 12) << "}";
  112. return guid;
  113. }
  114. const String unixStylePath (const String& path)
  115. {
  116. return path.replaceCharacter ('\\', '/');
  117. }
  118. const String windowsStylePath (const String& path)
  119. {
  120. return path.replaceCharacter ('/', '\\');
  121. }
  122. const String appendPath (const String& path, const String& subpath)
  123. {
  124. if (File::isAbsolutePath (subpath)
  125. || subpath.startsWithChar ('$')
  126. || subpath.startsWithChar ('~')
  127. || (CharacterFunctions::isLetter (subpath[0]) && subpath[1] == ':'))
  128. return subpath.replaceCharacter ('\\', '/');
  129. String path1 (path.replaceCharacter ('\\', '/'));
  130. if (! path1.endsWithChar ('/'))
  131. path1 << '/';
  132. return path1 + subpath.replaceCharacter ('\\', '/');
  133. }
  134. bool shouldPathsBeRelative (String path1, String path2)
  135. {
  136. path1 = unixStylePath (path1);
  137. path2 = unixStylePath (path2);
  138. const int len = jmin (path1.length(), path2.length());
  139. int commonBitLength = 0;
  140. for (int i = 0; i < len; ++i)
  141. {
  142. if (CharacterFunctions::toLowerCase (path1[i]) != CharacterFunctions::toLowerCase (path2[i]))
  143. break;
  144. ++commonBitLength;
  145. }
  146. return path1.substring (0, commonBitLength).removeCharacters ("/:").isNotEmpty();
  147. }
  148. const String createIncludeStatement (const File& includeFile, const File& targetFile)
  149. {
  150. return "#include \"" + unixStylePath (includeFile.getRelativePathFrom (targetFile.getParentDirectory()))
  151. + "\"";
  152. }
  153. const String makeHeaderGuardName (const File& file)
  154. {
  155. return "__" + file.getFileName().toUpperCase()
  156. .replaceCharacters (" .", "__")
  157. .retainCharacters ("_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  158. + "_"
  159. + String::toHexString (file.hashCode()).toUpperCase()
  160. + "__";
  161. }
  162. //==============================================================================
  163. bool isJuceFolder (const File& folder)
  164. {
  165. return folder.getFileName().containsIgnoreCase ("juce")
  166. && folder.getChildFile ("juce.h").exists()
  167. && folder.getChildFile ("juce_Config.h").exists();
  168. }
  169. static const File lookInFolderForJuceFolder (const File& folder)
  170. {
  171. for (DirectoryIterator di (folder, false, "*juce*", File::findDirectories); di.next();)
  172. {
  173. if (isJuceFolder (di.getFile()))
  174. return di.getFile();
  175. }
  176. return File::nonexistent;
  177. }
  178. const File findParentJuceFolder (const File& file)
  179. {
  180. File f (file);
  181. while (f.exists() && f.getParentDirectory() != f)
  182. {
  183. if (isJuceFolder (f))
  184. return f;
  185. File found = lookInFolderForJuceFolder (f);
  186. if (found.exists())
  187. return found;
  188. f = f.getParentDirectory();
  189. }
  190. return File::nonexistent;
  191. }
  192. const File findDefaultJuceFolder()
  193. {
  194. File f = findParentJuceFolder (File::getSpecialLocation (File::currentApplicationFile));
  195. if (! f.exists())
  196. f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userHomeDirectory));
  197. if (! f.exists())
  198. f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userDocumentsDirectory));
  199. return f;
  200. }
  201. //==============================================================================
  202. const String replaceCEscapeChars (const String& s)
  203. {
  204. const int len = s.length();
  205. String r;
  206. r.preallocateStorage (len + 2);
  207. bool lastWasHexEscapeCode = false;
  208. for (int i = 0; i < len; ++i)
  209. {
  210. const tchar c = s[i];
  211. switch (c)
  212. {
  213. case '\t': r << "\\t"; lastWasHexEscapeCode = false; break;
  214. case '\r': r << "\\r"; lastWasHexEscapeCode = false; break;
  215. case '\n': r << "\\n"; lastWasHexEscapeCode = false; break;
  216. case '\\': r << "\\\\"; lastWasHexEscapeCode = false; break;
  217. case '\'': r << "\\\'"; lastWasHexEscapeCode = false; break;
  218. case '\"': r << "\\\""; lastWasHexEscapeCode = false; break;
  219. default:
  220. if (c < 128
  221. && ! (lastWasHexEscapeCode
  222. && String ("0123456789abcdefABCDEF").containsChar (c))) // (have to avoid following a hex escape sequence with a valid hex digit)
  223. {
  224. r << c;
  225. lastWasHexEscapeCode = false;
  226. }
  227. else
  228. {
  229. r << "\\x" << String::toHexString ((int) c);
  230. lastWasHexEscapeCode = true;
  231. }
  232. break;
  233. }
  234. }
  235. return r;
  236. }
  237. //==============================================================================
  238. const String makeValidCppIdentifier (String s,
  239. const bool capitalise,
  240. const bool removeColons,
  241. const bool allowTemplates)
  242. {
  243. if (s.isEmpty())
  244. return "unknown";
  245. if (removeColons)
  246. s = s.replaceCharacters (".,;:/@", "______");
  247. else
  248. s = s.replaceCharacters (".,;/@", "_____");
  249. int i;
  250. for (i = s.length(); --i > 0;)
  251. if (CharacterFunctions::isLetter (s[i])
  252. && CharacterFunctions::isLetter (s[i - 1])
  253. && CharacterFunctions::isUpperCase (s[i])
  254. && ! CharacterFunctions::isUpperCase (s[i - 1]))
  255. s = s.substring (0, i) + " " + s.substring (i);
  256. String allowedChars ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_ 0123456789");
  257. if (allowTemplates)
  258. allowedChars += "<>";
  259. if (! removeColons)
  260. allowedChars += ":";
  261. StringArray words;
  262. words.addTokens (s.retainCharacters (allowedChars), false);
  263. words.trim();
  264. String n (words[0]);
  265. if (capitalise)
  266. n = n.toLowerCase();
  267. for (i = 1; i < words.size(); ++i)
  268. {
  269. if (capitalise && words[i].length() > 1)
  270. n << words[i].substring (0, 1).toUpperCase()
  271. << words[i].substring (1).toLowerCase();
  272. else
  273. n << words[i];
  274. }
  275. if (CharacterFunctions::isDigit (n[0]))
  276. n = "_" + n;
  277. if (CPlusPlusCodeTokeniser::isReservedKeyword (n))
  278. n << '_';
  279. return n;
  280. }
  281. void autoScrollForMouseEvent (const MouseEvent& e)
  282. {
  283. Viewport* const viewport = e.eventComponent->findParentComponentOfClass ((Viewport*) 0);
  284. if (viewport != 0)
  285. {
  286. const MouseEvent e2 (e.getEventRelativeTo (viewport));
  287. viewport->autoScroll (e2.x, e2.y, 8, 16);
  288. }
  289. }
  290. void drawComponentPlaceholder (Graphics& g, int w, int h, const String& text)
  291. {
  292. g.fillAll (Colours::white.withAlpha (0.4f));
  293. g.setColour (Colours::grey);
  294. g.drawRect (0, 0, w, h);
  295. g.drawLine (0.5f, 0.5f, w - 0.5f, h - 0.5f);
  296. g.drawLine (0.5f, h - 0.5f, w - 0.5f, 0.5f);
  297. g.setColour (Colours::black);
  298. g.setFont (11.0f);
  299. g.drawFittedText (text, 2, 2, w - 4, h - 4, Justification::centredTop, 2);
  300. }
  301. //==============================================================================
  302. const String floatToCode (const float v)
  303. {
  304. String s ((double) (float) v, 4);
  305. if (s.containsChar ('.'))
  306. s << 'f';
  307. else
  308. s << ".0f";
  309. return s;
  310. }
  311. const String doubleToCode (const double v)
  312. {
  313. String s (v, 7);
  314. if (! s.containsChar ('.'))
  315. s << ".0";
  316. return s;
  317. }
  318. const String boolToCode (const bool b)
  319. {
  320. return b ? "true" : "false";
  321. }
  322. const String colourToCode (const Colour& col)
  323. {
  324. const Colour colours[] =
  325. {
  326. #define COL(col) Colours::col,
  327. #include "jucer_Colours.h"
  328. #undef COL
  329. Colours::transparentBlack
  330. };
  331. static const char* colourNames[] =
  332. {
  333. #define COL(col) #col,
  334. #include "jucer_Colours.h"
  335. #undef COL
  336. 0
  337. };
  338. for (int i = 0; i < numElementsInArray (colourNames) - 1; ++i)
  339. if (col == colours[i])
  340. return "Colours::" + String (colourNames[i]);
  341. return "Colour (0x" + hexString8Digits ((int) col.getARGB()) + ')';
  342. }
  343. const String justificationToCode (const Justification& justification)
  344. {
  345. switch (justification.getFlags())
  346. {
  347. case Justification::centred: return "Justification::centred";
  348. case Justification::centredLeft: return "Justification::centredLeft";
  349. case Justification::centredRight: return "Justification::centredRight";
  350. case Justification::centredTop: return "Justification::centredTop";
  351. case Justification::centredBottom: return "Justification::centredBottom";
  352. case Justification::topLeft: return "Justification::topLeft";
  353. case Justification::topRight: return "Justification::topRight";
  354. case Justification::bottomLeft: return "Justification::bottomLeft";
  355. case Justification::bottomRight: return "Justification::bottomRight";
  356. case Justification::left: return "Justification::left";
  357. case Justification::right: return "Justification::right";
  358. case Justification::horizontallyCentred: return "Justification::horizontallyCentred";
  359. case Justification::top: return "Justification::top";
  360. case Justification::bottom: return "Justification::bottom";
  361. case Justification::verticallyCentred: return "Justification::verticallyCentred";
  362. case Justification::horizontallyJustified: return "Justification::horizontallyJustified";
  363. default: jassertfalse; break;
  364. }
  365. return "Justification (" + String (justification.getFlags()) + ")";
  366. }
  367. const String castToFloat (const String& expression)
  368. {
  369. if (expression.containsOnly ("0123456789.f"))
  370. {
  371. String s (expression.getFloatValue());
  372. if (s.containsChar (T('.')))
  373. return s + "f";
  374. return s + ".0f";
  375. }
  376. return "(float) (" + expression + ")";
  377. }
  378. const String indentCode (const String& code, const int numSpaces)
  379. {
  380. if (numSpaces == 0)
  381. return code;
  382. const String space (String::repeatedString (" ", numSpaces));
  383. StringArray lines;
  384. lines.addLines (code);
  385. for (int i = 1; i < lines.size(); ++i)
  386. {
  387. String s (lines[i].trimEnd());
  388. if (s.isNotEmpty())
  389. s = space + s;
  390. lines.set (i, s);
  391. }
  392. return lines.joinIntoString (newLine);
  393. }
  394. int indexOfLineStartingWith (const StringArray& lines, const String& text, int startIndex)
  395. {
  396. startIndex = jmax (0, startIndex);
  397. while (startIndex < lines.size())
  398. {
  399. if (lines[startIndex].trimStart().startsWithIgnoreCase (text))
  400. return startIndex;
  401. ++startIndex;
  402. }
  403. return -1;
  404. }
  405. //==============================================================================
  406. PropertyPanelWithTooltips::PropertyPanelWithTooltips()
  407. : lastComp (0)
  408. {
  409. addAndMakeVisible (panel = new PropertyPanel());
  410. startTimer (150);
  411. }
  412. PropertyPanelWithTooltips::~PropertyPanelWithTooltips()
  413. {
  414. deleteAllChildren();
  415. }
  416. void PropertyPanelWithTooltips::paint (Graphics& g)
  417. {
  418. g.setColour (Colour::greyLevel (0.15f));
  419. g.setFont (13.0f);
  420. TextLayout tl;
  421. tl.appendText (lastTip, Font (14.0f));
  422. tl.layout (getWidth() - 10, Justification::left, true); // try to make it look nice
  423. if (tl.getNumLines() > 3)
  424. tl.layout (getWidth() - 10, Justification::left, false); // too big, so just squash it in..
  425. tl.drawWithin (g, 5, panel->getBottom() + 2, getWidth() - 10,
  426. getHeight() - panel->getBottom() - 4,
  427. Justification::centredLeft);
  428. }
  429. void PropertyPanelWithTooltips::resized()
  430. {
  431. panel->setBounds (0, 0, getWidth(), jmax (getHeight() - 60, proportionOfHeight (0.6f)));
  432. }
  433. void PropertyPanelWithTooltips::timerCallback()
  434. {
  435. Component* const newComp = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  436. if (newComp != lastComp)
  437. {
  438. lastComp = newComp;
  439. String newTip (findTip (newComp));
  440. if (newTip != lastTip)
  441. {
  442. lastTip = newTip;
  443. repaint (0, panel->getBottom(), getWidth(), getHeight());
  444. }
  445. }
  446. }
  447. const String PropertyPanelWithTooltips::findTip (Component* c)
  448. {
  449. while (c != 0 && c != this)
  450. {
  451. TooltipClient* const tc = dynamic_cast <TooltipClient*> (c);
  452. if (tc != 0)
  453. {
  454. const String tip (tc->getTooltip());
  455. if (tip.isNotEmpty())
  456. return tip;
  457. }
  458. c = c->getParentComponent();
  459. }
  460. return String::empty;
  461. }
  462. //==============================================================================
  463. FloatingLabelComponent::FloatingLabelComponent()
  464. : font (10.0f)
  465. {
  466. setInterceptsMouseClicks (false ,false);
  467. }
  468. void FloatingLabelComponent::remove()
  469. {
  470. if (getParentComponent() != 0)
  471. getParentComponent()->removeChildComponent (this);
  472. }
  473. void FloatingLabelComponent::update (Component* parent, const String& text, const Colour& textColour, int x, int y, bool toRight, bool below)
  474. {
  475. colour = textColour;
  476. Rectangle<int> r;
  477. if (text != getName())
  478. {
  479. setName (text);
  480. glyphs.clear();
  481. glyphs.addJustifiedText (font, text, 0, 0, 200.0f, Justification::left);
  482. glyphs.justifyGlyphs (0, std::numeric_limits<int>::max(), 0, 0, 1000, 1000, Justification::topLeft);
  483. r = glyphs.getBoundingBox (0, std::numeric_limits<int>::max(), false)
  484. .getSmallestIntegerContainer().expanded (2, 2);
  485. }
  486. else
  487. {
  488. r = getLocalBounds();
  489. }
  490. r.setPosition (x + (toRight ? 3 : -(r.getWidth() + 3)), y + (below ? 2 : -(r.getHeight() + 2)));
  491. setBounds (r);
  492. parent->addAndMakeVisible (this);
  493. }
  494. void FloatingLabelComponent::paint (Graphics& g)
  495. {
  496. g.setFont (font);
  497. g.setColour (Colours::white);
  498. for (int y = -1; y <= 1; ++y)
  499. for (int x = -1; x <= 1; ++x)
  500. glyphs.draw (g, AffineTransform::translation (1.0f + x, 1.0f + y));
  501. g.setColour (colour);
  502. glyphs.draw (g, AffineTransform::translation (1.0f, 1.0f));
  503. }
  504. //==============================================================================
  505. class FontNameValueSource : public Value::ValueSource,
  506. public Value::Listener
  507. {
  508. public:
  509. FontNameValueSource (const Value& source)
  510. : sourceValue (source)
  511. {
  512. sourceValue.addListener (this);
  513. }
  514. ~FontNameValueSource() {}
  515. void valueChanged (Value&) { sendChangeMessage (true); }
  516. const var getValue() const
  517. {
  518. const String fontName (sourceValue.toString());
  519. const int index = StoredSettings::getInstance()->getFontNames().indexOf (fontName);
  520. if (index >= 0) return 5 + index;
  521. else if (fontName == getDefaultFontName()) return 1;
  522. else if (fontName == getDefaultSansFontName()) return 2;
  523. else if (fontName == getDefaultSerifFontName()) return 3;
  524. else if (fontName == getDefaultMonospacedFontName()) return 4;
  525. return 1;
  526. }
  527. void setValue (const var& newValue)
  528. {
  529. const int index = newValue;
  530. if (index <= 1) sourceValue = getDefaultFontName();
  531. else if (index == 2) sourceValue = getDefaultSansFontName();
  532. else if (index == 3) sourceValue = getDefaultSerifFontName();
  533. else if (index == 4) sourceValue = getDefaultMonospacedFontName();
  534. else sourceValue = StoredSettings::getInstance()->getFontNames() [index - 5];
  535. }
  536. static ChoicePropertyComponent* createProperty (const String& title, const Value& value)
  537. {
  538. StringArray fontNames;
  539. fontNames.add (getDefaultFontName());
  540. fontNames.add (getDefaultSansFontName());
  541. fontNames.add (getDefaultSerifFontName());
  542. fontNames.add (getDefaultMonospacedFontName());
  543. fontNames.add (String::empty);
  544. fontNames.addArray (StoredSettings::getInstance()->getFontNames());
  545. return new ChoicePropertyComponent (Value (new FontNameValueSource (value)), title, fontNames);
  546. }
  547. static void applyToFont (Font& font, const String& fontName)
  548. {
  549. if (fontName.isEmpty() || fontName == getDefaultFontName() || fontName == getDefaultSansFontName())
  550. return;
  551. font.setTypefaceName (fontName == getDefaultSerifFontName() ? Font::getDefaultSerifFontName()
  552. : (fontName == getDefaultMonospacedFontName() ? Font::getDefaultMonospacedFontName()
  553. : fontName));
  554. }
  555. static const char* getDefaultFontName() throw() { return "Default Font"; }
  556. static const char* getDefaultSansFontName() throw() { return "Default Sans-Serif Font"; }
  557. static const char* getDefaultSerifFontName() throw() { return "Default Serif Font"; }
  558. static const char* getDefaultMonospacedFontName() throw() { return "Default Monospaced Font"; }
  559. private:
  560. Value sourceValue;
  561. FontNameValueSource (const FontNameValueSource&);
  562. const FontNameValueSource& operator= (const FontNameValueSource&);
  563. };
  564. static const char* const fontStyles[] = { "Normal", "Bold", "Italic", "Bold + Italic", 0 };
  565. const Font getFontFromState (const ValueTree& state, const var::identifier& fontName, const var::identifier& fontSize, const var::identifier& fontStyle)
  566. {
  567. const String styleString (state.getProperty (fontStyle).toString());
  568. const int fontFlags = styleString == fontStyles[1] ? Font::bold
  569. : (styleString == fontStyles[2] ? Font::italic
  570. : (styleString == fontStyles[3] ? (Font::italic | Font::bold)
  571. : 0));
  572. Font f (state.getProperty (fontSize, 14), fontFlags);
  573. FontNameValueSource::applyToFont (f, state.getProperty (fontName));
  574. return f;
  575. }
  576. void createFontProperties (Array <PropertyComponent*>& props, const ValueTree& state,
  577. const var::identifier& fontName, const var::identifier& fontSize, const var::identifier& fontStyle,
  578. UndoManager* undoManager)
  579. {
  580. props.add (FontNameValueSource::createProperty ("Font", state.getPropertyAsValue (fontName, undoManager)));
  581. props.add (new SliderPropertyComponent (state.getPropertyAsValue (fontSize, undoManager), "Font Size", 1.0, 150.0, 0.1, 0.5));
  582. props.add (StringListValueSource::create ("Font Style", state.getPropertyAsValue (fontStyle, undoManager), StringArray (fontStyles)));
  583. }
  584. PropertyComponent* createJustificationProperty (const String& name, const Value& value, bool onlyHorizontal)
  585. {
  586. ValueRemapperSource* remapper = new ValueRemapperSource (value);
  587. StringArray strings;
  588. if (onlyHorizontal)
  589. {
  590. const char* const layouts[] = { "Left", "Centred", "Right", 0 };
  591. const int justifications[] = { Justification::left, Justification::centred, Justification::right, 0 };
  592. for (int i = 0; i < numElementsInArray (justifications) - 1; ++i)
  593. remapper->addMapping (justifications[i], i + 1);
  594. strings = StringArray (layouts);
  595. }
  596. else
  597. {
  598. const char* const layouts[] = { "Centred", "Centred-left", "Centred-right", "Centred-top", "Centred-bottom", "Top-left",
  599. "Top-right", "Bottom-left", "Bottom-right", 0 };
  600. const int justifications[] = { Justification::centred, Justification::centredLeft, Justification::centredRight,
  601. Justification::centredTop, Justification::centredBottom, Justification::topLeft,
  602. Justification::topRight, Justification::bottomLeft, Justification::bottomRight, 0 };
  603. for (int i = 0; i < numElementsInArray (justifications) - 1; ++i)
  604. remapper->addMapping (justifications[i], i + 1);
  605. strings = StringArray (layouts);
  606. }
  607. return new ChoicePropertyComponent (Value (remapper), name, strings);
  608. }