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.

436 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. Timer::callAfterDelay(1000, [] ()
  40. {
  41. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  42. "Your credit card will be charged!",
  43. "You are running the sample code for JUCE In-App purchases."
  44. "Although this is only sample code, it will still CHARGE YOUR CREDIT CARD!",
  45. "Understood!");
  46. });
  47. }
  48. void shutdown() override
  49. {
  50. mainWindow = nullptr;
  51. dm.closeAudioDevice();
  52. dm.removeAudioCallback (&player);
  53. }
  54. static InAppPurchaseApplication* getInstance()
  55. {
  56. return static_cast<InAppPurchaseApplication*> (JUCEApplication::getInstance());
  57. }
  58. //==============================================================================
  59. SoundPlayer& getPlayer()
  60. {
  61. return player;
  62. }
  63. VoicePurchases& getPurchases()
  64. {
  65. return voicePurchases;
  66. }
  67. //==============================================================================
  68. class MainContentComponent : public Component, private Button::Listener
  69. {
  70. public:
  71. MainContentComponent()
  72. {
  73. setOpaque (true);
  74. phraseListBox.setRowHeight (33);
  75. phraseListBox.selectRow (0);
  76. phraseListBox.updateContent();
  77. voiceListBox.setRowHeight (66);
  78. voiceListBox.selectRow (0);
  79. voiceListBox.updateContent();
  80. voiceListBox.getViewport()->setScrollOnDragEnabled (true);
  81. addAndMakeVisible (phraseLabel);
  82. addAndMakeVisible (phraseListBox);
  83. addAndMakeVisible (playStopButton);
  84. addAndMakeVisible (voiceLabel);
  85. addAndMakeVisible (voiceListBox);
  86. playStopButton.addListener (this);
  87. #if JUCE_ANDROID || JUCE_IOS
  88. auto screenBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  89. setSize (screenBounds.getWidth(), screenBounds.getHeight());
  90. #else
  91. setSize (800, 600);
  92. #endif
  93. }
  94. void updateDisplay()
  95. {
  96. voiceListBox.updateContent();
  97. voiceListBox.repaint();
  98. }
  99. private:
  100. //==============================================================================
  101. class PhraseModel : public ListBoxModel
  102. {
  103. public:
  104. PhraseModel() {}
  105. int getNumRows() override { return phrases.size(); }
  106. void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
  107. {
  108. Rectangle<int> r (0, 0, w, h);
  109. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  110. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  111. g.fillRect (r);
  112. g.setColour (lf.findColour (ListBox::textColourId));
  113. g.setFont (18);
  114. String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
  115. g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
  116. }
  117. private:
  118. static StringArray phrases;
  119. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
  120. };
  121. //==============================================================================
  122. class VoiceModel : public ListBoxModel
  123. {
  124. public:
  125. //==============================================================================
  126. class VoiceRow : public Component, private Button::Listener, private Timer
  127. {
  128. public:
  129. VoiceRow () : voices (getInstance()->getPurchases().getVoiceNames())
  130. {
  131. addAndMakeVisible (nameLabel);
  132. addAndMakeVisible (purchaseButton);
  133. addAndMakeVisible (priceLabel);
  134. purchaseButton.addListener (this);
  135. setSize (600, 33);
  136. }
  137. void paint (Graphics& g) override
  138. {
  139. auto r = getLocalBounds().reduced (4);
  140. {
  141. auto voiceIconBounds = r.removeFromLeft (r.getHeight());
  142. g.setColour (Colours::black);
  143. g.drawRect (voiceIconBounds);
  144. voiceIconBounds.reduce (1, 1);
  145. g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
  146. g.fillRect (voiceIconBounds);
  147. g.drawImage (avatar, voiceIconBounds.toFloat());
  148. if (! hasBeenPurchased)
  149. {
  150. g.setColour (Colours::white.withAlpha (0.8f));
  151. g.fillRect (voiceIconBounds);
  152. if (purchaseInProgress)
  153. getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
  154. voiceIconBounds.getX(), voiceIconBounds.getY(),
  155. voiceIconBounds.getWidth(), voiceIconBounds.getHeight());
  156. }
  157. }
  158. }
  159. void resized() override
  160. {
  161. auto r = getLocalBounds().reduced (4 + 8, 4);
  162. auto h = r.getHeight();
  163. auto w = static_cast<int> (h * 1.5);
  164. r.removeFromLeft (h);
  165. purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
  166. nameLabel.setBounds (r.removeFromTop (18));
  167. priceLabel.setBounds (r.removeFromTop (18));
  168. }
  169. void update (int rowNumber, bool rowIsSelected)
  170. {
  171. isSelected = rowIsSelected;
  172. rowSelected = rowNumber;
  173. if (isPositiveAndBelow (rowNumber, voices.size()))
  174. {
  175. auto imageResourceName = voices[rowNumber] + "_png";
  176. nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
  177. auto purchase = getInstance()->getPurchases().getPurchase (rowNumber);
  178. hasBeenPurchased = purchase.isPurchased;
  179. purchaseInProgress = purchase.purchaseInProgress;
  180. if (purchaseInProgress)
  181. startTimer (1000 / 50);
  182. else
  183. stopTimer();
  184. nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
  185. nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  186. priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
  187. priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  188. priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
  189. if (rowNumber == 0)
  190. {
  191. purchaseButton.setButtonText ("Internal");
  192. purchaseButton.setEnabled (false);
  193. }
  194. else
  195. {
  196. purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
  197. purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
  198. }
  199. setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
  200. int rawSize;
  201. if (auto* rawData = BinaryData::getNamedResource (imageResourceName.toRawUTF8(), rawSize))
  202. {
  203. MemoryInputStream imageData (rawData, static_cast<size_t> (rawSize), false);
  204. avatar = PNGImageFormat().decodeImage (imageData);
  205. }
  206. }
  207. }
  208. private:
  209. //==============================================================================
  210. void buttonClicked (Button*) override
  211. {
  212. if (rowSelected >= 0)
  213. {
  214. if (! hasBeenPurchased)
  215. {
  216. getInstance()->getPurchases().purchaseVoice (rowSelected);
  217. purchaseInProgress = true;
  218. startTimer (1000 / 50);
  219. }
  220. }
  221. }
  222. void timerCallback() override { repaint(); }
  223. //==============================================================================
  224. bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
  225. int rowSelected = -1;
  226. Image avatar;
  227. StringArray voices;
  228. Label nameLabel, priceLabel;
  229. TextButton purchaseButton {"Purchase"};
  230. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
  231. };
  232. //==============================================================================
  233. VoiceModel() : voiceProducts (getInstance()->getPurchases().getVoiceNames()) {}
  234. int getNumRows() override { return voiceProducts.size(); }
  235. Component* refreshComponentForRow (int row, bool selected, Component* existing) override
  236. {
  237. if (isPositiveAndBelow (row, voiceProducts.size()))
  238. {
  239. if (existing == nullptr)
  240. existing = new VoiceRow;
  241. if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
  242. voiceRow->update (row, selected);
  243. return existing;
  244. }
  245. return nullptr;
  246. }
  247. void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
  248. {
  249. auto r = Rectangle<int> (0, 0, w, h).reduced (4);
  250. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  251. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  252. g.fillRect (r);
  253. }
  254. private:
  255. StringArray voiceProducts;
  256. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
  257. };
  258. //==============================================================================
  259. void resized() override
  260. {
  261. auto r = getLocalBounds().reduced (20);
  262. {
  263. auto phraseArea = r.removeFromTop (r.getHeight() / 2);
  264. phraseLabel.setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
  265. playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
  266. phraseListBox.setBounds (phraseArea);
  267. }
  268. {
  269. auto voiceArea = r;
  270. voiceLabel.setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
  271. voiceListBox.setBounds (voiceArea);
  272. }
  273. }
  274. void paint (Graphics& g) override
  275. {
  276. g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
  277. .findColour (ResizableWindow::backgroundColourId));
  278. }
  279. //==============================================================================
  280. void buttonClicked (Button*) override
  281. {
  282. MemoryOutputStream resourceName;
  283. auto idx = voiceListBox.getSelectedRow();
  284. if (isPositiveAndBelow (idx, soundNames.size()))
  285. {
  286. resourceName << soundNames[idx] << phraseListBox.getSelectedRow() << "_ogg";
  287. int numBytes;
  288. if (auto* data = BinaryData::getNamedResource (resourceName.toString().toRawUTF8(), numBytes))
  289. getInstance()->getPlayer().play (data, static_cast<size_t> (numBytes));
  290. }
  291. }
  292. //==============================================================================
  293. StringArray soundNames = getInstance()->getPurchases().getVoiceNames();
  294. PhraseModel phraseModel;
  295. Label phraseLabel {"phraseLabel", NEEDS_TRANS ("Phrases:")};
  296. ListBox phraseListBox { "phraseListBox", &phraseModel };
  297. TextButton playStopButton {"Play"};
  298. VoiceModel voiceModel;
  299. Label voiceLabel {"voiceLabel", NEEDS_TRANS ("Voices:")};
  300. ListBox voiceListBox { "voiceListBox", &voiceModel };
  301. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
  302. };
  303. //==============================================================================
  304. class MainWindow : public DocumentWindow
  305. {
  306. public:
  307. MainWindow () : DocumentWindow (ProjectInfo::projectName,
  308. Desktop::getInstance().getDefaultLookAndFeel()
  309. .findColour (ResizableWindow::backgroundColourId),
  310. DocumentWindow::allButtons)
  311. {
  312. setUsingNativeTitleBar (true);
  313. setContentOwned (new MainContentComponent(), true);
  314. #if JUCE_ANDROID || JUCE_IOS
  315. setFullScreen (true);
  316. #else
  317. centreWithSize (getWidth(), getHeight());
  318. #endif
  319. setVisible (true);
  320. }
  321. void closeButtonPressed() override
  322. {
  323. JUCEApplication::getInstance()->systemRequestedQuit();
  324. }
  325. private:
  326. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
  327. };
  328. private:
  329. //==============================================================================
  330. void handleAsyncUpdate() override
  331. {
  332. if (auto* contentComponent = dynamic_cast<MainContentComponent*> (mainWindow->getContentComponent()))
  333. contentComponent->updateDisplay();
  334. }
  335. //==============================================================================
  336. VoicePurchases voicePurchases;
  337. AudioDeviceManager dm;
  338. SoundPlayer player;
  339. ScopedPointer<MainWindow> mainWindow;
  340. ScopedPointer<AlertWindow> alertWindow;
  341. };
  342. StringArray InAppPurchaseApplication::MainContentComponent::PhraseModel::phrases {{"I love JUCE!", "The five dimensions of touch", "Make it fast!"}};
  343. //==============================================================================
  344. // This macro generates the main() routine that launches the app.
  345. START_JUCE_APPLICATION (InAppPurchaseApplication)