blob: ba9d59f8b586cfa150b7bdc02ea83ffe77a50e59 [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>
Alexandre Lisionc5148052015-03-04 15:10:35 -050041#import <video/previewmanager.h>
42#import <video/renderer.h>
43
Alexandre Lision74dd47f2015-04-14 13:47:42 -040044#import "views/CallView.h"
45
Alexandre Lisionc5148052015-03-04 15:10:35 -050046/** FrameReceiver class - delegate for AVCaptureSession
47 */
48@interface RendererConnectionsHolder : NSObject
49
50@property QMetaObject::Connection frameUpdated;
51@property QMetaObject::Connection started;
52@property QMetaObject::Connection stopped;
53
54@end
55
56@implementation RendererConnectionsHolder
57
58@end
59
60@interface CurrentCallVC ()
61
62@property (assign) IBOutlet NSTextField *personLabel;
63@property (assign) IBOutlet NSTextField *stateLabel;
64@property (assign) IBOutlet NSButton *holdOnOffButton;
65@property (assign) IBOutlet NSButton *hangUpButton;
66@property (assign) IBOutlet NSButton *recordOnOffButton;
67@property (assign) IBOutlet NSButton *pickUpButton;
68@property (assign) IBOutlet NSTextField *timeSpentLabel;
69@property (assign) IBOutlet NSView *controlsPanel;
70
71@property QHash<int, NSButton*> actionHash;
72
73// Video
Alexandre Lision74dd47f2015-04-14 13:47:42 -040074@property (assign) IBOutlet CallView *videoView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050075@property CALayer* videoLayer;
76@property (assign) IBOutlet NSView *previewView;
77@property CALayer* previewLayer;
78
79@property RendererConnectionsHolder* previewHolder;
80@property RendererConnectionsHolder* videoHolder;
Alexandre Lisionef6333a2015-03-24 12:30:31 -040081@property QMetaObject::Connection videoStarted;
Alexandre Lisionc5148052015-03-04 15:10:35 -050082
83@end
84
85@implementation CurrentCallVC
86@synthesize personLabel;
87@synthesize actionHash;
88@synthesize stateLabel;
89@synthesize holdOnOffButton;
90@synthesize hangUpButton;
91@synthesize recordOnOffButton;
92@synthesize pickUpButton;
93@synthesize timeSpentLabel;
94@synthesize controlsPanel;
95@synthesize videoView;
96@synthesize videoLayer;
97@synthesize previewLayer;
98@synthesize previewView;
99
100@synthesize previewHolder;
101@synthesize videoHolder;
102
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400103- (void) updateAllActions
Alexandre Lisionc5148052015-03-04 15:10:35 -0500104{
105 for(int i = 0 ; i <= CallModel::instance()->userActionModel()->rowCount() ; i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400106 [self updateActionAtIndex:i];
107 }
108}
109
110- (void) updateActionAtIndex:(int) row
111{
112 const QModelIndex& idx = CallModel::instance()->userActionModel()->index(row,0);
113 UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
114 NSButton* a = actionHash[(int) action];
115 if (a != nil) {
116 [a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
117 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
118
119 if(action == UserActionModel::Action::HOLD) {
120 [a setTitle:(a.state == NSOnState ? @"Hold off" : @"Hold")];
121 }
122 if(action == UserActionModel::Action::RECORD) {
123 [a setTitle:(a.state == NSOnState ? @"Record off" : @"Record")];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500124 }
125 }
126}
127
128-(void) updateCall
129{
130 QModelIndex callIdx = CallModel::instance()->selectionModel()->currentIndex();
131 [personLabel setStringValue:CallModel::instance()->data(callIdx, Qt::DisplayRole).toString().toNSString()];
132 [timeSpentLabel setStringValue:CallModel::instance()->data(callIdx, (int)Call::Role::Length).toString().toNSString()];
133
134 Call::State state = CallModel::instance()->data(callIdx, (int)Call::Role::State).value<Call::State>();
135
136 switch (state) {
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400137 case Call::State::DIALING:
138 [stateLabel setStringValue:@"Dialing"];
139 break;
140 case Call::State::NEW:
141 [stateLabel setStringValue:@"New"];
142 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500143 case Call::State::INITIALIZATION:
144 [stateLabel setStringValue:@"Initializing"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400145 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500146 break;
147 case Call::State::RINGING:
148 [stateLabel setStringValue:@"Ringing"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400149 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500150 break;
151 case Call::State::CURRENT:
152 [stateLabel setStringValue:@"Current"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400153 [videoView setShouldAcceptInteractions:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500154 break;
155 case Call::State::HOLD:
156 [stateLabel setStringValue:@"On Hold"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400157 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500158 break;
159 case Call::State::BUSY:
160 [stateLabel setStringValue:@"Busy"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400161 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500162 break;
163 case Call::State::OVER:
164 [stateLabel setStringValue:@"Finished"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400165 [videoView setShouldAcceptInteractions:NO];
166 if(videoView.isInFullScreenMode)
167 [videoView exitFullScreenModeWithOptions:nil];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500168 break;
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400169 case Call::State::ABORTED:
170 [stateLabel setStringValue:@"Aborted"];
171 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500172 case Call::State::FAILURE:
173 [stateLabel setStringValue:@"Failure"];
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400174 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500175 break;
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400176 case Call::State::INCOMING:
177 [stateLabel setStringValue:@"Incoming"];
178 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500179 default:
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400180 [stateLabel setStringValue:@""];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500181 break;
182 }
183
184}
185
186- (void)awakeFromNib
187{
188 NSLog(@"INIT CurrentCall VC");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400189 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500190 [self.view setLayer:[CALayer layer]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500191
192 [controlsPanel setWantsLayer:YES];
193 [controlsPanel setLayer:[CALayer layer]];
194 [controlsPanel.layer setZPosition:2.0];
195 [controlsPanel.layer setBackgroundColor:[NSColor whiteColor].CGColor];
196
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400197 actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
198 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
199 actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
200 actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500201
Alexandre Lisionc5148052015-03-04 15:10:35 -0500202 videoLayer = [CALayer layer];
203 [videoView setWantsLayer:YES];
204 [videoView setLayer:videoLayer];
205 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
206 [videoView.layer setFrame:videoView.frame];
207 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500208
209 previewLayer = [CALayer layer];
210 [previewView setWantsLayer:YES];
211 [previewView setLayer:previewLayer];
212 [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400213 [previewLayer setContentsGravity:kCAGravityResizeAspectFill];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500214 [previewLayer setFrame:previewView.frame];
215
216 [controlsPanel setWantsLayer:YES];
217 [controlsPanel setLayer:[CALayer layer]];
218 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
219 [controlsPanel.layer setFrame:controlsPanel.frame];
220
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400221 previewHolder = [[RendererConnectionsHolder alloc] init];
222 videoHolder = [[RendererConnectionsHolder alloc] init];
223
Alexandre Lisionc5148052015-03-04 15:10:35 -0500224 [self connect];
225}
226
227- (void) connect
228{
229 QObject::connect(CallModel::instance()->selectionModel(),
230 &QItemSelectionModel::currentChanged,
231 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500232 if(!current.isValid()) {
233 [self animateOut];
234 return;
235 }
236 [self updateCall];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400237 [self updateAllActions];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500238 [self animateOut];
239 });
240
241 QObject::connect(CallModel::instance(),
242 &QAbstractItemModel::dataChanged,
243 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500244 [self updateCall];
245 });
246
247 QObject::connect(CallModel::instance()->userActionModel(),
248 &QAbstractItemModel::dataChanged,
249 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500250 const int first(topLeft.row()),last(bottomRight.row());
251 for(int i = first; i <= last;i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400252 [self updateActionAtIndex:i];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500253 }
254 });
255
256 QObject::connect(CallModel::instance(),
257 &CallModel::callStateChanged,
258 [self](Call* c, Call::State state) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500259 [self updateCall];
260 });
261}
262
263-(void) connectVideoSignals
264{
265 QModelIndex idx = CallModel::instance()->selectionModel()->currentIndex();
266 Call* call = CallModel::instance()->getCall(idx);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500267 QObject::connect(call,
268 &Call::videoStarted,
269 [=](Video::Renderer* renderer) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400270 NSLog(@"Video started!");
271 QObject::disconnect(self.videoStarted);
272 [self connectVideoRenderer:renderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500273 });
274
275 if(call->videoRenderer())
276 {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500277 [self connectVideoRenderer:call->videoRenderer()];
278 }
279
280 [self connectPreviewRenderer];
281
282}
283
284-(void) connectPreviewRenderer
285{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400286 QObject::disconnect(previewHolder.frameUpdated);
287 QObject::disconnect(previewHolder.stopped);
288 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500289 previewHolder.started = QObject::connect(Video::PreviewManager::instance(),
290 &Video::PreviewManager::previewStarted,
291 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500292 QObject::disconnect(previewHolder.frameUpdated);
293 previewHolder.frameUpdated = QObject::connect(renderer,
294 &Video::Renderer::frameUpdated,
295 [=]() {
296 [self renderer:Video::PreviewManager::instance()->previewRenderer()
297 renderFrameForView:previewView];
298 });
299 });
300
301 previewHolder.stopped = QObject::connect(Video::PreviewManager::instance(),
302 &Video::PreviewManager::previewStopped,
303 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500304 QObject::disconnect(previewHolder.frameUpdated);
305 [previewView.layer setContents:nil];
306 });
307
308 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance()->previewRenderer(),
309 &Video::Renderer::frameUpdated,
310 [=]() {
311 [self renderer:Video::PreviewManager::instance()->previewRenderer()
312 renderFrameForView:previewView];
313 });
314}
315
316-(void) connectVideoRenderer: (Video::Renderer*)renderer
317{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400318 QObject::disconnect(videoHolder.frameUpdated);
319 QObject::disconnect(videoHolder.started);
320 QObject::disconnect(videoHolder.stopped);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500321 videoHolder.frameUpdated = QObject::connect(renderer,
322 &Video::Renderer::frameUpdated,
323 [=]() {
324 [self renderer:renderer renderFrameForView:videoView];
325 });
326
327 videoHolder.started = QObject::connect(renderer,
328 &Video::Renderer::started,
329 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500330 QObject::disconnect(videoHolder.frameUpdated);
331 videoHolder.frameUpdated = QObject::connect(renderer,
332 &Video::Renderer::frameUpdated,
333 [=]() {
334 [self renderer:renderer renderFrameForView:videoView];
335 });
336 });
337
338 videoHolder.stopped = QObject::connect(renderer,
339 &Video::Renderer::stopped,
340 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500341 QObject::disconnect(videoHolder.frameUpdated);
342 [videoView.layer setContents:nil];
343 });
344}
345
346-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
347{
348 const QByteArray& data = renderer->currentFrame();
349 QSize res = renderer->size();
350
351 auto buf = reinterpret_cast<const unsigned char*>(data.data());
352
353 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
354 CGContextRef newContext = CGBitmapContextCreate((void *)buf,
355 res.width(),
356 res.height(),
357 8,
358 4*res.width(),
359 colorSpace,
360 kCGImageAlphaPremultipliedLast);
361
362
363 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
364
365 /*We release some components*/
366 CGContextRelease(newContext);
367 CGColorSpaceRelease(colorSpace);
368
369 [CATransaction begin];
370 view.layer.contents = (__bridge id)newImage;
371 [CATransaction commit];
372
373 CFRelease(newImage);
374}
375
376- (void) initFrame
377{
378 [self.view setFrame:self.view.superview.bounds];
379 [self.view setHidden:YES];
380 self.view.layer.position = self.view.frame.origin;
381}
382
383# pragma private IN/OUT animations
384
385-(void) animateIn
386{
387 NSLog(@"animateIn");
388 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
389 [self.view setHidden:NO];
390
391 [CATransaction begin];
392 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
393 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
394 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
395 [animation setDuration:0.2f];
396 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
397 [CATransaction setCompletionBlock:^{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500398 [self connectVideoSignals];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500399 }];
400 [self.view.layer addAnimation:animation forKey:animation.keyPath];
401
402 [CATransaction commit];
403}
404
405-(void) cleanUp
406{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400407 QObject::disconnect(videoHolder.frameUpdated);
408 QObject::disconnect(videoHolder.started);
409 QObject::disconnect(videoHolder.stopped);
410 QObject::disconnect(previewHolder.frameUpdated);
411 QObject::disconnect(previewHolder.stopped);
412 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500413 [videoView.layer setContents:nil];
414 [previewView.layer setContents:nil];
415}
416
417-(void) animateOut
418{
419 NSLog(@"animateOut");
420 if(self.view.frame.origin.x < 0) {
421 NSLog(@"Already hidden");
422 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
423 [self animateIn];
424 }
425 return;
426 }
427
428 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
429 [CATransaction begin];
430 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
431 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
432 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
433 [animation setDuration:0.2f];
434 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
435
436 [CATransaction setCompletionBlock:^{
437 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400438 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500439 [self cleanUp];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500440 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
441 [self animateIn];
442 }
443 }];
444 [self.view.layer addAnimation:animation forKey:animation.keyPath];
445 [CATransaction commit];
446}
447
448/**
449 * Debug purpose
450 */
451-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
452{
453 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
454}
455
456
457#pragma button methods
458- (IBAction)hangUp:(id)sender {
459 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::REFUSE;
460}
461
462- (IBAction)accept:(id)sender {
463 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::ACCEPT;
464}
465
466- (IBAction)toggleRecording:(id)sender {
467 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::RECORD;
468}
469
470- (IBAction)toggleHold:(id)sender {
471 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::HOLD;
472}
473
474@end