CurrentCallVC.mm 29 KB
Newer Older
1
/*
Alexandre Lision's avatar
Alexandre Lision committed
2
 *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */
#import "CurrentCallVC.h"

#import <QuartzCore/QuartzCore.h>

#import <call.h>
#import <callmodel.h>
25
#import <recentmodel.h>
26
#import <useractionmodel.h>
27
#import <QMimeData>
28 29 30 31 32 33
#import <contactmethod.h>
#import <qabstractitemmodel.h>
#import <QItemSelectionModel>
#import <QItemSelection>
#import <video/previewmanager.h>
#import <video/renderer.h>
Alexandre Lision's avatar
Alexandre Lision committed
34
#import <media/text.h>
35
#import <person.h>
36

37
#import "views/ITProgressIndicator.h"
Alexandre Lision's avatar
Alexandre Lision committed
38
#import "views/CallView.h"
39
#import "PersonLinkerVC.h"
Alexandre Lision's avatar
Alexandre Lision committed
40
#import "ChatVC.h"
Alexandre Lision's avatar
Alexandre Lision committed
41
#import "BrokerVC.h"
Alexandre Lision's avatar
Alexandre Lision committed
42

43 44 45 46 47 48 49 50 51 52 53 54
@interface RendererConnectionsHolder : NSObject

@property QMetaObject::Connection frameUpdated;
@property QMetaObject::Connection started;
@property QMetaObject::Connection stopped;

@end

@implementation RendererConnectionsHolder

@end

55
@interface CurrentCallVC () <NSPopoverDelegate, ContactLinkedDelegate>
56

57 58
// Header info
@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision's avatar
Alexandre Lision committed
59 60
@property (unsafe_unretained) IBOutlet NSTextField* personLabel;
@property (unsafe_unretained) IBOutlet NSTextField* stateLabel;
61 62 63 64
@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;

// Call Controls
@property (unsafe_unretained) IBOutlet NSView* controlsPanel;
Alexandre Lision's avatar
Alexandre Lision committed
65 66 67 68 69 70 71 72
@property (unsafe_unretained) IBOutlet NSButton* holdOnOffButton;
@property (unsafe_unretained) IBOutlet NSButton* hangUpButton;
@property (unsafe_unretained) IBOutlet NSButton* recordOnOffButton;
@property (unsafe_unretained) IBOutlet NSButton* pickUpButton;
@property (unsafe_unretained) IBOutlet NSButton* muteAudioButton;
@property (unsafe_unretained) IBOutlet NSButton* muteVideoButton;
@property (unsafe_unretained) IBOutlet NSButton* addContactButton;
@property (unsafe_unretained) IBOutlet NSButton* transferButton;
73 74
@property (unsafe_unretained) IBOutlet NSButton* addParticipantButton;
@property (unsafe_unretained) IBOutlet NSButton* chatButton;
75

76
@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
77

78 79 80 81
// Join call panel
@property (unsafe_unretained) IBOutlet NSView* joinPanel;
@property (unsafe_unretained) IBOutlet NSButton* mergeCallsButton;

Alexandre Lision's avatar
Alexandre Lision committed
82
@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
83

84
@property (strong) NSPopover* addToContactPopover;
85
@property (strong) NSPopover* brokerPopoverVC;
Alexandre Lision's avatar
Alexandre Lision committed
86
@property (strong) IBOutlet ChatVC* chatVC;
87

88 89 90
@property QHash<int, NSButton*> actionHash;

// Video
Alexandre Lision's avatar
Alexandre Lision committed
91
@property (unsafe_unretained) IBOutlet CallView *videoView;
92
@property CALayer* videoLayer;
Alexandre Lision's avatar
Alexandre Lision committed
93
@property (unsafe_unretained) IBOutlet NSView *previewView;
94 95 96 97
@property CALayer* previewLayer;

@property RendererConnectionsHolder* previewHolder;
@property RendererConnectionsHolder* videoHolder;
98
@property QMetaObject::Connection videoStarted;
99 100
@property QMetaObject::Connection messageConnection;
@property QMetaObject::Connection mediaAddedConnection;
101 102 103 104

@end

@implementation CurrentCallVC
105
@synthesize personLabel, actionHash, stateLabel, holdOnOffButton, hangUpButton,
106
            recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Alexandre Lision's avatar
