blob: 42b5729801e792369d4cb1c4bab343f3110aeda3 [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
2 * Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30#import "CurrentCallVC.h"
31
32#import <QuartzCore/QuartzCore.h>
33
34#import <call.h>
35#import <callmodel.h>
36#import <useractionmodel.h>
37#import <contactmethod.h>
38#import <qabstractitemmodel.h>
39#import <QItemSelectionModel>
40#import <QItemSelection>
41
42#import <video/previewmanager.h>
43#import <video/renderer.h>
44
45/** FrameReceiver class - delegate for AVCaptureSession
46 */
47@interface RendererConnectionsHolder : NSObject
48
49@property QMetaObject::Connection frameUpdated;
50@property QMetaObject::Connection started;
51@property QMetaObject::Connection stopped;
52
53@end
54
55@implementation RendererConnectionsHolder
56
57@end
58
59@interface CurrentCallVC ()
60
61@property (assign) IBOutlet NSTextField *personLabel;
62@property (assign) IBOutlet NSTextField *stateLabel;
63@property (assign) IBOutlet NSButton *holdOnOffButton;
64@property (assign) IBOutlet NSButton *hangUpButton;
65@property (assign) IBOutlet NSButton *recordOnOffButton;
66@property (assign) IBOutlet NSButton *pickUpButton;
67@property (assign) IBOutlet NSTextField *timeSpentLabel;
68@property (assign) IBOutlet NSView *controlsPanel;
69
70@property QHash<int, NSButton*> actionHash;
71
72// Video
73@property (assign) IBOutlet NSView *videoView;
74@property CALayer* videoLayer;
75@property (assign) IBOutlet NSView *previewView;
76@property CALayer* previewLayer;
77
78@property RendererConnectionsHolder* previewHolder;
79@property RendererConnectionsHolder* videoHolder;
80
81@end
82
83@implementation CurrentCallVC
84@synthesize personLabel;
85@synthesize actionHash;
86@synthesize stateLabel;
87@synthesize holdOnOffButton;
88@synthesize hangUpButton;
89@synthesize recordOnOffButton;
90@synthesize pickUpButton;
91@synthesize timeSpentLabel;
92@synthesize controlsPanel;
93@synthesize videoView;
94@synthesize videoLayer;
95@synthesize previewLayer;
96@synthesize previewView;
97
98@synthesize previewHolder;
99@synthesize videoHolder;
100
Alexandre Lisionc5148052015-03-04 15:10:35 -0500101- (void) updateActions
102{
103 for(int i = 0 ; i <= CallModel::instance()->userActionModel()->rowCount() ; i++) {
104 const QModelIndex& idx = CallModel::instance()->userActionModel()->index(i,0);
105 NSButton* a = actionHash[(int)qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION))];
106 if (a != nil) {
107 [a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
108 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
109 }
110 }
111}
112
113-(void) updateCall
114{
115 QModelIndex callIdx = CallModel::instance()->selectionModel()->currentIndex();
116 [personLabel setStringValue:CallModel::instance()->data(callIdx, Qt::DisplayRole).toString().toNSString()];
117 [timeSpentLabel setStringValue:CallModel::instance()->data(callIdx, (int)Call::Role::Length).toString().toNSString()];
118
119 Call::State state = CallModel::instance()->data(callIdx, (int)Call::Role::State).value<Call::State>();
120
121 switch (state) {
122 case Call::State::INITIALIZATION:
123 [stateLabel setStringValue:@"Initializing"];
124 break;
125 case Call::State::RINGING:
126 [stateLabel setStringValue:@"Ringing"];
127 break;
128 case Call::State::CURRENT:
129 [stateLabel setStringValue:@"Current"];
130 break;
131 case Call::State::HOLD:
132 [stateLabel setStringValue:@"On Hold"];
133 break;
134 case Call::State::BUSY:
135 [stateLabel setStringValue:@"Busy"];
136 break;
137 case Call::State::OVER:
138 [stateLabel setStringValue:@"Finished"];
139 break;
140 case Call::State::FAILURE:
141 [stateLabel setStringValue:@"Failure"];
142 break;
143 default:
144 break;
145 }
146
147}
148
149- (void)awakeFromNib
150{
151 NSLog(@"INIT CurrentCall VC");
152 [self.view setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
153 [self.view setLayer:[CALayer layer]];
154 //self.view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
155
156
157 [controlsPanel setWantsLayer:YES];
158 [controlsPanel setLayer:[CALayer layer]];
159 [controlsPanel.layer setZPosition:2.0];
160 [controlsPanel.layer setBackgroundColor:[NSColor whiteColor].CGColor];
161
162 actionHash[ (int)UserActionModel::Action::ACCEPT ] = pickUpButton;
163 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
164 actionHash[ (int)UserActionModel::Action::RECORD ] = recordOnOffButton;
165 actionHash[ (int)UserActionModel::Action::HANGUP ] = hangUpButton;
166 //actionHash[ (int)UserActionModel::Action::MUTE_AUDIO ] = action_mute_capture;
167 //actionHash[ (int)UserActionModel::Action::SERVER_TRANSFER ] = action_transfer;
168
Alexandre Lisionc5148052015-03-04 15:10:35 -0500169 videoLayer = [CALayer layer];
170 [videoView setWantsLayer:YES];
171 [videoView setLayer:videoLayer];
172 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
173 [videoView.layer setFrame:videoView.frame];
174 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
175 //[videoView.layer setBounds:CGRectMake(0, 0, videoView.frame.size.width, videoView.frame.size.height)];
176
177 previewLayer = [CALayer layer];
178 [previewView setWantsLayer:YES];
179 [previewView setLayer:previewLayer];
180 [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
181 [previewLayer setContentsGravity:kCAGravityResizeAspect];
182 [previewLayer setFrame:previewView.frame];
183
184 [controlsPanel setWantsLayer:YES];
185 [controlsPanel setLayer:[CALayer layer]];
186 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
187 [controlsPanel.layer setFrame:controlsPanel.frame];
188
189 [self connect];
190}
191
192- (void) connect
193{
194 QObject::connect(CallModel::instance()->selectionModel(),
195 &QItemSelectionModel::currentChanged,
196 [=](const QModelIndex &current, const QModelIndex &previous) {
197 NSLog(@"selection changed!");
198 if(!current.isValid()) {
199 [self animateOut];
200 return;
201 }
202 [self updateCall];
203 [self updateActions];
204 [self animateOut];
205 });
206
207 QObject::connect(CallModel::instance(),
208 &QAbstractItemModel::dataChanged,
209 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
210 NSLog(@"data changed!");
211 [self updateCall];
212 });
213
214 QObject::connect(CallModel::instance()->userActionModel(),
215 &QAbstractItemModel::dataChanged,
216 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
217 NSLog(@"useraction changed");
218 const int first(topLeft.row()),last(bottomRight.row());
219 for(int i = first; i <= last;i++) {
220 const QModelIndex& idx = CallModel::instance()->userActionModel()->index(i,0);
221 NSButton* a = actionHash[(int)qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION))];
222 if (a) {
223 [a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
224 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
225 }
226 }
227 });
228
229 QObject::connect(CallModel::instance(),
230 &CallModel::callStateChanged,
231 [self](Call* c, Call::State state) {
232 NSLog(@"callStateChanged");
233 [self updateCall];
234 });
235}
236
237-(void) connectVideoSignals
238{
239 QModelIndex idx = CallModel::instance()->selectionModel()->currentIndex();
240 Call* call = CallModel::instance()->getCall(idx);
241
242 QObject::connect(call,
243 &Call::videoStarted,
244 [=](Video::Renderer* renderer) {
245 NSLog(@"Video started!");
246 [self connectVideoRenderer:renderer];
247 });
248
249 if(call->videoRenderer())
250 {
251 NSLog(@"GONNA CONNECT TO FRAMES");
252 [self connectVideoRenderer:call->videoRenderer()];
253 }
254
255 [self connectPreviewRenderer];
256
257}
258
259-(void) connectPreviewRenderer
260{
261 previewHolder.started = QObject::connect(Video::PreviewManager::instance(),
262 &Video::PreviewManager::previewStarted,
263 [=](Video::Renderer* renderer) {
264 NSLog(@"Preview started");
265 QObject::disconnect(previewHolder.frameUpdated);
266 previewHolder.frameUpdated = QObject::connect(renderer,
267 &Video::Renderer::frameUpdated,
268 [=]() {
269 [self renderer:Video::PreviewManager::instance()->previewRenderer()
270 renderFrameForView:previewView];
271 });
272 });
273
274 previewHolder.stopped = QObject::connect(Video::PreviewManager::instance(),
275 &Video::PreviewManager::previewStopped,
276 [=](Video::Renderer* renderer) {
277 NSLog(@"Preview stopped");
278 QObject::disconnect(previewHolder.frameUpdated);
279 [previewView.layer setContents:nil];
280 });
281
282 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance()->previewRenderer(),
283 &Video::Renderer::frameUpdated,
284 [=]() {
285 [self renderer:Video::PreviewManager::instance()->previewRenderer()
286 renderFrameForView:previewView];
287 });
288}
289
290-(void) connectVideoRenderer: (Video::Renderer*)renderer
291{
292 videoHolder.frameUpdated = QObject::connect(renderer,
293 &Video::Renderer::frameUpdated,
294 [=]() {
295 [self renderer:renderer renderFrameForView:videoView];
296 });
297
298 videoHolder.started = QObject::connect(renderer,
299 &Video::Renderer::started,
300 [=]() {
301 NSLog(@"Renderer started");
302 QObject::disconnect(videoHolder.frameUpdated);
303 videoHolder.frameUpdated = QObject::connect(renderer,
304 &Video::Renderer::frameUpdated,
305 [=]() {
306 [self renderer:renderer renderFrameForView:videoView];
307 });
308 });
309
310 videoHolder.stopped = QObject::connect(renderer,
311 &Video::Renderer::stopped,
312 [=]() {
313 NSLog(@"Renderer stopped");
314 QObject::disconnect(videoHolder.frameUpdated);
315 [videoView.layer setContents:nil];
316 });
317}
318
319-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
320{
321 const QByteArray& data = renderer->currentFrame();
322 QSize res = renderer->size();
323
324 auto buf = reinterpret_cast<const unsigned char*>(data.data());
325
326 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
327 CGContextRef newContext = CGBitmapContextCreate((void *)buf,
328 res.width(),
329 res.height(),
330 8,
331 4*res.width(),
332 colorSpace,
333 kCGImageAlphaPremultipliedLast);
334
335
336 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
337
338 /*We release some components*/
339 CGContextRelease(newContext);
340 CGColorSpaceRelease(colorSpace);
341
342 [CATransaction begin];
343 view.layer.contents = (__bridge id)newImage;
344 [CATransaction commit];
345
346 CFRelease(newImage);
347}
348
349- (void) initFrame
350{
351 [self.view setFrame:self.view.superview.bounds];
352 [self.view setHidden:YES];
353 self.view.layer.position = self.view.frame.origin;
354}
355
356# pragma private IN/OUT animations
357
358-(void) animateIn
359{
360 NSLog(@"animateIn");
361 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
362 [self.view setHidden:NO];
363
364 [CATransaction begin];
365 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
366 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
367 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
368 [animation setDuration:0.2f];
369 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
370 [CATransaction setCompletionBlock:^{
371 NSLog(@"COMPLETION IN");
372
373 [self connectVideoSignals];
374
375 }];
376 [self.view.layer addAnimation:animation forKey:animation.keyPath];
377
378 [CATransaction commit];
379}
380
381-(void) cleanUp
382{
383 [videoView.layer setContents:nil];
384 [previewView.layer setContents:nil];
385}
386
387-(void) animateOut
388{
389 NSLog(@"animateOut");
390 if(self.view.frame.origin.x < 0) {
391 NSLog(@"Already hidden");
392 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
393 [self animateIn];
394 }
395 return;
396 }
397
398 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
399 [CATransaction begin];
400 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
401 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
402 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
403 [animation setDuration:0.2f];
404 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
405
406 [CATransaction setCompletionBlock:^{
407 [self.view setHidden:YES];
408
409 [self cleanUp];
410
411 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
412 [self animateIn];
413 }
414 }];
415 [self.view.layer addAnimation:animation forKey:animation.keyPath];
416 [CATransaction commit];
417}
418
419/**
420 * Debug purpose
421 */
422-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
423{
424 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
425}
426
427
428#pragma button methods
429- (IBAction)hangUp:(id)sender {
430 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::REFUSE;
431}
432
433- (IBAction)accept:(id)sender {
434 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::ACCEPT;
435}
436
437- (IBAction)toggleRecording:(id)sender {
438 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::RECORD;
439}
440
441- (IBAction)toggleHold:(id)sender {
442 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::HOLD;
443}
444
445@end