blob: 6aab070439aad7ccff2a88ac9a714d31887d07c8 [file] [log] [blame]
alisioncc7bb422013-06-06 15:31:39 -04001/*
2 * Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
3 *
4 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
5 * Adrien Béraud <adrien.beraud@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 *
21 * Additional permission under GNU GPL version 3 section 7:
22 *
23 * If you modify this program, or any covered work, by linking or
24 * combining it with the OpenSSL project's OpenSSL library (or a
25 * modified version of that library), containing parts covered by the
26 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
27 * grants you additional permission to convey the resulting work.
28 * Corresponding Source for a non-source form of such a combination
29 * shall include the source code for the parts of OpenSSL used as well
30 * as that of the covered work.
31 */
32
Adrien Béraud04463092013-05-06 14:17:22 +100033package com.savoirfairelinux.sflphone.model;
alisionfe9cf712013-05-03 17:26:08 -040034
Adrien Béraud0c9bd8f2013-05-30 16:16:57 +100035import java.util.List;
36
alisionfe9cf712013-05-03 17:26:08 -040037import android.content.Context;
38import android.graphics.Canvas;
39import android.graphics.Color;
Adrien Béraud33268882013-05-18 03:41:15 +100040import android.graphics.Paint;
Adrien Béraud6bbce912013-05-24 00:48:13 +100041import android.graphics.Paint.Align;
Adrien Béraud04463092013-05-06 14:17:22 +100042import android.os.Handler;
43import android.os.Message;
alisionfe9cf712013-05-03 17:26:08 -040044import android.util.AttributeSet;
45import android.util.Log;
alision58356b72013-06-03 17:13:36 -040046import android.view.GestureDetector;
47import android.view.GestureDetector.OnGestureListener;
alisionfe9cf712013-05-03 17:26:08 -040048import android.view.MotionEvent;
Adrien Béraud04463092013-05-06 14:17:22 +100049import android.view.SurfaceHolder;
50import android.view.SurfaceView;
alisionfe9cf712013-05-03 17:26:08 -040051import android.view.View;
Adrien Béraud04463092013-05-06 14:17:22 +100052import android.view.View.OnTouchListener;
alisionfe9cf712013-05-03 17:26:08 -040053
alision2cb99562013-05-30 17:02:20 -040054public class BubblesView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {
55 private static final String TAG = BubblesView.class.getSimpleName();
alisionfe9cf712013-05-03 17:26:08 -040056
alision2cb99562013-05-30 17:02:20 -040057 private BubblesThread thread = null;
58 private BubbleModel model;
alisionfe9cf712013-05-03 17:26:08 -040059
alision2cb99562013-05-30 17:02:20 -040060 private Paint attractor_paint = new Paint();
61 private Paint name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Adrien Béraud6bbce912013-05-24 00:48:13 +100062
alisione38001f2013-06-04 14:14:39 -040063 private GestureDetector gDetector;
64
alision2cb99562013-05-30 17:02:20 -040065 private float density;
66 private float textDensity;
Adrien Béraud33268882013-05-18 03:41:15 +100067
alision2cb99562013-05-30 17:02:20 -040068 private boolean dragging_bubble = false;
Adrien Béraudc9c424d2013-05-30 17:47:35 +100069
alision2cb99562013-05-30 17:02:20 -040070 public BubblesView(Context context, AttributeSet attrs) {
71 super(context, attrs);
alisionfe9cf712013-05-03 17:26:08 -040072
alision2cb99562013-05-30 17:02:20 -040073 density = getResources().getDisplayMetrics().density;
74 textDensity = getResources().getDisplayMetrics().scaledDensity;
Adrien Béraud6bbce912013-05-24 00:48:13 +100075
alision2cb99562013-05-30 17:02:20 -040076 SurfaceHolder holder = getHolder();
77 holder.addCallback(this);
alisionfe9cf712013-05-03 17:26:08 -040078
alision2cb99562013-05-30 17:02:20 -040079 // create thread only; it's started in surfaceCreated()
80 createThread();
alisionfe9cf712013-05-03 17:26:08 -040081
alision2cb99562013-05-30 17:02:20 -040082 setOnTouchListener(this);
83 setFocusable(true);
Adrien Béraud33268882013-05-18 03:41:15 +100084
alision2cb99562013-05-30 17:02:20 -040085 attractor_paint.setColor(Color.RED);
86 // attractor_paint.set
87 name_paint.setTextSize(18 * textDensity);
88 name_paint.setColor(0xFF303030);
89 name_paint.setTextAlign(Align.CENTER);
alision58356b72013-06-03 17:13:36 -040090
91 gDetector = new GestureDetector(getContext(), new MyOnGestureListener());
alision2cb99562013-05-30 17:02:20 -040092 }
alisionfe9cf712013-05-03 17:26:08 -040093
alision2cb99562013-05-30 17:02:20 -040094 private void createThread() {
95 if (thread != null)
96 return;
97 thread = new BubblesThread(getHolder(), getContext(), new Handler() {
98 @Override
99 public void handleMessage(Message m) {
100 /*
101 * mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text"));
102 */
103 }
104 });
105 if (model != null)
106 thread.setModel(model);
107 }
alisionfe9cf712013-05-03 17:26:08 -0400108
alision2cb99562013-05-30 17:02:20 -0400109 public void setModel(BubbleModel model) {
110 this.model = model;
111 thread.setModel(model);
112 }
alisionfe9cf712013-05-03 17:26:08 -0400113
alision2cb99562013-05-30 17:02:20 -0400114 /*
115 * @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { thread.pause(); } }
116 */
alisionfe9cf712013-05-03 17:26:08 -0400117
alision2cb99562013-05-30 17:02:20 -0400118 @Override
119 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
120 Log.w(TAG, "surfaceChanged " + width + "-" + height);
121 thread.setSurfaceSize(width, height);
122 }
alisionfe9cf712013-05-03 17:26:08 -0400123
alision2cb99562013-05-30 17:02:20 -0400124 /*
125 * Callback invoked when the Surface has been created and is ready to be used.
126 */
127 @Override
128 public void surfaceCreated(SurfaceHolder holder) {
129 // start the thread here so that we don't busy-wait in run()
130 // waiting for the surface to be created
131 createThread();
alisionfe9cf712013-05-03 17:26:08 -0400132
alision2cb99562013-05-30 17:02:20 -0400133 Log.w(TAG, "surfaceCreated");
134 thread.setRunning(true);
135 thread.start();
136 }
alisionfe9cf712013-05-03 17:26:08 -0400137
alision2cb99562013-05-30 17:02:20 -0400138 /*
139 * Callback invoked when the Surface has been destroyed and must no longer be touched. WARNING: after this method returns, the Surface/Canvas must
140 * never be touched again!
141 */
142 @Override
143 public void surfaceDestroyed(SurfaceHolder holder) {
144 // we have to tell thread to shut down & wait for it to finish, or else
145 // it might touch the Surface after we return and explode
146 Log.w(TAG, "surfaceDestroyed");
147 boolean retry = true;
148 thread.setRunning(false);
149 while (retry) {
150 try {
151 thread.join();
152 retry = false;
153 } catch (InterruptedException e) {
154 }
155 }
156 thread = null;
157 }
alisionfe9cf712013-05-03 17:26:08 -0400158
alision2cb99562013-05-30 17:02:20 -0400159 public boolean isDraggingBubble() {
160 return dragging_bubble;
161 }
Adrien Béraudc9c424d2013-05-30 17:47:35 +1000162
alision2cb99562013-05-30 17:02:20 -0400163 class BubblesThread extends Thread {
164 private boolean running = false;
165 private SurfaceHolder surfaceHolder;
alisione38001f2013-06-04 14:14:39 -0400166 public Boolean suspendFlag = false;
alisionfe9cf712013-05-03 17:26:08 -0400167
alision2cb99562013-05-30 17:02:20 -0400168 BubbleModel model = null;
alisionfe9cf712013-05-03 17:26:08 -0400169
alision2cb99562013-05-30 17:02:20 -0400170 public BubblesThread(SurfaceHolder holder, Context context, Handler handler) {
171 surfaceHolder = holder;
172 }
alisionfe9cf712013-05-03 17:26:08 -0400173
alision2cb99562013-05-30 17:02:20 -0400174 public void setModel(BubbleModel model) {
175 this.model = model;
176 }
alisionfe9cf712013-05-03 17:26:08 -0400177
alision2cb99562013-05-30 17:02:20 -0400178 @Override
179 public void run() {
180 while (running) {
181 Canvas c = null;
182 try {
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000183
alisione38001f2013-06-04 14:14:39 -0400184 if (suspendFlag) {
185 synchronized (this) {
186 while (suspendFlag) {
187 try {
188 wait();
189 } catch (InterruptedException e) {
190 // TODO Auto-generated catch block
191 e.printStackTrace();
192 }
193 }
194 }
195 } else {
alisione38001f2013-06-04 14:14:39 -0400196 c = surfaceHolder.lockCanvas(null);
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000197
alisione38001f2013-06-04 14:14:39 -0400198 // for the case the surface is destroyed while already in the loop
199 if (c == null || model == null)
200 continue;
201
202 synchronized (surfaceHolder) {
203 // Log.w(TAG, "Thread doDraw");
204 model.update();
205 doDraw(c);
206 }
alision2cb99562013-05-30 17:02:20 -0400207 }
alisione38001f2013-06-04 14:14:39 -0400208
alision2cb99562013-05-30 17:02:20 -0400209 } finally {
210 if (c != null)
211 surfaceHolder.unlockCanvasAndPost(c);
212 }
213 }
214 }
Adrien Béraud04463092013-05-06 14:17:22 +1000215
alisione38001f2013-06-04 14:14:39 -0400216 public void setPaused(boolean wantToPause) {
217 synchronized (this) {
218 suspendFlag = wantToPause;
219 notify();
220 }
221 }
222
alision2cb99562013-05-30 17:02:20 -0400223 public void setRunning(boolean b) {
224 running = b;
225 }
Adrien Béraud04463092013-05-06 14:17:22 +1000226
alision2cb99562013-05-30 17:02:20 -0400227 public void setSurfaceSize(int width, int height) {
228 synchronized (surfaceHolder) {
229 if (model != null) {
230 model.width = width;
231 model.height = height;
232 }
233 }
234 }
Adrien Béraud04463092013-05-06 14:17:22 +1000235
alision2cb99562013-05-30 17:02:20 -0400236 /**
237 * I got multiple IndexOutOfBoundsException, when switching calls. //FIXME
238 *
239 * @param canvas
240 */
241 private void doDraw(Canvas canvas) {
242 canvas.drawColor(Color.WHITE);
Adrien Béraud04463092013-05-06 14:17:22 +1000243
alision2cb99562013-05-30 17:02:20 -0400244 synchronized (model) {
245 List<Bubble> bubbles = model.getBubbles();
246 List<Attractor> attractors = model.getAttractors();
247 try {
Adrien Béraud0c9bd8f2013-05-30 16:16:57 +1000248
alision2cb99562013-05-30 17:02:20 -0400249 for (int i = 0, n = attractors.size(); i < n; i++) {
250 Attractor a = attractors.get(i);
251 canvas.drawBitmap(a.getBitmap(), null, a.getBounds(), null);
252 }
Adrien Béraud33268882013-05-18 03:41:15 +1000253
alision2cb99562013-05-30 17:02:20 -0400254 for (int i = 0, n = bubbles.size(); i < n; i++) {
255 Bubble b = bubbles.get(i);
256 canvas.drawBitmap(b.getBitmap(), null, b.getBounds(), null);
257 canvas.drawText(b.contact.getmDisplayName(), b.getPosX(), b.getPosY() - 50 * density, name_paint);
258 }
259
260 } catch (IndexOutOfBoundsException e) {
261 Log.e(TAG, e.toString());
262 }
263 }
264 }
265 }
Adrien Béraud04463092013-05-06 14:17:22 +1000266
alisione38001f2013-06-04 14:14:39 -0400267 @Override
268 public boolean onTouch(View v, MotionEvent event) {
269 Log.w(TAG, "onTouch " + event.getAction());
270
271 int action = event.getActionMasked();
272
273 if (action == MotionEvent.ACTION_UP) {
274 if (thread.suspendFlag) {
275 Log.i(TAG, "Relaunch drawing thread");
276 thread.setPaused(false);
277 }
278
279 List<Bubble> bubbles = model.getBubbles();
280 final int n_bubbles = bubbles.size();
281 for (int i = 0; i < n_bubbles; i++) {
282 Bubble b = bubbles.get(i);
283 if (b.dragged) {
284 b.dragged = false;
285 b.target_scale = 1.f;
286 }
287 }
288 dragging_bubble = false;
289 } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) {
290
291 Log.i(TAG, "Not dragging thread should be stopped");
292 thread.setPaused(true);
293 // thread.holdDrawing();
294 }
295
296 return gDetector.onTouchEvent(event);
297 }
298
alision58356b72013-06-03 17:13:36 -0400299 class MyOnGestureListener implements OnGestureListener {
300 @Override
301 public boolean onDown(MotionEvent event) {
302 List<Bubble> bubbles = model.getBubbles();
303 final int n_bubbles = bubbles.size();
304 Log.d("Main", "onDown");
305 for (int i = 0; i < n_bubbles; i++) {
306 Bubble b = bubbles.get(i);
307 if (b.intersects(event.getX(), event.getY())) {
308 b.dragged = true;
309 b.last_drag = System.nanoTime();
310 b.setPos(event.getX(), event.getY());
311 b.target_scale = .8f;
312 dragging_bubble = true;
313 }
314 }
315 return true;
316 }
317
318 @Override
319 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
320 Log.d("Main", "onFling");
321 return true;
322 }
323
324 @Override
325 public void onLongPress(MotionEvent e) {
326 Log.d("Main", "onLongPress");
327 }
328
329 @Override
330 public boolean onScroll(MotionEvent e1, MotionEvent event, float distanceX, float distanceY) {
331 Log.d("Main", "onScroll");
332 List<Bubble> bubbles = model.getBubbles();
333 final int n_bubbles = bubbles.size();
334 long now = System.nanoTime();
335 for (int i = 0; i < n_bubbles; i++) {
336 Bubble b = bubbles.get(i);
337 if (b.dragged) {
338 float x = event.getX(), y = event.getY();
339 float dt = (float) ((now - b.last_drag) / 1000000000.);
340 float dx = x - b.getPosX(), dy = y - b.getPosY();
341 b.last_drag = now;
342
343 b.setPos(event.getX(), event.getY());
344 /*
345 * int hn = event.getHistorySize() - 2; Log.w(TAG, "event.getHistorySize() : " + event.getHistorySize()); if(hn > 0) { float dx =
346 * x-event.getHistoricalX(hn); float dy = y-event.getHistoricalY(hn); float dt = event.getHistoricalEventTime(hn)/1000.f;
347 */
348 b.speed.x = dx / dt;
349 b.speed.y = dy / dt;
350 // Log.w(TAG, "onTouch dx:" + b.speed.x + " dy:" + b.speed.y);
351 // }
352 return true;
353 }
354 }
355 return true;
356 }
357
358 @Override
359 public void onShowPress(MotionEvent e) {
360 Log.d("Main", "onShowPress");
361
362 }
363
364 @Override
365 public boolean onSingleTapUp(MotionEvent e) {
366 Log.d("Main", "onSingleTapUp");
367 return true;
368 }
369 }
370
alisione38001f2013-06-04 14:14:39 -0400371 public void restartDrawing() {
372 if (thread != null && thread.suspendFlag) {
373 Log.i(TAG, "Relaunch drawing thread");
374 thread.setPaused(false);
375 }
376 }
377
alisionfe9cf712013-05-03 17:26:08 -0400378}