blob: 2d221f681738a73761019c56d0e1431756a69fb5 [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04002 * Copyright (C) 2015 Savoir-faire Linux Inc.
Alexandre Lisionc5148052015-03-04 15:10:35 -05003 * 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.
Alexandre Lisionc5148052015-03-04 15:10:35 -050018 */
19#import "CurrentCallVC.h"
20
21#import <QuartzCore/QuartzCore.h>
22
23#import <call.h>
24#import <callmodel.h>
25#import <useractionmodel.h>
26#import <contactmethod.h>
27#import <qabstractitemmodel.h>
28#import <QItemSelectionModel>
29#import <QItemSelection>
Alexandre Lisionc5148052015-03-04 15:10:35 -050030#import <video/previewmanager.h>
31#import <video/renderer.h>
Alexandre Lision58cab672015-06-09 15:25:40 -040032#import <media/text.h>
Alexandre Lision2db8f472015-07-22 15:05:46 -040033#import <person.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050034
Alexandre Lisionf47a2562015-06-15 15:48:29 -040035#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040036#import "views/CallView.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040037#import "PersonLinkerVC.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040038#import "ChatVC.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040039
Alexandre Lisionc5148052015-03-04 15:10:35 -050040@interface RendererConnectionsHolder : NSObject
41
42@property QMetaObject::Connection frameUpdated;
43@property QMetaObject::Connection started;
44@property QMetaObject::Connection stopped;
45
46@end
47
48@implementation RendererConnectionsHolder
49
50@end
51
Alexandre Lision2db8f472015-07-22 15:05:46 -040052@interface CurrentCallVC () <NSPopoverDelegate, ContactLinkedDelegate>
Alexandre Lisionc5148052015-03-04 15:10:35 -050053
Alexandre Lision58cab672015-06-09 15:25:40 -040054@property (unsafe_unretained) IBOutlet NSTextField *personLabel;
55@property (unsafe_unretained) IBOutlet NSTextField *stateLabel;
56@property (unsafe_unretained) IBOutlet NSButton *holdOnOffButton;
57@property (unsafe_unretained) IBOutlet NSButton *hangUpButton;
58@property (unsafe_unretained) IBOutlet NSButton *recordOnOffButton;
59@property (unsafe_unretained) IBOutlet NSButton *pickUpButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040060@property (unsafe_unretained) IBOutlet NSButton *muteAudioButton;
61@property (unsafe_unretained) IBOutlet NSButton *muteVideoButton;
Alexandre Lision2db8f472015-07-22 15:05:46 -040062@property (unsafe_unretained) IBOutlet NSButton *addContactButton;
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -040063@property (unsafe_unretained) IBOutlet NSView *headerContainer;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040064
Alexandre Lisionf47a2562015-06-15 15:48:29 -040065@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040066
Alexandre Lision58cab672015-06-09 15:25:40 -040067@property (unsafe_unretained) IBOutlet NSTextField *timeSpentLabel;
68@property (unsafe_unretained) IBOutlet NSView *controlsPanel;
69@property (unsafe_unretained) IBOutlet NSSplitView *splitView;
70@property (unsafe_unretained) IBOutlet NSButton *chatButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -050071
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -040072@property (strong) IBOutlet NSPopover *qualityPopOver;
Alexandre Lision2db8f472015-07-22 15:05:46 -040073@property (strong) NSPopover* addToContactPopover;
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040074@property (strong) IBOutlet ChatVC *chatVC;
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -040075
Alexandre Lisionc5148052015-03-04 15:10:35 -050076@property QHash<int, NSButton*> actionHash;
77
78// Video
Alexandre Lision58cab672015-06-09 15:25:40 -040079@property (unsafe_unretained) IBOutlet CallView *videoView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050080@property CALayer* videoLayer;
Alexandre Lision58cab672015-06-09 15:25:40 -040081@property (unsafe_unretained) IBOutlet NSView *previewView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050082@property CALayer* previewLayer;
83
84@property RendererConnectionsHolder* previewHolder;
85@property RendererConnectionsHolder* videoHolder;
Alexandre Lisionef6333a2015-03-24 12:30:31 -040086@property QMetaObject::Connection videoStarted;
Alexandre Lisionb65c0272015-07-22 15:51:29 -040087@property QMetaObject::Connection messageConnection;
88@property QMetaObject::Connection mediaAddedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -050089
90@end
91
92@implementation CurrentCallVC
Alexandre Lisiond18fa272015-06-15 11:18:03 -040093@synthesize personLabel, actionHash, stateLabel, holdOnOffButton, hangUpButton,
94 recordOnOffButton, pickUpButton, chatButton, timeSpentLabel,
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -040095 muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView, videoLayer, previewLayer, previewView, splitView, loadingIndicator;
Alexandre Lisionc5148052015-03-04 15:10:35 -050096
97@synthesize previewHolder;
98@synthesize videoHolder;
99
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400100- (void) updateAllActions
Alexandre Lisionc5148052015-03-04 15:10:35 -0500101{
102 for(int i = 0 ; i <= CallModel::instance()->userActionModel()->rowCount() ; i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400103 [self updateActionAtIndex:i];
104 }
105}
106
107- (void) updateActionAtIndex:(int) row
108{
109 const QModelIndex& idx = CallModel::instance()->userActionModel()->index(row,0);
110 UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
111 NSButton* a = actionHash[(int) action];
112 if (a != nil) {
113 [a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
114 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
115
116 if(action == UserActionModel::Action::HOLD) {
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400117 NSString* imgName = (a.state == NSOnState ? @"ic_action_holdoff" : @"ic_action_hold");
118 [a setImage:[NSImage imageNamed:imgName]];
119
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400120 }
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();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400130 if (!callIdx.isValid()) {
131 return;
132 }
Alexandre Lision58cab672015-06-09 15:25:40 -0400133 [personLabel setStringValue:callIdx.data(Qt::DisplayRole).toString().toNSString()];
134 [timeSpentLabel setStringValue:callIdx.data((int)Call::Role::Length).toString().toNSString()];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500135
Alexandre Lision2db8f472015-07-22 15:05:46 -0400136 auto contactmethod = qvariant_cast<Call*>(callIdx.data(static_cast<int>(Call::Role::Object)))->peerContactMethod();
137 BOOL shouldShow = (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder());
138 [self.addContactButton setHidden:!shouldShow];
139
Alexandre Lision58cab672015-06-09 15:25:40 -0400140 Call::State state = callIdx.data((int)Call::Role::State).value<Call::State>();
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400141 [loadingIndicator setHidden:YES];
142 [stateLabel setStringValue:callIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500143 switch (state) {
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400144 case Call::State::DIALING:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400145 [loadingIndicator setHidden:NO];
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400146 break;
147 case Call::State::NEW:
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400148 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500149 case Call::State::INITIALIZATION:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400150 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400151 [loadingIndicator setHidden:NO];
152 break;
153 case Call::State::CONNECTED:
154 [videoView setShouldAcceptInteractions:NO];
155 [loadingIndicator setHidden:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500156 break;
157 case Call::State::RINGING:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400158 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500159 break;
160 case Call::State::CURRENT:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400161 [videoView setShouldAcceptInteractions:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500162 break;
163 case Call::State::HOLD:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400164 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500165 break;
166 case Call::State::BUSY:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400167 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500168 break;
169 case Call::State::OVER:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400170 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400171 if(self.splitView.isInFullScreenMode)
172 [self.splitView exitFullScreenModeWithOptions:nil];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500173 break;
174 case Call::State::FAILURE:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400175 [videoView setShouldAcceptInteractions:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500176 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500177 }
178
179}
180
181- (void)awakeFromNib
182{
183 NSLog(@"INIT CurrentCall VC");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400184 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500185 [self.view setLayer:[CALayer layer]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500186
187 [controlsPanel setWantsLayer:YES];
188 [controlsPanel setLayer:[CALayer layer]];
189 [controlsPanel.layer setZPosition:2.0];
190 [controlsPanel.layer setBackgroundColor:[NSColor whiteColor].CGColor];
191
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400192 actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
193 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
194 actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
195 actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400196 actionHash[ (int)UserActionModel::Action::MUTE_AUDIO] = muteAudioButton;
197 actionHash[ (int)UserActionModel::Action::MUTE_VIDEO] = muteVideoButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500198
Alexandre Lisionc5148052015-03-04 15:10:35 -0500199 videoLayer = [CALayer layer];
200 [videoView setWantsLayer:YES];
201 [videoView setLayer:videoLayer];
202 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
203 [videoView.layer setFrame:videoView.frame];
204 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500205
206 previewLayer = [CALayer layer];
207 [previewView setWantsLayer:YES];
208 [previewView setLayer:previewLayer];
209 [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400210 [previewLayer setContentsGravity:kCAGravityResizeAspectFill];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500211 [previewLayer setFrame:previewView.frame];
212
213 [controlsPanel setWantsLayer:YES];
214 [controlsPanel setLayer:[CALayer layer]];
215 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
216 [controlsPanel.layer setFrame:controlsPanel.frame];
217
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400218 previewHolder = [[RendererConnectionsHolder alloc] init];
219 videoHolder = [[RendererConnectionsHolder alloc] init];
220
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400221 [loadingIndicator setColor:[NSColor whiteColor]];
222 [loadingIndicator setNumberOfLines:100];
223 [loadingIndicator setWidthOfLine:2];
224 [loadingIndicator setLengthOfLine:2];
225 [loadingIndicator setInnerMargin:30];
226
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400227 [self.videoView setCallDelegate:self];
Alexandre Lision58cab672015-06-09 15:25:40 -0400228
Alexandre Lisionc5148052015-03-04 15:10:35 -0500229 [self connect];
230}
231
232- (void) connect
233{
234 QObject::connect(CallModel::instance()->selectionModel(),
235 &QItemSelectionModel::currentChanged,
236 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500237 if(!current.isValid()) {
238 [self animateOut];
239 return;
240 }
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400241
242 auto call = CallModel::instance()->getCall(current);
243 if (call->state() == Call::State::HOLD) {
244 call << Call::Action::HOLD;
245 }
246
Alexandre Lision58cab672015-06-09 15:25:40 -0400247 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500248 [self updateCall];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400249 [self updateAllActions];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500250 [self animateOut];
251 });
252
Alexandre Lisionc5148052015-03-04 15:10:35 -0500253 QObject::connect(CallModel::instance()->userActionModel(),
254 &QAbstractItemModel::dataChanged,
255 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500256 const int first(topLeft.row()),last(bottomRight.row());
257 for(int i = first; i <= last;i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400258 [self updateActionAtIndex:i];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500259 }
260 });
261
262 QObject::connect(CallModel::instance(),
263 &CallModel::callStateChanged,
264 [self](Call* c, Call::State state) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500265 [self updateCall];
266 });
267}
268
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400269- (void) monitorIncomingTextMessages:(Media::Text*) media
270{
271 /* connect to incoming chat messages to open the chat view */
272 QObject::disconnect(self.messageConnection);
273 self.messageConnection = QObject::connect(media,
274 &Media::Text::messageReceived,
275 [self] (const QMap<QString,QString>& m) {
276 if([[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]])
277 [self uncollapseRightView];
278 });
279}
280
Alexandre Lisionc5148052015-03-04 15:10:35 -0500281-(void) connectVideoSignals
282{
283 QModelIndex idx = CallModel::instance()->selectionModel()->currentIndex();
284 Call* call = CallModel::instance()->getCall(idx);
Alexandre Lision58cab672015-06-09 15:25:40 -0400285 self.videoStarted = QObject::connect(call,
Alexandre Lisionc5148052015-03-04 15:10:35 -0500286 &Call::videoStarted,
287 [=](Video::Renderer* renderer) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400288 NSLog(@"Video started!");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400289 [self connectVideoRenderer:renderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500290 });
291
292 if(call->videoRenderer())
293 {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500294 [self connectVideoRenderer:call->videoRenderer()];
295 }
296
297 [self connectPreviewRenderer];
298
299}
300
301-(void) connectPreviewRenderer
302{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400303 QObject::disconnect(previewHolder.frameUpdated);
304 QObject::disconnect(previewHolder.stopped);
305 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500306 previewHolder.started = QObject::connect(Video::PreviewManager::instance(),
307 &Video::PreviewManager::previewStarted,
308 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500309 QObject::disconnect(previewHolder.frameUpdated);
310 previewHolder.frameUpdated = QObject::connect(renderer,
311 &Video::Renderer::frameUpdated,
312 [=]() {
313 [self renderer:Video::PreviewManager::instance()->previewRenderer()
314 renderFrameForView:previewView];
315 });
316 });
317
318 previewHolder.stopped = QObject::connect(Video::PreviewManager::instance(),
319 &Video::PreviewManager::previewStopped,
320 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500321 QObject::disconnect(previewHolder.frameUpdated);
322 [previewView.layer setContents:nil];
323 });
324
325 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance()->previewRenderer(),
326 &Video::Renderer::frameUpdated,
327 [=]() {
328 [self renderer:Video::PreviewManager::instance()->previewRenderer()
329 renderFrameForView:previewView];
330 });
331}
332
333-(void) connectVideoRenderer: (Video::Renderer*)renderer
334{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400335 QObject::disconnect(videoHolder.frameUpdated);
336 QObject::disconnect(videoHolder.started);
337 QObject::disconnect(videoHolder.stopped);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500338 videoHolder.frameUpdated = QObject::connect(renderer,
339 &Video::Renderer::frameUpdated,
340 [=]() {
341 [self renderer:renderer renderFrameForView:videoView];
342 });
343
344 videoHolder.started = QObject::connect(renderer,
345 &Video::Renderer::started,
346 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500347 QObject::disconnect(videoHolder.frameUpdated);
348 videoHolder.frameUpdated = QObject::connect(renderer,
349 &Video::Renderer::frameUpdated,
350 [=]() {
351 [self renderer:renderer renderFrameForView:videoView];
352 });
353 });
354
355 videoHolder.stopped = QObject::connect(renderer,
356 &Video::Renderer::stopped,
357 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500358 QObject::disconnect(videoHolder.frameUpdated);
359 [videoView.layer setContents:nil];
360 });
361}
362
363-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
364{
Alexandre Lision911e0072015-07-13 16:19:08 -0400365 auto data = renderer->currentSmartFrame();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500366 QSize res = renderer->size();
367
Alexandre Lision911e0072015-07-13 16:19:08 -0400368 auto buf = reinterpret_cast<const unsigned char*>(data->data());
Alexandre Lisionc5148052015-03-04 15:10:35 -0500369
370 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
371 CGContextRef newContext = CGBitmapContextCreate((void *)buf,
372 res.width(),
373 res.height(),
374 8,
375 4*res.width(),
376 colorSpace,
377 kCGImageAlphaPremultipliedLast);
378
379
380 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
381
382 /*We release some components*/
383 CGContextRelease(newContext);
384 CGColorSpaceRelease(colorSpace);
385
386 [CATransaction begin];
387 view.layer.contents = (__bridge id)newImage;
388 [CATransaction commit];
389
390 CFRelease(newImage);
391}
392
393- (void) initFrame
394{
395 [self.view setFrame:self.view.superview.bounds];
396 [self.view setHidden:YES];
397 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400398 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500399}
400
401# pragma private IN/OUT animations
402
403-(void) animateIn
404{
405 NSLog(@"animateIn");
406 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
407 [self.view setHidden:NO];
408
409 [CATransaction begin];
410 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
411 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
412 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
413 [animation setDuration:0.2f];
414 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
415 [CATransaction setCompletionBlock:^{
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400416
417 // when call comes in we want to show the controls/header
418 [self mouseIsMoving:YES];
419
Alexandre Lisionc5148052015-03-04 15:10:35 -0500420 [self connectVideoSignals];
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400421 /* check if text media is already present */
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400422 if(!CallModel::instance()->selectedCall())
423 return;
Alexandre Lision21666f32015-09-22 17:04:36 -0400424
425 QObject::connect(CallModel::instance()->selectedCall(),
426 &Call::changed,
427 [=]() {
428 [self updateCall];
429 });
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400430 if (CallModel::instance()->selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
431 Media::Text *text = CallModel::instance()->selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::IN);
432 [self monitorIncomingTextMessages:text];
433 } else if (CallModel::instance()->selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
434 Media::Text *text = CallModel::instance()->selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::OUT);
435 [self monitorIncomingTextMessages:text];
436 } else {
437 /* monitor media for messaging text messaging */
438 self.mediaAddedConnection = QObject::connect(CallModel::instance()->selectedCall(),
439 &Call::mediaAdded,
440 [self] (Media::Media* media) {
441 if (media->type() == Media::Media::Type::TEXT) { [self monitorIncomingTextMessages:(Media::Text*)media];
442 QObject::disconnect(self.mediaAddedConnection);
443 }
444 });
445 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500446 }];
447 [self.view.layer addAnimation:animation forKey:animation.keyPath];
448
449 [CATransaction commit];
450}
451
452-(void) cleanUp
453{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400454 QObject::disconnect(videoHolder.frameUpdated);
455 QObject::disconnect(videoHolder.started);
456 QObject::disconnect(videoHolder.stopped);
457 QObject::disconnect(previewHolder.frameUpdated);
458 QObject::disconnect(previewHolder.stopped);
459 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500460 [videoView.layer setContents:nil];
461 [previewView.layer setContents:nil];
462}
463
464-(void) animateOut
465{
466 NSLog(@"animateOut");
467 if(self.view.frame.origin.x < 0) {
468 NSLog(@"Already hidden");
469 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
470 [self animateIn];
471 }
472 return;
473 }
474
475 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
476 [CATransaction begin];
477 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
478 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
479 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
480 [animation setDuration:0.2f];
481 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
482
483 [CATransaction setCompletionBlock:^{
484 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400485 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500486 [self cleanUp];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500487 if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
488 [self animateIn];
489 }
490 }];
491 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400492
493 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500494 [CATransaction commit];
495}
496
497/**
498 * Debug purpose
499 */
500-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
501{
502 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
503}
504
Alexandre Lision58cab672015-06-09 15:25:40 -0400505-(void)collapseRightView
506{
507 NSView *right = [[splitView subviews] objectAtIndex:1];
508 NSView *left = [[splitView subviews] objectAtIndex:0];
509 NSRect leftFrame = [left frame];
510 [right setHidden:YES];
511 [splitView display];
512}
Alexandre Lisionc5148052015-03-04 15:10:35 -0500513
Alexandre Lision58cab672015-06-09 15:25:40 -0400514-(void)uncollapseRightView
515{
516 NSView *left = [[splitView subviews] objectAtIndex:0];
517 NSView *right = [[splitView subviews] objectAtIndex:1];
518 [right setHidden:NO];
519
520 CGFloat dividerThickness = [splitView dividerThickness];
521
522 // get the different frames
523 NSRect leftFrame = [left frame];
524 NSRect rightFrame = [right frame];
525
526 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
527 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
528 [left setFrameSize:leftFrame.size];
529 [right setFrame:rightFrame];
530 [splitView display];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -0400531
532 [self.chatVC takeFocus];
Alexandre Lision58cab672015-06-09 15:25:40 -0400533}
534
535
536#pragma mark - Button methods
537
Alexandre Lision2db8f472015-07-22 15:05:46 -0400538- (IBAction)addToContact:(NSButton*) sender {
539 auto contactmethod = CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex())->peerContactMethod();
540
541 if (self.addToContactPopover != nullptr) {
542 [self.addToContactPopover performClose:self];
543 self.addToContactPopover = NULL;
544 } else if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
545 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
546 [editorVC setMethodToLink:contactmethod];
547 [editorVC setContactLinkedDelegate:self];
548 self.addToContactPopover = [[NSPopover alloc] init];
549 [self.addToContactPopover setContentSize:editorVC.view.frame.size];
550 [self.addToContactPopover setContentViewController:editorVC];
551 [self.addToContactPopover setAnimates:YES];
552 [self.addToContactPopover setBehavior:NSPopoverBehaviorTransient];
553 [self.addToContactPopover setDelegate:self];
554
555 [self.addToContactPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSMaxXEdge];
556 }
557}
558
Alexandre Lisionc5148052015-03-04 15:10:35 -0500559- (IBAction)hangUp:(id)sender {
560 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::REFUSE;
561}
562
563- (IBAction)accept:(id)sender {
564 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::ACCEPT;
565}
566
567- (IBAction)toggleRecording:(id)sender {
Alexandre Lision66643432015-06-04 11:59:36 -0400568 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::RECORD_AUDIO;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500569}
570
571- (IBAction)toggleHold:(id)sender {
572 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex()) << Call::Action::HOLD;
573}
574
Alexandre Lision58cab672015-06-09 15:25:40 -0400575-(IBAction)toggleChat:(id)sender;
576{
577 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
578 if (rightViewCollapsed) {
579 [self uncollapseRightView];
580 CallModel::instance()->getCall(CallModel::instance()->selectionModel()->currentIndex())->addOutgoingMedia<Media::Text>();
581 } else {
582 [self collapseRightView];
583 }
584 [chatButton setState:rightViewCollapsed];
585}
586
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400587- (IBAction)muteAudio:(id)sender
588{
589 UserActionModel* uam = CallModel::instance()->userActionModel();
590 uam << UserActionModel::Action::MUTE_AUDIO;
591}
592
593- (IBAction)muteVideo:(id)sender
594{
595 UserActionModel* uam = CallModel::instance()->userActionModel();
596 uam << UserActionModel::Action::MUTE_VIDEO;
597}
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400598- (IBAction)displayQualityPopUp:(id)sender {
599 [self.qualityPopOver showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxXEdge];
600}
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400601
Alexandre Lision2db8f472015-07-22 15:05:46 -0400602#pragma mark - NSPopOverDelegate
603
604- (void)popoverDidClose:(NSNotification *)notification
605{
606 if (self.addToContactPopover != nullptr) {
607 [self.addToContactPopover performClose:self];
608 self.addToContactPopover = NULL;
609 }
610}
611
612#pragma mark - ContactLinkedDelegate
613
614- (void)contactLinked
615{
616 if (self.addToContactPopover != nullptr) {
617 [self.addToContactPopover performClose:self];
618 self.addToContactPopover = NULL;
619 }
620}
621
Alexandre Lision58cab672015-06-09 15:25:40 -0400622#pragma mark - NSSplitViewDelegate
623
624/* Return YES if the subview should be collapsed because the user has double-clicked on an adjacent divider. If a split view has a delegate, and the delegate responds to this message, it will be sent once for the subview before a divider when the user double-clicks on that divider, and again for the subview after the divider, but only if the delegate returned YES when sent -splitView:canCollapseSubview: for the subview in question. When the delegate indicates that both subviews should be collapsed NSSplitView's behavior is undefined.
625 */
626- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
627{
628 NSView* rightView = [[splitView subviews] objectAtIndex:1];
629 return ([subview isEqual:rightView]);
630}
631
632
633- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
634{
635 NSView* rightView = [[splitView subviews] objectAtIndex:1];
636 return ([subview isEqual:rightView]);
637}
638
639
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400640# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -0400641
642- (void) callShouldToggleFullScreen
643{
644 if(self.splitView.isInFullScreenMode)
645 [self.splitView exitFullScreenModeWithOptions:nil];
646 else {
647 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
648 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
649 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
650 NSFullScreenModeApplicationPresentationOptions, nil];
651
652 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
653 }
654}
655
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400656-(void) mouseIsMoving:(BOOL) move
657{
658 [[controlsPanel animator] setAlphaValue:move]; // fade out
659 [[headerContainer animator] setAlphaValue:move];
660}
661
Alexandre Lisionc5148052015-03-04 15:10:35 -0500662@end