blob: 337aa23d2aa7c0f56ec8c9ed179690c0b8dfabba [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;
Alexandre Lisionef6333a2015-03-24 12:30:31 -040080@property QMetaObject::Connection videoStarted;
Alexandre Lisionc5148052015-03-04 15:10:35 -050081
82@end
83
84@implementation CurrentCallVC
85@synthesize personLabel;
86@synthesize actionHash;
87@synthesize stateLabel;
88@synthesize holdOnOffButton;
89@synthesize hangUpButton;
90@synthesize recordOnOffButton;
91@synthesize pickUpButton;
92@synthesize timeSpentLabel;
93@synthesize controlsPanel;
94@synthesize videoView;
95@synthesize videoLayer;
96@synthesize previewLayer;
97@synthesize previewView;
98
99@synthesize previewHolder;
100@synthesize videoHolder;
101
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400102- (void) updateAllActions
Alexandre Lisionc5148052015-03-04 15:10:35 -0500103{
104 for(int i = 0 ; i <= CallModel::instance()->userActionModel()->rowCount() ; i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400105 [self updateActionAtIndex:i];
106 }
107}
108
109- (void) updateActionAtIndex:(int) row
110{
111 const QModelIndex& idx = CallModel::instance()->userActionModel()->index(row,0);
112 UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
113 NSButton* a = actionHash[(int) action];
114 if (a != nil) {
115 [a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
116 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
117
118 if(action == UserActionModel::Action::HOLD) {
119 [a setTitle:(a.state == NSOnState ? @"Hold off" : @"Hold")];
120 }
121 if(action == UserActionModel::Action::RECORD) {
122 [a setTitle:(a.state == NSOnState ? @"Record off" : @"Record")];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500123 }
124 }
125}
126
127-(void) updateCall
128{
129 QModelIndex callIdx = CallModel::instance()->selectionModel()->currentIndex();
130 [personLabel setStringValue:CallModel::instance()->data(callIdx, Qt::DisplayRole).toString().toNSString()];
131 [timeSpentLabel setStringValue:CallModel::instance()->data(callIdx, (int)Call::Role::Length).toString().toNSString()];
132
133 Call::State state = CallModel::instance()->data(callIdx, (int)Call::Role::State).value<Call::State>();
134
135 switch (state) {
136 case Call::State::INITIALIZATION:
137 [stateLabel setStringValue:@"Initializing"];
138 break;
139 case Call::State::RINGING:
140 [stateLabel setStringValue:@"Ringing"];
141 break;
142 case Call::State::CURRENT:
143 [stateLabel setStringValue:@"Current"];
144 break;
145 case Call::State::HOLD:
146 [stateLabel setStringValue:@"On Hold"];
147 break;
148 case Call::State::BUSY:
149 [stateLabel setStringValue:@"Busy"];
150 break;
151 case Call::State::OVER:
152 [stateLabel setStringValue:@"Finished"];
153 break;
154 case Call::State::FAILURE:
155 [stateLabel setStringValue:@"Failure"];
156 break;
157 default:
158 break;
159 }
160
161}
162
163- (void)awakeFromNib
164{
165 NSLog(@"INIT CurrentCall VC");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400166 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500167 [self.view setLayer:[CALayer layer]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500168
169 [controlsPanel setWantsLayer:YES];
170 [controlsPanel setLayer:[CALayer layer]];
171 [controlsPanel.layer setZPosition:2.0];
172 [controlsPanel.layer setBackgroundColor:[NSColor whiteColor].CGColor];
173
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400174 actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
175 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
176 actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
177 actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500178
Alexandre Lisionc5148052015-03-04 15:10:35 -0500179 videoLayer = [CALayer layer];
180 [videoView setWantsLayer:YES];
181 [videoView setLayer:videoLayer];
182 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
183 [videoView.layer setFrame:videoView.frame];
184 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500185
186 previewLayer = [CALayer layer];
187 [previewView setWantsLayer:YES];
188 [previewView setLayer:previewLayer];
189 [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400190 [previewLayer setContentsGravity:kCAGravityResizeAspectFill];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500191 [previewLayer setFrame:previewView.frame];
192
193 [controlsPanel setWantsLayer:YES];
194 [controlsPanel setLayer:[CALayer layer]];
195 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
196 [controlsPanel.layer setFrame:controlsPanel.frame];
197
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400198 previewHolder = [[RendererConnectionsHolder alloc] init];
199 videoHolder = [[RendererConnectionsHolder alloc] init];
200
Alexandre Lisionc5148052015-03-04 15:10:35 -0500201 [self connect];
202}
203
204- (void) connect
205{
206 QObject::connect(CallModel::instance()->selectionModel(),
207 &QItemSelectionModel::currentChanged,
208 [=](const QModelIndex &current, const QModelIndex &previous) {
209 NSLog(@"selection changed!");
210 if(!current.isValid()) {
211 [self animateOut];
212 return;
213 }
214 [self updateCall];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400215 [self updateAllActions];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500216 [self animateOut];
217 });
218
219 QObject::connect(CallModel::instance(),
220 &QAbstractItemModel::dataChanged,
221 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
222 NSLog(@"data changed!");
223 [self updateCall];
224 });
225
226 QObject::connect(CallModel::instance()->userActionModel(),
227 &QAbstractItemModel::dataChanged,
228 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
229 NSLog(@"useraction changed");
230 const int first(topLeft.row()),last(bottomRight.row());
231 for(int i = first; i <= last;i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400232 [self updateActionAtIndex:i];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500233 }
234 });
235
236 QObject::connect(CallModel::instance(),
237 &CallModel::callStateChanged,
238 [self](Call* c, Call::State state) {
239 NSLog(@"callStateChanged");
240 [self updateCall];
241 });
242}
243
244-(void) connectVideoSignals
245{
246 QModelIndex idx = CallModel::instance()->selectionModel()->currentIndex();
247 Call* call = CallModel::instance()->getCall(idx);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500248 QObject::connect(call,
249 &Call::videoStarted,
250 [=](Video::Renderer* renderer) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400251 NSLog(@"Video started!");
252 QObject::disconnect(self.videoStarted);
253 [self connectVideoRenderer:renderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500254 });
255
256 if(call->videoRenderer())
257 {
258 NSLog(@"GONNA CONNECT TO FRAMES");
259 [self connectVideoRenderer:call->videoRenderer()];
260 }
261
262 [self connectPreviewRenderer];
263
264}
265
266-(void) connectPreviewRenderer
267{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400268 QObject::disconnect(previewHolder.frameUpdated);
269 QObject::disconnect(previewHolder.stopped);
270 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500271 previewHolder.started = QObject::connect(Video::PreviewManager::instance(),
272 &Video::PreviewManager::previewStarted,
273 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500274 QObject::disconnect(previewHolder.frameUpdated);
275 previewHolder.frameUpdated = QObject::connect(renderer,
276 &Video::Renderer::frameUpdated,
277 [=]() {
278 [self renderer:Video::PreviewManager::instance()->previewRenderer()
279 renderFrameForView:previewView];
280 });
281 });
282
283 previewHolder.stopped = QObject::connect(Video::PreviewManager::instance(),
284 &Video::PreviewManager::previewStopped,
285 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500286 QObject::disconnect(previewHolder.frameUpdated);
287 [previewView.layer setContents:nil];
288 });
289
290 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance()->previewRenderer(),
291 &Video::Renderer::frameUpdated,
292 [=]() {
293 [self renderer:Video::PreviewManager::instance()->previewRenderer()
294 renderFrameForView:previewView];
295 });
296}
297
298-(void) connectVideoRenderer: (Video::Renderer*)renderer
299{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400300 QObject::disconnect(videoHolder.frameUpdated);
301 QObject::disconnect(videoHolder.started);
302 QObject::disconnect(videoHolder.stopped);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500303 videoHolder.frameUpdated = QObject::connect(renderer,
304 &Video::Renderer::frameUpdated,
305 [=]() {
306 [self renderer:renderer renderFrameForView:videoView];
307 });
308
309 videoHolder.started = QObject::connect(renderer,
310 &Video::Renderer::started,
311 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500312 QObject::disconnect(videoHolder.frameUpdated);
313 videoHolder.frameUpdated = QObject::connect(renderer,
314 &Video::Renderer::frameUpdated,
315 [=]() {
316 [self renderer:renderer renderFrameForView:videoView];
317 });
318 });
319
320 videoHolder.stopped = QObject::connect(renderer,
321 &Video::Renderer::stopped,
322 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500323 QObject::disconnect(videoHolder.frameUpdated);
324 [videoView.layer setContents:nil];
325 });
326}
327
328-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
329{
330 const QByteArray& data = renderer->currentFrame();
331 QSize res = renderer->size();
332
333 auto buf = reinterpret_cast<const unsigned char*>(data.data());
334
335 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
336 CGContextRef newContext = CGBitmapContextCreate((void *)buf,
337 res.width(),
338 res.height(),
339 8,
340 4*res.width(),
341 colorSpace,
342 kCGImageAlphaPremultipliedLast);
343
344
345 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
346
347 /*We release some components*/
348 CGContextRelease(newContext);
349 CGColorSpaceRelease(colorSpace);
350
351 [CATransaction begin];
352 view.layer.contents = (__bridge id)newImage;
353 [CATransaction commit];
354
355 CFRelease(newImage);
356}
357
358- (void) initFrame
359{
360 [self.view setFrame:self.view.superview.bounds];
361 [self.view setHidden:YES];
362 self.view.layer.position = self.view.frame.origin;
363}
364
365# pragma private IN/OUT animations
366
367-(void) animateIn
368{
369 NSLog(@"animateIn");
370 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
371 [self.view setHidden:NO];
372
373 [CATransaction begin];
374 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
375 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
376 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
377 [animation setDuration:0.2f];
378 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
379 [CATransaction setCompletionBlock:^{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500380 [self connectVideoSignals];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500381 }];
382 [self.view.layer addAnimation:animation forKey:animation.keyPath];
383
384 [CATransaction commit];
385}
386
387-(void) cleanUp
388{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400389 QObject::disconnect(videoHolder.frameUpdated);
390 QObject::disconnect(videoHolder.started);
391 QObject::disconnect(videoHolder.stopped);
392 QObject::disconnect(previewHolder.frameUpdated);
393 QObject::disconnect(previewHolder.stopped);
394 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500395 [videoView.layer setContents:nil];
396 [previewView.layer setContents:nil];
397}
398
399-(void) animateOut
400{
401 NSLog(@"animateOut");
402 if(self.view.frame.origin.x < 0) {
403 NSLog(@"Already hidden");
404 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
405 [self animateIn];
406 }
407 return;
408 }
409
410 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
411 [CATransaction begin];
412 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
413 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
414 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
415 [animation setDuration:0.2f];
416 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
417
418 [CATransaction setCompletionBlock:^{
419 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400420 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500421 [self cleanUp];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500422 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
423 [self animateIn];
424 }
425 }];
426 [self.view.layer addAnimation:animation forKey:animation.keyPath];
427 [CATransaction commit];
428}
429
430/**
431 * Debug purpose
432 */
433-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
434{
435 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
436}
437
438
439#pragma button methods
440- (IBAction)hangUp:(id)sender {
441 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::REFUSE;
442}
443
444- (IBAction)accept:(id)sender {
445 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::ACCEPT;
446}
447
448- (IBAction)toggleRecording:(id)sender {
449 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::RECORD;
450}
451
452- (IBAction)toggleHold:(id)sender {
453 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::HOLD;
454}
455
456@end