Alexandre Lision committed
107 108
            muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView,
            videoLayer, previewLayer, previewView, splitView, loadingIndicator;
109 110 111 112

@synthesize previewHolder;
@synthesize videoHolder;

113
- (void) updateAllActions
114
{
Alexandre Lision's avatar
Alexandre Lision committed
115
    for (int i = 0 ; i < CallModel::instance().userActionModel()->rowCount() ; i++) {
116 117 118 119 120 121
        [self updateActionAtIndex:i];
    }
}

- (void) updateActionAtIndex:(int) row
{
Alexandre Lision's avatar
Alexandre Lision committed
122
    const QModelIndex& idx = CallModel::instance().userActionModel()->index(row,0);
123 124
    UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
    NSButton* a = actionHash[(int) action];
Alexandre Lision's avatar
Alexandre Lision committed
125 126
    if (a) {
        [a setHidden:!(idx.flags() & Qt::ItemIsEnabled)];
127
        [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
128 129 130 131 132
    }
}

-(void) updateCall
{
Alexandre Lision's avatar
Alexandre Lision committed
133
    QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
134 135 136
    if (!callIdx.isValid()) {
        return;
    }
137 138
    auto current = CallModel::instance().getCall(callIdx);

Alexandre Lision's avatar
Alexandre Lision committed
139 140
    [personLabel setStringValue:callIdx.data(Qt::DisplayRole).toString().toNSString()];
    [timeSpentLabel setStringValue:callIdx.data((int)Call::Role::Length).toString().toNSString()];
Alexandre Lision's avatar
Alexandre Lision committed
141
    [stateLabel setStringValue:callIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
142

143 144 145 146
    auto contactmethod = qvariant_cast<Call*>(callIdx.data(static_cast<int>(Call::Role::Object)))->peerContactMethod();
    BOOL shouldShow = (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder());
    [self.addContactButton setHidden:!shouldShow];

Alexandre Lision's avatar
Alexandre Lision committed
147
    Call::State state = callIdx.data((int)Call::Role::State).value<Call::State>();
Alexandre Lision's avatar
Alexandre Lision committed
148 149

    // Default values for this views
150
    [loadingIndicator setHidden:YES];
Alexandre Lision's avatar
Alexandre Lision committed
151 152
    [videoView setShouldAcceptInteractions:NO];

153 154
    [self.controlsPanel setHidden:current->hasParentCall()];
    [self.joinPanel setHidden:!current->hasParentCall()];
Alexandre Lision's avatar
Alexandre Lision committed
155

156
    switch (state) {
157
        case Call::State::DIALING:
158
            [loadingIndicator setHidden:NO];
159 160 161
            break;
        case Call::State::NEW:
            break;
162
        case Call::State::INITIALIZATION:
163 164 165 166
            [loadingIndicator setHidden:NO];
            break;
        case Call::State::CONNECTED:
            [loadingIndicator setHidden:NO];
167 168 169
            break;
        case Call::State::RINGING:
            break;
170 171 172 173 174 175
        case Call::State::CONFERENCE:
            [videoView setShouldAcceptInteractions:YES];
            [self.chatButton setHidden:NO];
            [self.addParticipantButton setHidden:NO];
            [self.transferButton setHidden:YES];
            break;
176
        case Call::State::CURRENT:
Alexandre Lision's avatar
Alexandre Lision committed
177
            [videoView setShouldAcceptInteractions:YES];
Alexandre Lision's avatar
Alexandre Lision committed
178
            [self.chatButton setHidden:NO];
179 180
            [self.addParticipantButton setHidden:NO];
            [self.transferButton setHidden:NO];
181 182 183 184 185 186
            break;
        case Call::State::HOLD:
            break;
        case Call::State::BUSY:
            break;
        case Call::State::OVER:
Alexandre Lision's avatar
Alexandre Lision committed
187
        case Call::State::FAILURE:
Alexandre Lision's avatar
Alexandre Lision committed
188 189
            if(self.splitView.isInFullScreenMode)
                [self.splitView exitFullScreenModeWithOptions:nil];
190 191 192 193 194 195 196 197
            break;
    }

}

- (void)awakeFromNib
{
    NSLog(@"INIT CurrentCall VC");
198
    [self.view setWantsLayer:YES];
199 200
    [self.view setLayer:[CALayer layer]];

201 202 203 204
    actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
    actionHash[ (int)UserActionModel::Action::HOLD  ] = holdOnOffButton;
    actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
    actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
205 206
    actionHash[ (int)UserActionModel::Action::MUTE_AUDIO] = muteAudioButton;
    actionHash[ (int)UserActionModel::Action::MUTE_VIDEO] = muteVideoButton;
207 208 209 210 211 212 213 214 215 216 217 218

    videoLayer = [CALayer layer];
    [videoView setWantsLayer:YES];
    [videoView setLayer:videoLayer];
    [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
    [videoView.layer setFrame:videoView.frame];
    [videoView.layer setContentsGravity:kCAGravityResizeAspect];

    previewLayer = [CALayer layer];
    [previewView setWantsLayer:YES];
    [previewView setLayer:previewLayer];
    [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
219
    [previewLayer setContentsGravity:kCAGravityResizeAspectFill];
220 221 222 223 224 225 226
    [previewLayer setFrame:previewView.frame];

    [controlsPanel setWantsLayer:YES];
    [controlsPanel setLayer:[CALayer layer]];
    [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
    [controlsPanel.layer setFrame:controlsPanel.frame];

227 228 229
    previewHolder = [[RendererConnectionsHolder alloc] init];
    videoHolder = [[RendererConnectionsHolder alloc] init];

230 231 232 233 234 235
    [loadingIndicator setColor:[NSColor whiteColor]];
    [loadingIndicator setNumberOfLines:100];
    [loadingIndicator setWidthOfLine:2];
    [loadingIndicator setLengthOfLine:2];
    [loadingIndicator setInnerMargin:30];

Alexandre Lision's avatar
Alexandre Lision committed
236
    [self.videoView setCallDelegate:self];
Alexandre Lision's avatar
Alexandre Lision committed
237

238 239 240 241 242
    [self connect];
}

- (void) connect
{
243
    QObject::connect(RecentModel::instance().selectionModel(),
244 245
                     &QItemSelectionModel::currentChanged,
                     [=](const QModelIndex &current, const QModelIndex &previous) {
246 247
                         auto call = RecentModel::instance().getActiveCall(current);
                         if(!current.isValid() || !call) {
248 249 250
                             [self animateOut];
                             return;
                         }
251

252 253
                         CallModel::instance().selectCall(call);

254 255 256 257
                         if (call->state() == Call::State::HOLD) {
                             call << Call::Action::HOLD;
                         }

Alexandre Lision's avatar
Alexandre Lision committed
258
                         [self collapseRightView];
259
                         [self updateCall];
260
                         [self updateAllActions];
261 262 263
                         [self animateOut];
                     });

Alexandre Lision's avatar
Alexandre Lision committed
264
    QObject::connect(CallModel::instance().userActionModel(),
265 266 267 268
                     &QAbstractItemModel::dataChanged,
                     [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
                         const int first(topLeft.row()),last(bottomRight.row());
                         for(int i = first; i <= last;i++) {
269
                             [self updateActionAtIndex:i];
270 271 272
                         }
                     });

Alexandre Lision's avatar
Alexandre Lision committed
273
    QObject::connect(&CallModel::instance(),
274 275
                     &CallModel::callStateChanged,
                     [self](Call* c, Call::State state) {
Alexandre Lision's avatar
Alexandre Lision committed
276 277 278 279 280 281 282 283 284 285
                         auto current = CallModel::instance().selectionModel()->currentIndex();
                         if (!current.isValid())
                             [self animateOut];
                         else if (CallModel::instance().getIndex(c) == current) {
                             if (c->state() == Call::State::OVER) {
                                 [self animateOut];
                             } else {
                                 [self updateCall];
                             }
                         }
286 287 288
    });
}

289 290 291 292 293 294 295 296 297 298 299 300
- (void) monitorIncomingTextMessages:(Media::Text*) media
{
    /* connect to incoming chat messages to open the chat view */
    QObject::disconnect(self.messageConnection);
    self.messageConnection = QObject::connect(media,
                                              &Media::Text::messageReceived,
                                              [self] (const QMap<QString,QString>& m) {
                                                  if([[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]])
                                                      [self uncollapseRightView];
                                              });
}

301 302
-(void) connectVideoSignals
{
Alexandre Lision's avatar
Alexandre Lision committed
303 304
    QModelIndex idx = CallModel::instance().selectionModel()->currentIndex();
    Call* call = CallModel::instance().getCall(idx);
305
    QObject::disconnect(self.videoStarted);
Alexandre Lision's avatar
Alexandre Lision committed
306
    self.videoStarted = QObject::connect(call,
307 308
                     &Call::videoStarted,
                     [=](Video::Renderer* renderer) {
309 310
                         NSLog(@"Video started!");
                         [self connectVideoRenderer:renderer];
311 312 313 314 315 316 317 318 319 320 321 322 323
                     });

    if(call->videoRenderer())
    {
        [self connectVideoRenderer:call->videoRenderer()];
    }

    [self connectPreviewRenderer];

}

-(void) connectPreviewRenderer
{
324 325 326
    QObject::disconnect(previewHolder.frameUpdated);
    QObject::disconnect(previewHolder.stopped);
    QObject::disconnect(previewHolder.started);
Alexandre Lision's avatar
Alexandre Lision committed
327
    previewHolder.started = QObject::connect(&Video::PreviewManager::instance(),
328 329 330 331 332 333
                     &Video::PreviewManager::previewStarted,
                     [=](Video::Renderer* renderer) {
                         QObject::disconnect(previewHolder.frameUpdated);
                         previewHolder.frameUpdated = QObject::connect(renderer,
                                                                       &Video::Renderer::frameUpdated,
                                                                       [=]() {
Alexandre Lision's avatar
Alexandre Lision committed
334
                                                                           [self renderer:Video::PreviewManager::instance().previewRenderer()
335 336 337 338
                                                                       renderFrameForView:previewView];
                                                                       });
                     });

Alexandre Lision's avatar
Alexandre Lision committed
339
    previewHolder.stopped = QObject::connect(&Video::PreviewManager::instance(),
340 341 342 343 344 345
                     &Video::PreviewManager::previewStopped,
                     [=](Video::Renderer* renderer) {
                         QObject::disconnect(previewHolder.frameUpdated);
                        [previewView.layer setContents:nil];
                     });

Alexandre Lision's avatar
Alexandre Lision committed
346
    previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance().previewRenderer(),
347 348
                                                 &Video::Renderer::frameUpdated,
                                                 [=]() {
Alexandre Lision's avatar
Alexandre Lision committed
349
                                                     [self renderer:Video::PreviewManager::instance().previewRenderer()
350 351 352 353 354 355
                                                            renderFrameForView:previewView];
                                                 });
}

-(void) connectVideoRenderer: (Video::Renderer*)renderer
{
356 357 358
    QObject::disconnect(videoHolder.frameUpdated);
    QObject::disconnect(videoHolder.started);
    QObject::disconnect(videoHolder.stopped);
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
    videoHolder.frameUpdated = QObject::connect(renderer,
                     &Video::Renderer::frameUpdated,
                     [=]() {
                         [self renderer:renderer renderFrameForView:videoView];
                     });

    videoHolder.started = QObject::connect(renderer,
                     &Video::Renderer::started,
                     [=]() {
                         QObject::disconnect(videoHolder.frameUpdated);
                         videoHolder.frameUpdated = QObject::connect(renderer,
                                                                     &Video::Renderer::frameUpdated,
                                                                     [=]() {
                                                                         [self renderer:renderer renderFrameForView:videoView];
                                                                     });
                     });

    videoHolder.stopped = QObject::connect(renderer,
                     &Video::Renderer::stopped,
                     [=]() {
                         QObject::disconnect(videoHolder.frameUpdated);
                        [videoView.layer setContents:nil];
                     });
}

-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
{
    QSize res = renderer->size();

388 389 390 391 392
    auto frame_ptr = renderer->currentFrame();
    auto frame_data = frame_ptr.ptr;
    if (!frame_data)
        return;

393 394

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
395
    CGContextRef newContext = CGBitmapContextCreate(frame_data,
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
                                                    res.width(),
                                                    res.height(),
                                                    8,
                                                    4*res.width(),
                                                    colorSpace,
                                                    kCGImageAlphaPremultipliedLast);


    CGImageRef newImage = CGBitmapContextCreateImage(newContext);

    /*We release some components*/
    CGContextRelease(newContext);
    CGColorSpaceRelease(colorSpace);

    [CATransaction begin];
    view.layer.contents = (__bridge id)newImage;
    [CATransaction commit];

    CFRelease(newImage);
}

- (void) initFrame
{
    [self.view setFrame:self.view.superview.bounds];
    [self.view setHidden:YES];
    self.view.layer.position = self.view.frame.origin;
422
    [self collapseRightView];
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
}

# pragma private IN/OUT animations

-(void) animateIn
{
    NSLog(@"animateIn");
    CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
    [self.view setHidden:NO];

    [CATransaction begin];
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
    [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
    [animation setDuration:0.2f];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
    [CATransaction setCompletionBlock:^{
Alexandre Lision's avatar
Alexandre Lision committed
440 441 442 443

        // when call comes in we want to show the controls/header
        [self mouseIsMoving:YES];

444
        [self connectVideoSignals];
445
        /* check if text media is already present */
Alexandre Lision's avatar
Alexandre Lision committed
446
        if(!CallModel::instance().selectedCall())
447
            return;
448

Alexandre Lision's avatar
Alexandre Lision committed
449
        QObject::connect(CallModel::instance().selectedCall(),
450 451 452 453
                            &Call::changed,
                            [=]() {
                                [self updateCall];
                            });
Alexandre Lision's avatar
Alexandre Lision committed
454 455
        if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
            Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::IN);
456
            [self monitorIncomingTextMessages:text];
Alexandre Lision's avatar
Alexandre Lision committed
457 458
        } else if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
            Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::OUT);
459 460 461
            [self monitorIncomingTextMessages:text];
        } else {
            /* monitor media for messaging text messaging */
Alexandre Lision's avatar
Alexandre Lision committed
462
            self.mediaAddedConnection = QObject::connect(CallModel::instance().selectedCall(),
463 464 465 466 467 468 469
                                                         &Call::mediaAdded,
                                                         [self] (Media::Media* media) {
                                                             if (media->type() == Media::Media::Type::TEXT) {                                                                     [self monitorIncomingTextMessages:(Media::Text*)media];
                                                                 QObject::disconnect(self.mediaAddedConnection);
                                                             }
                                                         });
        }
470 471 472 473 474 475 476 477
    }];
    [self.view.layer addAnimation:animation forKey:animation.keyPath];

    [CATransaction commit];
}

-(void) cleanUp
{
478 479 480 481 482 483
    QObject::disconnect(videoHolder.frameUpdated);
    QObject::disconnect(videoHolder.started);
    QObject::disconnect(videoHolder.stopped);
    QObject::disconnect(previewHolder.frameUpdated);
    QObject::disconnect(previewHolder.stopped);
    QObject::disconnect(previewHolder.started);
484 485
    [videoView.layer setContents:nil];
    [previewView.layer setContents:nil];
Alexandre Lision's avatar
Alexandre Lision committed
486

487
    [_brokerPopoverVC performClose:self];
Alexandre Lision's avatar
Alexandre Lision committed
488
    [self.addToContactPopover performClose:self];
Alexandre Lision's avatar
Alexandre Lision committed
489

490 491 492 493
    [self.chatButton setHidden:YES];
    [self.addParticipantButton setHidden:YES];
    [self.transferButton setHidden:YES];

Alexandre Lision's avatar
Alexandre Lision committed
494
    [self.chatButton setState:NSOffState];
495
    [self.mergeCallsButton setState:NSOffState];
Alexandre Lision's avatar
Alexandre Lision committed
496
    [self collapseRightView];
Alexandre Lision's avatar
Alexandre Lision committed
497 498 499 500 501

    [personLabel setStringValue:@""];
    [timeSpentLabel setStringValue:@""];
    [stateLabel setStringValue:@""];
    [self.addContactButton setHidden:YES];
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
}

-(void) animateOut
{
    NSLog(@"animateOut");
    if(self.view.frame.origin.x < 0) {
        NSLog(@"Already hidden");
        return;
    }

    CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
    [CATransaction begin];
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
    [animation setToValue:[NSValue valueWithPoint:frame.origin]];
    [animation setDuration:0.2f];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];

    [CATransaction setCompletionBlock:^{
        [self.view setHidden:YES];
522
        // first make sure everything is disconnected
523
        [self cleanUp];
524
        if (RecentModel::instance().getActiveCall(RecentModel::instance().selectionModel()->currentIndex())) {
525 526 527 528
            [self animateIn];
        }
    }];
    [self.view.layer addAnimation:animation forKey:animation.keyPath];
529 530

    [self.view.layer setPosition:frame.origin];
531 532 533 534 535 536 537 538 539 540 541
    [CATransaction commit];
}

