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.

429 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,
  119. private Button::Listener,
  120. private Timer
  121. {
  122. public:
  123. VoiceRow() : voices (getInstance()->getPurchases().getVoiceNames())
  124. {
  125. addAndMakeVisible (nameLabel);
  126. addAndMakeVisible (purchaseButton);
  127. addAndMakeVisible (priceLabel);
  128. purchaseButton.addListener (this);
  129. setSize (600, 33);
  130. }
  131. void paint (Graphics& g) override
  132. {
  133. auto r = getLocalBounds().reduced (4);
  134. {
  135. auto voiceIconBounds = r.removeFromLeft (r.getHeight());
  136. g.setColour (Colours::black);
  137. g.drawRect (voiceIconBounds);
  138. voiceIconBounds.reduce (1, 1);
  139. g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
  140. g.fillRect (voiceIconBounds);
  141. g.drawImage (avatar, voiceIconBounds.toFloat());
  142. if (! hasBeenPurchased)
  143. {
  144. g.setColour (Colours::white.withAlpha (0.8f));
  145. g.fillRect (voiceIconBounds);
  146. if (purchaseInProgress)
  147. getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
  148. voiceIconBounds.getX(), voiceIconBounds.getY(),
  149. voiceIconBounds.getWidth(), voiceIconBounds.getHeight());
  150. }
  151. }
  152. }
  153. void resized() override
  154. {
  155. auto r = getLocalBounds().reduced (4 + 8, 4);
  156. auto h = r.getHeight();
  157. auto w = static_cast<int> (h * 1.5);
  158. r.removeFromLeft (h);
  159. purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
  160. nameLabel.setBounds (r.removeFromTop (18));
  161. priceLabel.setBounds (r.removeFromTop (18));
  162. }
  163. void update (int rowNumber, bool rowIsSelected)
  164. {
  165. isSelected = rowIsSelected;
  166. rowSelected = rowNumber;
  167. if (isPositiveAndBelow (rowNumber, voices.size()))
  168. {
  169. auto imageResourceName = voices[rowNumber] + "_png";
  170. nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
  171. auto purchase = getInstance()->getPurchases().getPurchase (rowNumber);
  172. hasBeenPurchased = purchase.isPurchased;
  173. purchaseInProgress = purchase.purchaseInProgress;
  174. if (purchaseInProgress)
  175. startTimer (1000 / 50);
  176. else
  177. stopTimer();
  178. nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
  179. nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  180. priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
  181. priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  182. priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
  183. if (rowNumber == 0)
  184. {
  185. purchaseButton.setButtonText ("Internal");
  186. purchaseButton.setEnabled (false);
  187. }
  188. else
  189. {
  190. purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
  191. purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
  192. }
  193. setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
  194. int rawSize;
  195. if (auto* rawData = BinaryData::getNamedResource (imageResourceName.toRawUTF8(), rawSize))
  196. {
  197. MemoryInputStream imageData (rawData, static_cast<size_t> (rawSize), false);
  198. avatar = PNGImageFormat().decodeImage (imageData);
  199. }
  200. }
  201. }
  202. private:
  203. //==============================================================================
  204. void buttonClicked (Button*) override
  205. {
  206. if (rowSelected >= 0)
  207. {
  208. if (! hasBeenPurchased)
  209. {
  210. getInstance()->getPurchases().purchaseVoice (rowSelected);
  211. purchaseInProgress = true;
  212. startTimer (1000 / 50);
  213. }
  214. }
  215. }
  216. void timerCallback() override { repaint(); }
  217. //==============================================================================
  218. bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
  219. int rowSelected = -1;
  220. Image avatar;
  221. StringArray voices;
  222. Label nameLabel, priceLabel;
  223. TextButton purchaseButton {"Purchase"};
  224. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
  225. };
  226. //==============================================================================
  227. VoiceModel() : voiceProducts (getInstance()->getPurchases().getVoiceNames()) {}
  228. int getNumRows() override { return voiceProducts.size(); }
  229. Component* refreshComponentForRow (int row, bool selected, Component* existing) override
  230. {
  231. if (isPositiveAndBelow (row, voiceProducts.size()))
  232. {
  233. if (existing == nullptr)
  234. existing = new VoiceRow;
  235. if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
  236. voiceRow->update (row, selected);
  237. return existing;
  238. }
  239. return nullptr;
  240. }
  241. void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
  242. {
  243. auto r = Rectangle<int> (0, 0, w, h).reduced (4);
  244. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  245. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  246. g.fillRect (r);
  247. }
  248. private:
  249. StringArray voiceProducts;
  250. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
  251. };
  252. //==============================================================================
  253. void resized() override
  254. {
  255. auto r = getLocalBounds().reduced (20);
  256. {
  257. auto phraseArea = r.removeFromTop (r.getHeight() / 2);
  258. phraseLabel.setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
  259. playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
  260. phraseListBox.setBounds (phraseArea);
  261. }
  262. {
  263. auto voiceArea = r;
  264. voiceLabel.setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
  265. voiceListBox.setBounds (voiceArea);
  266. }
  267. }
  268. void paint (Graphics& g) override
  269. {
  270. g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
  271. .findColour (ResizableWindow::backgroundColourId));
  272. }
  273. //==============================================================================
  274. void buttonClicked (Button*) override
  275. {
  276. MemoryOutputStream resourceName;
  277. auto idx = voiceListBox.getSelectedRow();
  278. if (isPositiveAndBelow (idx, soundNames.size()))
  279. {
  280. resourceName << soundNames[idx] << phraseListBox.getSelectedRow() << "_ogg";
  281. int numBytes;
  282. if (auto* data = BinaryData::getNamedResource (resourceName.toString().toRawUTF8(), numBytes))
  283. getInstance()->getPlayer().play (data, static_cast<size_t> (numBytes));
  284. }
  285. }
  286. //==============================================================================
  287. StringArray soundNames = getInstance()->getPurchases().getVoiceNames();
  288. PhraseModel phraseModel;
  289. Label phraseLabel {"phraseLabel", NEEDS_TRANS ("Phrases:")};
  290. ListBox phraseListBox { "phraseListBox", &phraseModel };
  291. TextButton playStopButton {"Play"};
  292. VoiceModel voiceModel;
  293. Label voiceLabel {"voiceLabel", NEEDS_TRANS ("Voices:")};
  294. ListBox voiceListBox { "voiceListBox", &voiceModel };
  295. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
  296. };
  297. //==============================================================================
  298. class MainWindow : public DocumentWindow
  299. {
  300. public:
  301. MainWindow() : DocumentWindow (ProjectInfo::projectName,
  302. Desktop::getInstance().getDefaultLookAndFeel()
  303. .findColour (ResizableWindow::backgroundColourId),
  304. DocumentWindow::allButtons)
  305. {
  306. setUsingNativeTitleBar (true);
  307. setContentOwned (new MainContentComponent(), true);
  308. #if JUCE_ANDROID || JUCE_IOS
  309. setFullScreen (true);
  310. #else
  311. centreWithSize (getWidth(), getHeight());
  312. #endif
  313. setVisible (true);
  314. }
  315. void closeButtonPressed() override
  316. {
  317. JUCEApplication::getInstance()->systemRequestedQuit();
  318. }
  319. private:
  320. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
  321. };
  322. private:
  323. //==============================================================================
  324. void handleAsyncUpdate() override
  325. {
  326. if (auto* contentComponent = dynamic_cast<MainContentComponent*> (mainWindow->getContentComponent()))
  327. contentComponent->updateDisplay();
  328. }
  329. //==============================================================================
  330. VoicePurchases voicePurchases;
  331. AudioDeviceManager dm;
  332. SoundPlayer player;
  333. ScopedPointer<MainWindow> mainWindow;
  334. ScopedPointer<AlertWindow> alertWindow;
  335. };
  336. StringArray InAppPurchaseApplication::MainContentComponent::PhraseModel::phrases {"I love JUCE!", "The five dimensions of touch", "Make it fast!"};
  337. //==============================================================================
  338. // This macro generates the main() routine that launches the app.
  339. START_JUCE_APPLICATION (InAppPurchaseApplication)