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.

747 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. package com.juce;
  18. import android.app.Activity;
  19. import android.app.AlertDialog;
  20. import android.content.DialogInterface;
  21. import android.content.Context;
  22. import android.content.Intent;
  23. import android.content.res.Configuration;
  24. import android.net.Uri;
  25. import android.os.Bundle;
  26. import android.view.*;
  27. import android.view.inputmethod.BaseInputConnection;
  28. import android.view.inputmethod.EditorInfo;
  29. import android.view.inputmethod.InputConnection;
  30. import android.view.inputmethod.InputMethodManager;
  31. import android.graphics.*;
  32. import android.opengl.*;
  33. import android.text.ClipboardManager;
  34. import android.text.InputType;
  35. import android.util.DisplayMetrics;
  36. import java.io.BufferedInputStream;
  37. import java.io.IOException;
  38. import java.io.InputStream;
  39. import java.io.OutputStream;
  40. import java.net.URL;
  41. import java.net.HttpURLConnection;
  42. import javax.microedition.khronos.egl.EGLConfig;
  43. import javax.microedition.khronos.opengles.GL10;
  44. import android.media.AudioManager;
  45. import android.media.MediaScannerConnection;
  46. import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  47. //==============================================================================
  48. public final class JuceAppActivity extends Activity
  49. {
  50. //==============================================================================
  51. static
  52. {
  53. System.loadLibrary ("juce_jni");
  54. }
  55. @Override
  56. public final void onCreate (Bundle savedInstanceState)
  57. {
  58. super.onCreate (savedInstanceState);
  59. viewHolder = new ViewHolder (this);
  60. setContentView (viewHolder);
  61. setVolumeControlStream (AudioManager.STREAM_MUSIC);
  62. }
  63. @Override
  64. protected final void onDestroy()
  65. {
  66. quitApp();
  67. super.onDestroy();
  68. }
  69. @Override
  70. protected final void onPause()
  71. {
  72. if (viewHolder != null)
  73. viewHolder.onPause();
  74. suspendApp();
  75. super.onPause();
  76. }
  77. @Override
  78. protected final void onResume()
  79. {
  80. super.onResume();
  81. if (viewHolder != null)
  82. viewHolder.onResume();
  83. resumeApp();
  84. }
  85. @Override
  86. public void onConfigurationChanged (Configuration cfg)
  87. {
  88. super.onConfigurationChanged (cfg);
  89. setContentView (viewHolder);
  90. }
  91. private void callAppLauncher()
  92. {
  93. launchApp (getApplicationInfo().publicSourceDir,
  94. getApplicationInfo().dataDir);
  95. }
  96. //==============================================================================
  97. private native void launchApp (String appFile, String appDataDir);
  98. private native void quitApp();
  99. private native void suspendApp();
  100. private native void resumeApp();
  101. private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
  102. //==============================================================================
  103. public native void deliverMessage (long value);
  104. private android.os.Handler messageHandler = new android.os.Handler();
  105. public final void postMessage (long value)
  106. {
  107. messageHandler.post (new MessageCallback (value));
  108. }
  109. private final class MessageCallback implements Runnable
  110. {
  111. public MessageCallback (long value_) { value = value_; }
  112. public final void run() { deliverMessage (value); }
  113. private long value;
  114. }
  115. //==============================================================================
  116. private ViewHolder viewHolder;
  117. public final ComponentPeerView createNewView (boolean opaque, long host)
  118. {
  119. ComponentPeerView v = new ComponentPeerView (this, opaque, host);
  120. viewHolder.addView (v);
  121. return v;
  122. }
  123. public final void deleteView (ComponentPeerView view)
  124. {
  125. ViewGroup group = (ViewGroup) (view.getParent());
  126. if (group != null)
  127. group.removeView (view);
  128. }
  129. final class ViewHolder extends ViewGroup
  130. {
  131. public ViewHolder (Context context)
  132. {
  133. super (context);
  134. setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
  135. setFocusable (false);
  136. }
  137. protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
  138. {
  139. setScreenSize (getWidth(), getHeight(), getDPI());
  140. if (isFirstResize)
  141. {
  142. isFirstResize = false;
  143. callAppLauncher();
  144. }
  145. }
  146. public final void onPause()
  147. {
  148. for (int i = getChildCount(); --i >= 0;)
  149. {
  150. View v = getChildAt (i);
  151. if (v instanceof ComponentPeerView)
  152. ((ComponentPeerView) v).onPause();
  153. }
  154. }
  155. public final void onResume()
  156. {
  157. for (int i = getChildCount(); --i >= 0;)
  158. {
  159. View v = getChildAt (i);
  160. if (v instanceof ComponentPeerView)
  161. ((ComponentPeerView) v).onResume();
  162. }
  163. }
  164. private final int getDPI()
  165. {
  166. DisplayMetrics metrics = new DisplayMetrics();
  167. getWindowManager().getDefaultDisplay().getMetrics (metrics);
  168. return metrics.densityDpi;
  169. }
  170. private boolean isFirstResize = true;
  171. }
  172. public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
  173. {
  174. canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
  175. }
  176. //==============================================================================
  177. public final String getClipboardContent()
  178. {
  179. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  180. return clipboard.getText().toString();
  181. }
  182. public final void setClipboardContent (String newText)
  183. {
  184. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  185. clipboard.setText (newText);
  186. }
  187. //==============================================================================
  188. public final void showMessageBox (String title, String message, final long callback)
  189. {
  190. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  191. builder.setTitle (title)
  192. .setMessage (message)
  193. .setCancelable (true)
  194. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  195. {
  196. public void onClick (DialogInterface dialog, int id)
  197. {
  198. dialog.cancel();
  199. JuceAppActivity.this.alertDismissed (callback, 0);
  200. }
  201. });
  202. builder.create().show();
  203. }
  204. public final void showOkCancelBox (String title, String message, final long callback)
  205. {
  206. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  207. builder.setTitle (title)
  208. .setMessage (message)
  209. .setCancelable (true)
  210. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  211. {
  212. public void onClick (DialogInterface dialog, int id)
  213. {
  214. dialog.cancel();
  215. JuceAppActivity.this.alertDismissed (callback, 1);
  216. }
  217. })
  218. .setNegativeButton ("Cancel", new DialogInterface.OnClickListener()
  219. {
  220. public void onClick (DialogInterface dialog, int id)
  221. {
  222. dialog.cancel();
  223. JuceAppActivity.this.alertDismissed (callback, 0);
  224. }
  225. });
  226. builder.create().show();
  227. }
  228. public final void showYesNoCancelBox (String title, String message, final long callback)
  229. {
  230. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  231. builder.setTitle (title)
  232. .setMessage (message)
  233. .setCancelable (true)
  234. .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
  235. {
  236. public void onClick (DialogInterface dialog, int id)
  237. {
  238. dialog.cancel();
  239. JuceAppActivity.this.alertDismissed (callback, 1);
  240. }
  241. })
  242. .setNegativeButton ("No", new DialogInterface.OnClickListener()
  243. {
  244. public void onClick (DialogInterface dialog, int id)
  245. {
  246. dialog.cancel();
  247. JuceAppActivity.this.alertDismissed (callback, 2);
  248. }
  249. })
  250. .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
  251. {
  252. public void onClick (DialogInterface dialog, int id)
  253. {
  254. dialog.cancel();
  255. JuceAppActivity.this.alertDismissed (callback, 0);
  256. }
  257. });
  258. builder.create().show();
  259. }
  260. public native void alertDismissed (long callback, int id);
  261. //==============================================================================
  262. public final class ComponentPeerView extends ViewGroup
  263. implements View.OnFocusChangeListener
  264. {
  265. public ComponentPeerView (Context context, boolean opaque_, long host)
  266. {
  267. super (context);
  268. this.host = host;
  269. setWillNotDraw (false);
  270. opaque = opaque_;
  271. setFocusable (true);
  272. setFocusableInTouchMode (true);
  273. setOnFocusChangeListener (this);
  274. requestFocus();
  275. }
  276. //==============================================================================
  277. private native void handlePaint (long host, Canvas canvas);
  278. @Override
  279. public void onDraw (Canvas canvas)
  280. {
  281. handlePaint (host, canvas);
  282. }
  283. @Override
  284. public boolean isOpaque()
  285. {
  286. return opaque;
  287. }
  288. private boolean opaque;
  289. private long host;
  290. //==============================================================================
  291. private native void handleMouseDown (long host, int index, float x, float y, long time);
  292. private native void handleMouseDrag (long host, int index, float x, float y, long time);
  293. private native void handleMouseUp (long host, int index, float x, float y, long time);
  294. @Override
  295. public boolean onTouchEvent (MotionEvent event)
  296. {
  297. int action = event.getAction();
  298. long time = event.getEventTime();
  299. switch (action & MotionEvent.ACTION_MASK)
  300. {
  301. case MotionEvent.ACTION_DOWN:
  302. handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
  303. return true;
  304. case MotionEvent.ACTION_CANCEL:
  305. case MotionEvent.ACTION_UP:
  306. handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
  307. return true;
  308. case MotionEvent.ACTION_MOVE:
  309. {
  310. int n = event.getPointerCount();
  311. for (int i = 0; i < n; ++i)
  312. handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  313. return true;
  314. }
  315. case MotionEvent.ACTION_POINTER_UP:
  316. {
  317. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  318. handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  319. return true;
  320. }
  321. case MotionEvent.ACTION_POINTER_DOWN:
  322. {
  323. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  324. handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  325. return true;
  326. }
  327. default:
  328. break;
  329. }
  330. return false;
  331. }
  332. //==============================================================================
  333. private native void handleKeyDown (long host, int keycode, int textchar);
  334. private native void handleKeyUp (long host, int keycode, int textchar);
  335. public void showKeyboard (String type)
  336. {
  337. InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
  338. if (imm != null)
  339. {
  340. if (type.length() > 0)
  341. {
  342. imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
  343. imm.setInputMethod (getWindowToken(), type);
  344. }
  345. else
  346. {
  347. imm.hideSoftInputFromWindow (getWindowToken(), 0);
  348. }
  349. }
  350. }
  351. @Override
  352. public boolean onKeyDown (int keyCode, KeyEvent event)
  353. {
  354. switch (keyCode)
  355. {
  356. case KeyEvent.KEYCODE_VOLUME_UP:
  357. case KeyEvent.KEYCODE_VOLUME_DOWN:
  358. return super.onKeyDown (keyCode, event);
  359. default: break;
  360. }
  361. handleKeyDown (host, keyCode, event.getUnicodeChar());
  362. return true;
  363. }
  364. @Override
  365. public boolean onKeyUp (int keyCode, KeyEvent event)
  366. {
  367. handleKeyUp (host, keyCode, event.getUnicodeChar());
  368. return true;
  369. }
  370. // this is here to make keyboard entry work on a Galaxy Tab2 10.1
  371. @Override
  372. public InputConnection onCreateInputConnection (EditorInfo outAttrs)
  373. {
  374. outAttrs.actionLabel = "";
  375. outAttrs.hintText = "";
  376. outAttrs.initialCapsMode = 0;
  377. outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
  378. outAttrs.label = "";
  379. outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
  380. outAttrs.inputType = InputType.TYPE_NULL;
  381. return new BaseInputConnection (this, false);
  382. }
  383. //==============================================================================
  384. @Override
  385. protected void onSizeChanged (int w, int h, int oldw, int oldh)
  386. {
  387. super.onSizeChanged (w, h, oldw, oldh);
  388. viewSizeChanged (host);
  389. }
  390. @Override
  391. protected void onLayout (boolean changed, int left, int top, int right, int bottom)
  392. {
  393. for (int i = getChildCount(); --i >= 0;)
  394. requestTransparentRegion (getChildAt (i));
  395. }
  396. private native void viewSizeChanged (long host);
  397. @Override
  398. public void onFocusChange (View v, boolean hasFocus)
  399. {
  400. if (v == this)
  401. focusChanged (host, hasFocus);
  402. }
  403. private native void focusChanged (long host, boolean hasFocus);
  404. public void setViewName (String newName) {}
  405. public boolean isVisible() { return getVisibility() == VISIBLE; }
  406. public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
  407. public boolean containsPoint (int x, int y)
  408. {
  409. return true; //xxx needs to check overlapping views
  410. }
  411. public final void onPause()
  412. {
  413. for (int i = getChildCount(); --i >= 0;)
  414. {
  415. View v = getChildAt (i);
  416. if (v instanceof OpenGLView)
  417. ((OpenGLView) v).onPause();
  418. }
  419. }
  420. public final void onResume()
  421. {
  422. for (int i = getChildCount(); --i >= 0;)
  423. {
  424. View v = getChildAt (i);
  425. if (v instanceof OpenGLView)
  426. ((OpenGLView) v).onResume();
  427. }
  428. }
  429. public OpenGLView createGLView()
  430. {
  431. OpenGLView glView = new OpenGLView (getContext());
  432. addView (glView);
  433. return glView;
  434. }
  435. }
  436. //==============================================================================
  437. public final class OpenGLView extends GLSurfaceView
  438. implements GLSurfaceView.Renderer
  439. {
  440. OpenGLView (Context context)
  441. {
  442. super (context);
  443. setEGLContextClientVersion (2);
  444. setRenderer (this);
  445. setRenderMode (RENDERMODE_WHEN_DIRTY);
  446. }
  447. @Override
  448. public void onSurfaceCreated (GL10 unused, EGLConfig config)
  449. {
  450. contextCreated();
  451. }
  452. @Override
  453. public void onSurfaceChanged (GL10 unused, int width, int height)
  454. {
  455. contextChangedSize();
  456. }
  457. @Override
  458. public void onDrawFrame (GL10 unused)
  459. {
  460. render();
  461. }
  462. private native void contextCreated();
  463. private native void contextChangedSize();
  464. private native void render();
  465. }
  466. //==============================================================================
  467. public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds)
  468. {
  469. Path p = new Path();
  470. paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p);
  471. RectF boundsF = new RectF();
  472. p.computeBounds (boundsF, true);
  473. matrix.mapRect (boundsF);
  474. boundsF.roundOut (bounds);
  475. bounds.left--;
  476. bounds.right++;
  477. final int w = bounds.width();
  478. final int h = Math.max (1, bounds.height());
  479. Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
  480. Canvas c = new Canvas (bm);
  481. matrix.postTranslate (-bounds.left, -bounds.top);
  482. c.setMatrix (matrix);
  483. c.drawPath (p, paint);
  484. final int sizeNeeded = w * h;
  485. if (cachedRenderArray.length < sizeNeeded)
  486. cachedRenderArray = new int [sizeNeeded];
  487. bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
  488. bm.recycle();
  489. return cachedRenderArray;
  490. }
  491. private int[] cachedRenderArray = new int [256];
  492. //==============================================================================
  493. public static class HTTPStream
  494. {
  495. public HTTPStream (HttpURLConnection connection_,
  496. int[] statusCode, StringBuffer responseHeaders) throws IOException
  497. {
  498. connection = connection_;
  499. try
  500. {
  501. inputStream = new BufferedInputStream (connection.getInputStream());
  502. }
  503. catch (IOException e)
  504. {
  505. if (connection.getResponseCode() < org.apache.http.HttpStatus.SC_BAD_REQUEST)
  506. throw e;
  507. }
  508. finally
  509. {
  510. statusCode[0] = connection.getResponseCode();
  511. }
  512. if (statusCode[0] >= org.apache.http.HttpStatus.SC_BAD_REQUEST)
  513. inputStream = connection.getErrorStream();
  514. else
  515. inputStream = connection.getInputStream();
  516. for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
  517. if (entry.getKey() != null && entry.getValue() != null)
  518. responseHeaders.append (entry.getKey() + ": "
  519. + android.text.TextUtils.join (",", entry.getValue()) + "\n");
  520. }
  521. public final void release()
  522. {
  523. try
  524. {
  525. inputStream.close();
  526. }
  527. catch (IOException e)
  528. {}
  529. connection.disconnect();
  530. }
  531. public final int read (byte[] buffer, int numBytes)
  532. {
  533. int num = 0;
  534. try
  535. {
  536. num = inputStream.read (buffer, 0, numBytes);
  537. }
  538. catch (IOException e)
  539. {}
  540. if (num > 0)
  541. position += num;
  542. return num;
  543. }
  544. public final long getPosition() { return position; }
  545. public final long getTotalLength() { return -1; }
  546. public final boolean isExhausted() { return false; }
  547. public final boolean setPosition (long newPos) { return false; }
  548. private HttpURLConnection connection;
  549. private InputStream inputStream;
  550. private long position;
  551. }
  552. public static final HTTPStream createHTTPStream (String address,
  553. boolean isPost, byte[] postData, String headers,
  554. int timeOutMs, int[] statusCode,
  555. StringBuffer responseHeaders)
  556. {
  557. try
  558. {
  559. HttpURLConnection connection = (HttpURLConnection) (new URL(address)
  560. .openConnection());
  561. if (connection != null)
  562. {
  563. try
  564. {
  565. if (isPost)
  566. {
  567. connection.setRequestMethod("POST");
  568. connection.setConnectTimeout(timeOutMs);
  569. connection.setDoOutput(true);
  570. connection.setChunkedStreamingMode(0);
  571. OutputStream out = connection.getOutputStream();
  572. out.write(postData);
  573. out.flush();
  574. }
  575. return new HTTPStream (connection, statusCode, responseHeaders);
  576. }
  577. catch (Throwable e)
  578. {
  579. connection.disconnect();
  580. }
  581. }
  582. }
  583. catch (Throwable e) {}
  584. return null;
  585. }
  586. public final void launchURL (String url)
  587. {
  588. startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
  589. }
  590. public static final String getLocaleValue (boolean isRegion)
  591. {
  592. java.util.Locale locale = java.util.Locale.getDefault();
  593. return isRegion ? locale.getDisplayCountry (java.util.Locale.US)
  594. : locale.getDisplayLanguage (java.util.Locale.US);
  595. }
  596. //==============================================================================
  597. private final class SingleMediaScanner implements MediaScannerConnectionClient
  598. {
  599. public SingleMediaScanner (Context context, String filename)
  600. {
  601. file = filename;
  602. msc = new MediaScannerConnection (context, this);
  603. msc.connect();
  604. }
  605. @Override
  606. public void onMediaScannerConnected()
  607. {
  608. msc.scanFile (file, null);
  609. }
  610. @Override
  611. public void onScanCompleted (String path, Uri uri)
  612. {
  613. msc.disconnect();
  614. }
  615. private MediaScannerConnection msc;
  616. private String file;
  617. }
  618. public final void scanFile (String filename)
  619. {
  620. new SingleMediaScanner (this, filename);
  621. }
  622. }