/**
 *  Debug purpose
 */
-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
{
    NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
}

Alexandre Lision's avatar
Alexandre Lision committed
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
-(void)collapseRightView
{
    NSView *right = [[splitView subviews] objectAtIndex:1];
    NSView *left  = [[splitView subviews] objectAtIndex:0];
    NSRect leftFrame = [left frame];
    [right setHidden:YES];
    [splitView display];
}

-(void)uncollapseRightView
{
    NSView *left  = [[splitView subviews] objectAtIndex:0];
    NSView *right = [[splitView subviews] objectAtIndex:1];
    [right setHidden:NO];

    CGFloat dividerThickness = [splitView dividerThickness];

    // get the different frames
    NSRect leftFrame = [left frame];
    NSRect rightFrame = [right frame];

    leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
    rightFrame.origin.x = leftFrame.size.width + dividerThickness;
    [left setFrameSize:leftFrame.size];
    [right setFrame:rightFrame];
    [splitView display];
Alexandre Lision's avatar
Alexandre Lision committed
568 569

    [self.chatVC takeFocus];
Alexandre Lision's avatar
Alexandre Lision committed
570 571 572 573
}


#pragma mark - Button methods
574

575
- (IBAction)addToContact:(NSButton*) sender {
Alexandre Lision's avatar
Alexandre Lision committed
576
    auto contactmethod = CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->peerContactMethod();
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593

    if (self.addToContactPopover != nullptr) {
        [self.addToContactPopover performClose:self];
        self.addToContactPopover = NULL;
    } else if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
        auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
        [editorVC setMethodToLink:contactmethod];
        [editorVC setContactLinkedDelegate:self];
        self.addToContactPopover = [[NSPopover alloc] init];
        [self.addToContactPopover setContentSize:editorVC.view.frame.size];
        [self.addToContactPopover setContentViewController:editorVC];
        [self.addToContactPopover setAnimates:YES];
        [self.addToContactPopover setBehavior:NSPopoverBehaviorTransient];
        [self.addToContactPopover setDelegate:self];

        [self.addToContactPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSMaxXEdge];
    }
