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.

427 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceLibraryCode/JuceHeader.h"
  20. #include "VoicePurchases.h"
  21. //==============================================================================
  22. class InAppPurchaseApplication : public JUCEApplication,
  23. private AsyncUpdater
  24. {
  25. public:
  26. //==============================================================================
  27. InAppPurchaseApplication() : voicePurchases (*this) {}
  28. //==============================================================================
  29. const String getApplicationName() override { return ProjectInfo::projectName; }
  30. const String getApplicationVersion() override { return ProjectInfo::versionString; }
  31. bool moreThanOneInstanceAllowed() override { return false; }
  32. //==============================================================================
  33. void initialise (const String&) override
  34. {
  35. Desktop::getInstance().getDefaultLookAndFeel().setUsingNativeAlertWindows (true);
  36. dm.addAudioCallback (&player);
  37. dm.initialiseWithDefaultDevices (0, 2);
  38. mainWindow = new MainWindow;
  39. }
  40. void shutdown() override
  41. {
  42. mainWindow = nullptr;
  43. dm.closeAudioDevice();
  44. dm.removeAudioCallback (&player);
  45. }
  46. static InAppPurchaseApplication* getInstance()
  47. {
  48. return static_cast<InAppPurchaseApplication*> (JUCEApplication::getInstance());
  49. }
  50. //==============================================================================
  51. SoundPlayer& getPlayer()
  52. {
  53. return player;
  54. }
  55. VoicePurchases& getPurchases()
  56. {
  57. return voicePurchases;
  58. }
  59. //==============================================================================
  60. class MainContentComponent : public Component, private Button::Listener
  61. {
  62. public:
  63. MainContentComponent()
  64. {
  65. setOpaque (true);
  66. phraseListBox.setRowHeight (33);
  67. phraseListBox.selectRow (0);
  68. phraseListBox.updateContent();
  69. voiceListBox.setRowHeight (66);
  70. voiceListBox.selectRow (0);
  71. voiceListBox.updateContent();
  72. voiceListBox.getViewport()->setScrollOnDragEnabled (true);
  73. addAndMakeVisible (phraseLabel);
  74. addAndMakeVisible (phraseListBox);
  75. addAndMakeVisible (playStopButton);
  76. addAndMakeVisible (voiceLabel);
  77. addAndMakeVisible (voiceListBox);
  78. playStopButton.addListener (this);
  79. #if JUCE_ANDROID || JUCE_IOS
  80. auto screenBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  81. setSize (screenBounds.getWidth(), screenBounds.getHeight());
  82. #else
  83. setSize (800, 600);
  84. #endif
  85. }
  86. void updateDisplay()
  87. {
  88. voiceListBox.updateContent();
  89. voiceListBox.repaint();
  90. }
  91. private:
  92. //==============================================================================
  93. class PhraseModel : public ListBoxModel
  94. {
  95. public:
  96. PhraseModel() {}
  97. int getNumRows() override { return phrases.size(); }
  98. void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
  99. {
  100. Rectangle<int> r (0, 0, w, h);
  101. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  102. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  103. g.fillRect (r);
  104. g.setColour (lf.findColour (ListBox::textColourId));
  105. g.setFont (18);
  106. String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
  107. g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
  108. }
  109. private:
  110. static StringArray phrases;
  111. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
  112. };
  113. //==============================================================================
  114. class VoiceModel : public ListBoxModel
  115. {
  116. public:
  117. //==============================================================================
  118. class VoiceRow : public Component, private Button::Listener, private Timer
  119. {
  120. public:
  121. VoiceRow () : voices (getInstance()->getPurchases().getVoiceNames())
  122. {
  123. addAndMakeVisible (nameLabel);
  124. addAndMakeVisible (purchaseButton);
  125. addAndMakeVisible (priceLabel);
  126. purchaseButton.addListener (this);
  127. setSize (600, 33);
  128. }
  129. void paint (Graphics& g) override
  130. {
  131. auto r = getLocalBounds().reduced (4);
  132. {
  133. auto voiceIconBounds = r.removeFromLeft (r.getHeight());
  134. g.setColour (Colours::black);
  135. g.drawRect (voiceIconBounds);
  136. voiceIconBounds.reduce (1, 1);
  137. g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
  138. g.fillRect (voiceIconBounds);
  139. g.drawImage (avatar, voiceIconBounds.toFloat());
  140. if (! hasBeenPurchased)
  141. {
  142. g.setColour (Colours::white.withAlpha (0.8f));
  143. g.fillRect (voiceIconBounds);
  144. if (purchaseInProgress)
  145. getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
  146. voiceIconBounds.getX(), voiceIconBounds.getY(),
  147. voiceIconBounds.getWidth(), voiceIconBounds.getHeight());
  148. }
  149. }
  150. }
  151. void resized() override
  152. {
  153. auto r = getLocalBounds().reduced (4 + 8, 4);
  154. auto h = r.getHeight();
  155. auto w = static_cast<int> (h * 1.5);
  156. r.removeFromLeft (h);
  157. purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
  158. nameLabel.setBounds (r.removeFromTop (18));
  159. priceLabel.setBounds (r.removeFromTop (18));
  160. }
  161. void update (int rowNumber, bool rowIsSelected)
  162. {
  163. isSelected = rowIsSelected;
  164. rowSelected = rowNumber;
  165. if (isPositiveAndBelow (rowNumber, voices.size()))
  166. {
  167. auto imageResourceName = voices[rowNumber] + "_png";
  168. nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
  169. auto purchase = getInstance()->getPurchases().getPurchase (rowNumber);
  170. hasBeenPurchased = purchase.isPurchased;
  171. purchaseInProgress = purchase.purchaseInProgress;
  172. if (purchaseInProgress)
  173. startTimer (1000 / 50);
  174. else
  175. stopTimer();
  176. nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
  177. nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  178. priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
  179. priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  180. priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
  181. if (rowNumber == 0)
  182. {
  183. purchaseButton.setButtonText ("Internal");
  184. purchaseButton.setEnabled (false);
  185. }
  186. else
  187. {
  188. purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
  189. purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
  190. }
  191. setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
  192. int rawSize;
  193. if (auto* rawData = BinaryData::getNamedResource (imageResourceName.toRawUTF8(), rawSize))
  194. {
  195. MemoryInputStream imageData (rawData, static_cast<size_t> (rawSize), false);
  196. avatar = PNGImageFormat().decodeImage (imageData);
  197. }
  198. }
  199. }
  200. private:
  201. //==============================================================================
  202. void buttonClicked (Button*) override
  203. {
  204. if (rowSelected >= 0)
  205. {
  206. if (! hasBeenPurchased)
  207. {
  208. getInstance()->getPurchases().purchaseVoice (rowSelected);
  209. purchaseInProgress = true;
  210. startTimer (1000 / 50);
  211. }
  212. }
  213. }
  214. void timerCallback() override { repaint(); }
  215. //==============================================================================
  216. bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
  217. int rowSelected = -1;
  218. Image avatar;
  219. StringArray voices;
  220. Label nameLabel, priceLabel;
  221. TextButton purchaseButton {"Purchase"};
  222. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
  223. };
  224. //==============================================================================
  225. VoiceModel() : voiceProducts (getInstance()->getPurchases().getVoiceNames()) {}
  226. int getNumRows() override { return voiceProducts.size(); }
  227. Component* refreshComponentForRow (int row, bool selected, Component* existing) override
  228. {
  229. if (isPositiveAndBelow (row, voiceProducts.size()))
  230. {
  231. if (existing == nullptr)
  232. existing = new VoiceRow;
  233. if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
  234. voiceRow->update (row, selected);
  235. return existing;
  236. }
  237. return nullptr;
  238. }
  239. void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
  240. {
  241. auto r = Rectangle<int> (0, 0, w, h).reduced (4);
  242. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  243. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  244. g.fillRect (r);
  245. }
  246. private:
  247. StringArray voiceProducts;
  248. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
  249. };
  250. //==============================================================================
  251. void resized() override
  252. {
  253. auto r = getLocalBounds().reduced (20);
  254. {
  255. auto phraseArea = r.removeFromTop (r.getHeight() / 2);
  256. phraseLabel.setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
  257. playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
  258. phraseListBox.setBounds (phraseArea);
  259. }
  260. {
  261. auto voiceArea = r;
  262. voiceLabel.setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
  263. voiceListBox.setBounds (voiceArea);
  264. }
  265. }
  266. void paint (Graphics& g) override
  267. {
  268. g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
  269. .findColour (ResizableWindow::backgroundColourId));
  270. }
  271. //==============================================================================
  272. void buttonClicked (Button*) override
  273. {
  274. MemoryOutputStream resourceName;
  275. auto idx = voiceListBox.getSelectedRow();
  276. if (isPositiveAndBelow (idx, soundNames.size()))
  277. {
  278. resourceName << soundNames[idx] << phraseListBox.getSelectedRow() << "_ogg";
  279. int numBytes;
  280. if (auto* data = BinaryData::getNamedResource (resourceName.toString().toRawUTF8(), numBytes))
  281. getInstance()->getPlayer().play (data, static_cast<size_t> (numBytes));
  282. }
  283. }
  284. //==============================================================================
  285. StringArray soundNames = getInstance()->getPurchases().getVoiceNames();
  286. PhraseModel phraseModel;
  287. Label phraseLabel {"phraseLabel", NEEDS_TRANS ("Phrases:")};
  288. ListBox phraseListBox { "phraseListBox", &phraseModel };
  289. TextButton playStopButton {"Play"};
  290. VoiceModel voiceModel;
  291. Label voiceLabel {"voiceLabel", NEEDS_TRANS ("Voices:")};
  292. ListBox voiceListBox { "voiceListBox", &voiceModel };
  293. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
  294. };
  295. //==============================================================================
  296. class MainWindow : public DocumentWindow
  297. {
  298. public:
  299. MainWindow () : DocumentWindow (ProjectInfo::projectName,
  300. Desktop::getInstance().getDefaultLookAndFeel()
  301. .findColour (ResizableWindow::backgroundColourId),
  302. DocumentWindow::allButtons)
  303. {
  304. setUsingNativeTitleBar (true);
  305. setContentOwned (new MainContentComponent(), true);
  306. #if JUCE_ANDROID || JUCE_IOS
  307. setFullScreen (true);
  308. #else
  309. centreWithSize (getWidth(), getHeight());
  310. #endif
  311. setVisible (true);
  312. }
  313. void closeButtonPressed() override
  314. {
  315. JUCEApplication::getInstance()->systemRequestedQuit();
  316. }
  317. private:
  318. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
  319. };
  320. private:
  321. //==============================================================================
  322. void handleAsyncUpdate() override
  323. {
  324. if (auto* contentComponent = dynamic_cast<MainContentComponent*> (mainWindow->getContentComponent()))
  325. contentComponent->updateDisplay();
  326. }
  327. //==============================================================================
  328. VoicePurchases voicePurchases;
  329. AudioDeviceManager dm;
  330. SoundPlayer player;
  331. ScopedPointer<MainWindow> mainWindow;
  332. ScopedPointer<AlertWindow> alertWindow;
  333. };
  334. StringArray InAppPurchaseApplication::MainContentComponent::PhraseModel::phrases {{"I love JUCE!", "The five dimensions of touch", "Make it fast!"}};
  335. //==============================================================================
  336. // This macro generates the main() routine that launches the app.
  337. START_JUCE_APPLICATION (InAppPurchaseApplication)