CurrentCallVC.mm 28.9 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 setHighlighted:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? YES : NO];
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
                             return;
                         }
250

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

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

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

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

Alexandre Lision's avatar
Alexandre Lision committed
271
    QObject::connect(&CallModel::instance(),
272 273
                     &CallModel::callStateChanged,
                     [self](Call* c, Call::State state) {
Alexandre Lision's avatar
Alexandre Lision committed
274 275 276 277 278 279 280 281 282 283
                         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];
                             }
                         }
284 285 286
    });
}

287 288 289 290 291 292 293 294 295 296 297 298
- (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];
                                              });
}

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

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

    [self connectPreviewRenderer];

}

-(void) connectPreviewRenderer
{
322 323 324
    QObject::disconnect(previewHolder.frameUpdated);
    QObject::disconnect(previewHolder.stopped);
    QObject::disconnect(previewHolder.started);
Alexandre Lision's avatar
Alexandre Lision committed
325
    previewHolder.started = QObject::connect(&Video::PreviewManager::instance(),
326 327 328 329 330 331
                     &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
332
                                                                           [self renderer:Video::PreviewManager::instance().previewRenderer()
333 334 335 336
                                                                       renderFrameForView:previewView];
                                                                       });
                     });

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

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

-(void) connectVideoRenderer: (Video::Renderer*)renderer
{
354 355 356
    QObject::disconnect(videoHolder.frameUpdated);
    QObject::disconnect(videoHolder.started);
    QObject::disconnect(videoHolder.stopped);
357 358 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
    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();

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

391 392

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
393
    CGContextRef newContext = CGBitmapContextCreate(frame_data,
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
                                                    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;
420
    [self collapseRightView];
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
}

# 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
438 439 440 441

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

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

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

    [CATransaction commit];
}

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

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

488 489 490 491
    [self.chatButton setHidden:YES];
    [self.addParticipantButton setHidden:YES];
    [self.transferButton setHidden:YES];

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

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

-(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];
520
        // first make sure everything is disconnected
521
        [self cleanUp];
522
        if (RecentModel::instance().getActiveCall(RecentModel::instance().selectionModel()->currentIndex())) {
523 524 525 526
            [self animateIn];
        }
    }];
    [self.view.layer addAnimation:animation forKey:animation.keyPath];
527 528

    [self.view.layer setPosition:frame.origin];
529 530 531 532 533 534 535 536 537 538 539
    [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
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
-(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
566 567

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


#pragma mark - Button methods
572

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

    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
592 593

    [videoView setCallDelegate:nil];
594 595
}

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

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

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

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

Alexandre Lision's avatar
Alexandre Lision committed
612 613 614 615 616
-(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
617
        CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->addOutgoingMedia<Media::Text>();
Alexandre Lision's avatar
Alexandre Lision committed
618 619 620 621 622 623
    } else {
        [self collapseRightView];
    }
    [chatButton setState:rightViewCollapsed];
}

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

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

- (IBAction)toggleTransferView:(id)sender {
637 638 639
    if (_brokerPopoverVC != nullptr) {
        [_brokerPopoverVC performClose:self];
        _brokerPopoverVC = NULL;
Alexandre Lision's avatar
Alexandre Lision committed
640 641
        [self.transferButton setState:NSOffState];
    } else {
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
        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
668 669
        [videoView setCallDelegate:nil];
    }
670
}
671

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

681 682
#pragma mark - NSPopOverDelegate

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

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

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

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

#pragma mark - ContactLinkedDelegate

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

Alexandre Lision's avatar
Alexandre Lision committed
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
#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
733
# pragma mark - CallnDelegate
Alexandre Lision's avatar
Alexandre Lision committed
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748

- (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
749 750 751 752 753 754
-(void) mouseIsMoving:(BOOL) move
{
    [[controlsPanel animator] setAlphaValue:move]; // fade out
    [[headerContainer animator] setAlphaValue:move];
}

755
@end