Alexandre Lision's avatar
Alexandre Lision committed
594 595

    [videoView setCallDelegate:nil];
596 597
}

598
- (IBAction)hangUp:(id)sender {
Alexandre Lision's avatar
Alexandre Lision committed
599
    CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
600 601 602
}

- (IBAction)accept:(id)sender {
Alexandre Lision's avatar
Alexandre Lision committed
603
    CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::ACCEPT;
604 605 606
}

- (IBAction)toggleRecording:(id)sender {
Alexandre Lision's avatar
Alexandre Lision committed
607
    CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::RECORD_AUDIO;
608 609 610
}

- (IBAction)toggleHold:(id)sender {
Alexandre Lision's avatar
Alexandre Lision committed
611
    CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::HOLD;
612 613
}

Alexandre Lision's avatar
Alexandre Lision committed
614 615 616 617 618
-(IBAction)toggleChat:(id)sender;
{
    BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
    if (rightViewCollapsed) {
        [self uncollapseRightView];
Alexandre Lision's avatar
Alexandre Lision committed
619
        CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->addOutgoingMedia<Media::Text>();
Alexandre Lision's avatar
Alexandre Lision committed
620 621 622 623 624 625
    } else {
        [self collapseRightView];
    }
    [chatButton setState:rightViewCollapsed];
}

