make zooming more smooth (zooming on background)
[makneto-zunavac1.git] / src / ui-mobile / declarative / BoardWidget.qml
blob744804b9a85487512e53de0a7f3619f29df12968
1 /*
2  *   Copyright (C) 2011 Lukáš Karas <lukas.karas@centrum.cz>
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the
16  *   Free Software Foundation, Inc.,
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  */
20 import QtQuick 1.1 // version 1.1 include PinchArea
21 import Qt 4.7
22 import QtWebKit 1.0
23 import MyTools 1.0 // for WheelArea
25 Rectangle {
26     id: board
27     width: 100
28     height: 62
29     color: "black"
31     signal sendWbMessage(string sessionId, string xmlContent);
32     signal messageSent(string content);
34     property int canvasWidth : 640;
35     property int canvasHeight: 480;
36     //property string uid : "demo@makneto.org"
38     function log(msg){
39         console.log("II [BoardWidget.qml]: "+msg);
40     }
41     function error(msg){
42         console.log("EE [BoardWidget.qml]: "+msg);
43     }
44     function warn(msg){
45         console.log("WW [BoardWidget.qml]: "+msg);
46     }
48     //signal foregroundColorChanged(variant color)
49     signal whiteboardMessageReceived(string data, string contact)
50     property bool whiteboardInitialiyed: false
51     property variant queue: undefined
52     property int animationLength : 200;
54     function onWhiteboardMessageReceived(data, contact){
55         if (whiteboardInitialiyed){
56             whiteboardMessageReceived.disconnect(onWhiteboardMessageReceived);
57             return;
58         }
60         if (queue===undefined)
61             queue = [];
63         var obj = {};
64         obj.contact = contact;
65         obj.data = data;
67         var tmp = queue;
68         tmp.push(obj);
69         queue = tmp;
70         //log("whiteboard is not initialized... "+JSON.stringify(queue.length));
71     }
74     state: "normal"
75     states: [
76         State {
77             name: "normal"
78             /*
79             PropertyChanges {
80                 target: stateIcon
81                 source: "img/fullscreen.png"
82             }
83             */
84             AnchorChanges { target: board; anchors.top: board.parent.top; anchors.left: board.parent.left; }
85         },
86         State {
87             name: "fullscreen"
88             /*
89             PropertyChanges {
90                 target: stateIcon
91                 source: "img/minimize.png"
92             }
93             */
94             AnchorChanges { target: board; anchors.right: board.parent.right; anchors.bottom: board.parent.bottom}
95         }
96     ]
99     Behavior on width {
100         NumberAnimation{
101             id: onWidthAnimation
102             duration: animationLength
103         }
104     }
105     Behavior on height {
106         NumberAnimation{
107             id: onHeightAnimation
108             duration: animationLength
109         }
110     }
111     Behavior on opacity {
112         NumberAnimation{duration: animationLength}
113     }
115     transitions: Transition {
116        // smoothly reanchor myRect and move into new position
117        AnchorAnimation { duration: animationLength }
118     }
120     Component.onCompleted: {
121         whiteboardMessageReceived.connect(onWhiteboardMessageReceived);
122         //toolbar.foregroundColorChanged.connect(board.foregroundColorChanged);
123     }
125     Rectangle{
126         id: wrapper
127         color: parent.color
128         anchors{top:parent.top; right: toolbar.left; left: parent.left; bottom: parent.bottom}
130         property int previousWidth : canvasWidth+30
131         property int previousHeith : canvasHeight+30
133         /**
134           * zooming process:
136                 PinchArea -> resizeAfterPinch -> onSnapshotTaken -> SVG resizing -> canvasResized
138                 For faster and smooth zooming, we use PinchArea with combination WebView scale property.
139                 After touch gesture release, we call resizeAfterPinch(). This take snaphost (graber component)
140                 of current WebView content (scaled) and overlap WebView.
141                 When snapshot is taken and shown (onSnapshotTaken), we invoke SVG scaling inside WebView.
142                 This operation is long (up to seconds) and not smooth. After SVG resizing is caled canvasResized()
143                 method. We currently change WebView size, return scale to 1 and remove old screenshot...
144           */
145         WebView {
146             id: wbView
147             width: canvasWidth * 3
148             height: canvasHeight * 3
149             x: -canvasWidth
150             y: -canvasHeight
151             settings.pluginsEnabled: false
153             property bool whiteboardInitialized : false;
154             property real previousX: 0
155             property real previousY: 0
157             pressGrabTime:0
159             javaScriptWindowObjects: [QtObject {
160                     WebView.windowObjectName: "connector"
161                     function getUID(){ return board.parent.sessionId; }
162                     function init() {
163                         wbView.whiteboardInitialized = true;
164                         log("html part of whiteboard is initialized! lets make big stufs...");
165                         // html part is loaded, init MaknetoWhiteboard...
166                         wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.init('+board.canvasWidth+', '+board.canvasHeight+')' );
168                         //var zoom = Math.min( wbView.width / (wbView.contentsSize.width *0.3), wbView.height / (wbView.contentsSize.height *0.3) );
169                         //wbView.evaluateJavaScript ( '$("#workArea").scrollLeft(640)');
170                         //wbView.calculateZoom();
171                         wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.setZoom('+ wbView.width / (canvasWidth*3) +')' );
173                         whiteboardMessageReceived.connect(onWhiteboardMessageReceived);
174                         toolbar.foregroundColorChanged.connect(onForegroundColorChanged);
175                         messageSent.connect(onMessageSent);
176                         whiteboardInitialiyed = true;
177                         while (queue !== undefined && queue.length !== 0){
178                             var tmp = queue;
179                             var obj = tmp.shift(); // take first element (from array begin)
180                             queue = tmp;
181                             //log("queue message: "+obj.data);
182                             onWhiteboardMessageReceived(obj.data, obj.contact);
183                         }
184                     }
185                     function onForegroundColorChanged(c){
186                         //log("onForegroundColorChanged "+c);
187                         wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.setForegroundColor("'+c+'")' );
188                     }
189                     function onWhiteboardMessageReceived(data, contact){
190                         wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.processXmlCommand('+JSON.stringify(data)+')' );
191                         //log("on whiteboard msg received from "+contact+" "+JSON.stringify(data));
192                     }
193                     function onMessageSent(content){
194                         wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.messageSent('+JSON.stringify(content)+')' );
195                     }
196                     function sendMessage(xmlMessage){
197                         //log(xmlMessage);
198                         sendWbMessage(board.parent.sessionId, xmlMessage);
199                         return true;
200                     }
201                     function enableRendering(b){
202                         wbView.renderingEnabled = b;
203                     }
204                     function canvasResized(){
205                         var originalWidth = wbView.width;
206                         var originalHeight = wbView.height;
207                         wbView.width = wbView.width * wbView.scale;
208                         wbView.height= wbView.height* wbView.scale;
209                         wbView.scale = 1;
210                         // move to new center
211                         wbView.x = wbView.x + (originalWidth - wbView.width)*.5;
212                         wbView.y = wbView.y + (originalHeight - wbView.height)*.5;
213                         wbView.previousX = wbView.x;
214                         wbView.previousY = wbView.y;
216                         log("resizeAfterPinch "+wbView.scale+" "+Math.floor(originalWidth)+"x"+Math.floor(originalHeight)+
217                             " => "+Math.floor(wbView.width)+"x"+Math.floor(wbView.height)+" ..."+Math.floor(wbView.x) +""+Math.floor(wbView.y)+"");
219                         pinchy.pinch.minimumX = Math.max( (wbView.width  / (-2/3)) , -(wbView.width - wrapper.width));
220                         pinchy.pinch.minimumY = Math.max( (wbView.height / (-2/3)) , -(wbView.height - wrapper.height));
221                         pinchy.pinch.maximumX = 0;
222                         pinchy.pinch.maximumY = 0;
224                         var minWscale = wrapper.width / wbView.width;
225                         var minHscale = wrapper.height / wbView.height;
226                         pinchy.pinch.minimumScale = Math.max(minHscale, minWscale);
227                         var maxWscale = (wrapper.width*15) / wbView.width;
228                         var maxHscale = (wrapper.height*15) / wbView.height;
229                         pinchy.pinch.maximumScale = Math.min(maxHscale, maxWscale);
231                         graber.state = "hide";
232                         //graberTimer.start();
233                     }
234                 },
235                 QtObject {
236                     WebView.windowObjectName: "console"
237                     function log(msg)   { console.log("II [Whiteboard]: " + msg); }
238                     function debug(msg) { console.log("DD [Whiteboard]: " + msg); }
239                     function warn(msg)  { console.log("WW [Whiteboard]: " + msg); }
240                     function error(msg)  { console.log("EE [Whiteboard]: " + msg); }
241                 }
242             ]
244             function resizeAfterPinch(){
246                 // TODO: SHOW waiting cursor while resizing SVG image
247                 graber.takeSnapshot();
248                 graber.state = "visible";
249             }
251             onContentsSizeChanged: {
253             }
255             Component.onCompleted: {
256                 wbView.previousX = wbView.x +15
257                 wbView.previousY = wbView.y +15
258             }
260             //html: "<html><head><script>console.log(\"This is in WebKit!\"); window.connector.qmlCall();</script></head><body><h1>Qt WebKit!</h1></body></html>"
261             url:"qrc:/whiteboard/main.html"
263         }
265         ViewGraber{
266             id: graber
267             opacity: 0
268             anchors.fill: parent
269             delegate: wbView
271             states: [
272                 State {
273                     name: "hide"
274                     PropertyChanges { target: graber; opacity: 0 }
275                 },
276                 State {
277                     name: "visible"
278                     PropertyChanges { target: graber; opacity: 1 }
279                 }
280             ]
281             transitions: [
282                 Transition {
283                     from: "hide"; to: "visible"
284                     NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 0 }
285                 },
286                 Transition {
287                     from: "visible"; to: "hide"
288                     NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 100 }
289                 }
290             ]
291             onSnapshotTaken:{
292                 log("snapshot taken");
293                 wbView.evaluateJavaScript ( 'MaknetoWhiteboard.instance.setZoom('+ wbView.width * wbView.scale / (canvasWidth*3) +')' );
294             }
295         }
298         //Rectangle { id: rect; color: "transparent"; border.color: "yellow"; x: 150; y: 150; height: 250; width: 250 }
300         /**
301           * I don't know better solution detect when resize animations stops...
302           */
303         Timer{
304             id: animationNotifier
305             interval: 300; running: false; repeat: false
306             onTriggered: {
307                 wrapper.onDimensionChanged(false);
308             }
309         }
311         // while animation we using QML scale for webView
312         function onDimensionChanged(fast){
313             if (wrapper.width < 10 || wrapper.height < 10)
314                 return;
316             if (fast){
317                 var minWscale = wrapper.width / wbView.width;
318                 var minHscale = wrapper.height / wbView.height;
319                 pinchy.pinch.minimumScale = Math.max(minHscale, minWscale);
321                 var wSc = wrapper.width / previousWidth;
322                 var hSc = wrapper.height / previousHeith;
323                 wbView.scale =  Math.max(pinchy.pinch.minimumScale, Math.min(wSc, hSc)); // FIXME: it is good?
324                 wbView.x = wbView.previousX + (wrapper.width - previousWidth) / 2
325                 wbView.y = wbView.previousY + (wrapper.height - previousHeith) / 2
327                 /*
328                 log("fast scale to "+wbView.scale+"");
329                 log("       "+Math.floor( wbView.previousX)+" + ("+Math.floor(wrapper.width)+" - "+Math.floor(previousWidth)+") / 2) = "+Math.floor( wbView.x)+"");
330                 log("       "+Math.floor( wbView.previousY)+" + ("+Math.floor(wrapper.height)+" - "+Math.floor(previousHeith)+") / 2) = "+Math.floor( wbView.y)+"");
331                 */
333                 animationNotifier.stop();
334                 animationNotifier.start();
335             }else{
336                 wbView.resizeAfterPinch();
337                 previousWidth = wrapper.width;
338                 previousHeith = wrapper.height
339             }
340         }
342         onWidthChanged: {
343             onDimensionChanged(true);
344         }
345         onHeightChanged: {
346             onDimensionChanged(true);
347         }
349         PinchArea {
350             id: pinchy
351             enabled: true
352             pinch.target: wbView
353             anchors.fill: parent
354             pinch.dragAxis: Pinch.XandYAxis
355             pinch.minimumScale: 1
356             pinch.maximumScale: 1
357             pinch.minimumX: -canvasWidth*2
358             pinch.maximumX: 0
359             pinch.minimumY: -canvasHeight*2
360             pinch.maximumY: 0
361             //pinch.minimumRotation: -150
362             //pinch.maximumRotation: 150
364             onPinchStarted: {
365                 log("onPinchStarted");
366                 pressDelay.stop();
367                 wbView.evaluateJavaScript("MaknetoWhiteboard.instance.whiteboard.mouseUp2()");
368             }
369             onPinchUpdated: {                
370             }
371             onPinchFinished: {
372                 wbView.resizeAfterPinch();                
373             }
374         }
376         /**
377           * delayed mouse down event is workaround for late detected gestures.
378           */
379         Timer{
380             id: pressDelay
381             property int mouseX;
382             property int mouseY;
383             interval: 200; running: false; repeat: false
384             onTriggered: {
385                 log("delayed mouseDown");
386                 wbView.evaluateJavaScript("MaknetoWhiteboard.instance.whiteboard.mouseDown2("+(mouseX-wbView.x)+", "+(mouseY-wbView.y)+",0)");
387             }
388         }
389         MouseArea {
390             id: mouseArea
391             anchors.fill: parent
392             acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
394             property int startX;
395             property int startY;
396             property int startWbX;
397             property int startWbY;
398             property variant pressed;
400             onPressed:{
401                 //log("mouseArea: onPressed");
402                 //mouse.accepted = false;
403                 startX = mouse.x;
404                 startY = mouse.y;
405                 startWbX = wbView.x;
406                 startWbY = wbView.y;
407                 pressed = mouse.button;
409                 if (mouse.button == Qt.LeftButton){
410                     log("left button");
411                     pressDelay.mouseX = mouse.x;
412                     pressDelay.mouseY = mouse.y;
413                     pressDelay.start();
414                     //wbView.evaluateJavaScript("MaknetoWhiteboard.instance.whiteboard.mouseDown2("+(mouseX-wbView.x)+", "+(mouseY-wbView.y)+",0)");
415                     return;
416                 }
417                 pressDelay.stop();
418             }
419             onDoubleClicked:{
420                 //log("mouseArea: onDoubleClicked");
421             }
422             onPositionChanged:{
423                 //log("mouseArea: onPositionChanged");
424             }
425             onPressAndHold:{
426                 //log("mouseArea: onPressAndHold");                
427             }
428             onReleased :{
429                 //log("mouseArea: onReleased");
430                 pressDelay.stop();
431                 wbView.evaluateJavaScript("MaknetoWhiteboard.instance.whiteboard.mouseUp2()");
432             }
433             onMousePositionChanged: {
434                 //pressDelay.stop();
435                 //log("mouseArea: onMousePositionChanged");
436                 if (pressed == Qt.LeftButton){
437                     //log("      ... paint");
438                     wbView.evaluateJavaScript("MaknetoWhiteboard.instance.whiteboard.mouseMove2("+(mouse.x-wbView.x)+", "+(mouse.y-wbView.y)+")");
439                     return;
440                 }
442                 if (pressed == Qt.RightButton || pressed == Qt.MiddleButton){
443                     pressDelay.stop();
445                     wbView.x = (mouse.x - startX) + startWbX;
446                     wbView.y = (mouse.y - startY) + startWbY;
447                     if (wbView.x < pinchy.pinch.minimumX)
448                         wbView.x = pinchy.pinch.minimumX;
449                     if (wbView.y < pinchy.pinch.minimumY)
450                         wbView.y = pinchy.pinch.minimumY;
451                     if (wbView.x > pinchy.pinch.maximumX)
452                         wbView.x = pinchy.pinch.maximumX;
453                     if (wbView.y > pinchy.pinch.maximumY)
454                         wbView.y = pinchy.pinch.maximumY;
455                     //log("       ... move "+wbView.x+" "+pinchy.pinch.minimumX);
456                     return;
457                 }
458             }
459         }
460         // we use wheel for zooming on desktop...
461         WheelArea {
462             anchors.fill: parent
463             onVerticalWheel: {
464                 //console.log("Vertical Wheel: " + delta)
465                 var scaleRange = pinchy.pinch.maximumScale - pinchy.pinch.minimumScale;
467                 wbView.scale += scaleRange * (delta / 5000);
468                 if (wbView.scale > pinchy.pinch.maximumScale)
469                     wbView.scale = pinchy.pinch.maximumScale;
470                 if (wbView.scale < pinchy.pinch.minimumScale)
471                     wbView.scale = pinchy.pinch.minimumScale;
472                 // FIXME: adjust position when we zoom out
474                 animationNotifier.stop();
475                 animationNotifier.start();
476             }
477             onHorizontalWheel: {
478                 //console.log("Horizontal Wheel: " + delta)
479             }
480         }
481     }
482     BoardToolbar{
483         id: toolbar
484         color: parent.color
485         anchors{bottom: parent.bottom; right: parent.right; top: parent.top}
486         BorderImage { source: "img/lineedit.sci"; anchors.fill: parent }
487         width: 60
488     }
490     onWidthChanged:{
491         wbView.renderingEnabled = false;
492         wbView.renderingEnabled = true;
493     }
494     onHeightChanged:{
495         wbView.renderingEnabled = false;
496         wbView.renderingEnabled = true;
497     }