626 627
- (IBAction)muteAudio:(id)sender
{
Alexandre Lision's avatar
Alexandre Lision committed
628
    UserActionModel* uam = CallModel::instance().userActionModel();
629 630 631 632 633
    uam << UserActionModel::Action::MUTE_AUDIO;
}

- (IBAction)muteVideo:(id)sender
{
Alexandre Lision's avatar
Alexandre Lision committed
634
    UserActionModel* uam = CallModel::instance().userActionModel();
635 636
    uam << UserActionModel::Action::MUTE_VIDEO;
}
Alexandre Lision's avatar
Alexandre Lision committed
637 638

- (IBAction)toggleTransferView:(id)sender {
639 640 641
    if (_brokerPopoverVC != nullptr) {
        [_brokerPopoverVC performClose:self];
        _brokerPopoverVC = NULL;
Alexandre Lision's avatar
Alexandre Lision committed
642 643
        [self.transferButton setState:NSOffState];
    } else {
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
        auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::TRANSFER];
        _brokerPopoverVC = [[NSPopover alloc] init];
        [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
        [_brokerPopoverVC setContentViewController:brokerVC];
        [_brokerPopoverVC setAnimates:YES];
        [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
        [_brokerPopoverVC setDelegate:self];
        [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
        [videoView setCallDelegate:nil];
    }
}

- (IBAction)toggleAddParticipantView:(id)sender {
    if (_brokerPopoverVC != nullptr) {
        [_brokerPopoverVC performClose:self];
        _brokerPopoverVC = NULL;
        [self.addParticipantButton setState:NSOffState];
    } else {
        auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::CONFERENCE];
        _brokerPopoverVC = [[NSPopover alloc] init];
        [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
        [_brokerPopoverVC setContentViewController:brokerVC];
        [_brokerPopoverVC setAnimates:YES];
        [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
        [_brokerPopoverVC setDelegate:self];
        [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
Alexandre Lision's avatar
Alexandre Lision committed
670 671
        [videoView setCallDelegate:nil];
    }
672
}
673

674 675 676 677 678 679 680 681 682
/**
 *  Merge current call with its parent call
 */
- (IBAction)mergeCalls:(id)sender
{
    auto current = CallModel::instance().selectedCall();
    current->joinToParent();
}

683 684
#pragma mark - NSPopOverDelegate

Alexandre Lision's avatar
Alexandre Lision committed
685 686
- (void)popoverWillClose:(NSNotification *)notification
{
687 688 689
    if (_brokerPopoverVC != nullptr) {
        [_brokerPopoverVC performClose:self];
        _brokerPopoverVC = NULL;
Alexandre Lision's avatar
Alexandre Lision committed
690 691 692 693 694 695 696
    }

    if (self.addToContactPopover != nullptr) {
        [self.addToContactPopover performClose:self];
        self.addToContactPopover = NULL;
    }

Alexandre Lision's avatar
Alexandre Lision committed
697
    [self.addContactButton setState:NSOffState];
Alexandre Lision's avatar
Alexandre Lision committed
698
    [self.transferButton setState:NSOffState];
699
    [self.addParticipantButton setState:NSOffState];
Alexandre Lision's avatar
Alexandre Lision committed
700 701
}

702 703
- (void)popoverDidClose:(NSNotification *)notification
{
Alexandre Lision's avatar
Alexandre Lision committed
704
    [videoView setCallDelegate:self];
705 706 707 708 709 710 711 712 713 714 715 716
}

#pragma mark - ContactLinkedDelegate

- (void)contactLinked
{
    if (self.addToContactPopover != nullptr) {
        [self.addToContactPopover performClose:self];
        self.addToContactPopover = NULL;
    }
}

Alexandre Lision's avatar
Alexandre Lision committed
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
#pragma mark - NSSplitViewDelegate

/* 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.
 */
- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
{
    NSView* rightView = [[splitView subviews] objectAtIndex:1];
    return ([subview isEqual:rightView]);
}


- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
{
    NSView* rightView = [[splitView subviews] objectAtIndex:1];
    return ([subview isEqual:rightView]);
}


Alexandre Lision's avatar
Alexandre Lision committed
735
# pragma mark - CallnDelegate
Alexandre Lision's avatar
Alexandre Lision committed
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750

- (void) callShouldToggleFullScreen
{
    if(self.splitView.isInFullScreenMode)
        [self.splitView exitFullScreenModeWithOptions:nil];
    else {
        NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
        NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
        NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
                              NSFullScreenModeApplicationPresentationOptions, nil];

        [self.splitView enterFullScreenMode:[NSScreen mainScreen]  withOptions:opts];
    }
}

Alexandre Lision's avatar
Alexandre Lision committed
751 752 753 754 755 756
-(void) mouseIsMoving:(BOOL) move
{
    [[controlsPanel animator] setAlphaValue:move]; // fade out
    [[headerContainer animator] setAlphaValue:move];
}

757
@end