CurrentCallVC.mm 45.6 KB
Newer Older
1
/*
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
 *  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"
20 21 22 23
extern "C" {
#import "libavutil/frame.h"
#import "libavutil/display.h"
}
24 25 26

#import <QuartzCore/QuartzCore.h>

27 28 29 30 31 32 33
///Qt
#import <QMimeData>
#import <QtMacExtras/qmacfunctions.h>
#import <QtCore/qabstractitemmodel.h>
#import <QPixmap>

///LRC
34 35
#import <video/previewmanager.h>
#import <video/renderer.h>
36 37 38
#import <api/newcallmodel.h>
#import <api/call.h>
#import <api/conversationmodel.h>
39
#import <globalinstances.h>
40

41
#import "AppDelegate.h"
42
#import "views/ITProgressIndicator.h"
43
#import "views/CallView.h"
44
#import "views/NSColor+RingTheme.h"
45
#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision's avatar
Alexandre Lision committed
46
#import "ChatVC.h"
47
#import "views/IconButton.h"
48
#import "utils.h"
49
#import "views/CallMTKView.h"
50

51 52 53 54 55 56 57 58 59 60 61 62
@interface RendererConnectionsHolder : NSObject

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

@end

@implementation RendererConnectionsHolder

@end

63
@interface CurrentCallVC () <NSPopoverDelegate> {
64 65 66
    std::string convUid_;
    std::string callUid_;
    const lrc::api::account::Info *accountInfo_;
67
    NSTimer* refreshDurationTimer;
68
}
69

70 71
// Main container
@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
72
@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
73
@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
74

75 76
// Header info
@property (unsafe_unretained) IBOutlet NSView* headerContainer;
77 78
@property (unsafe_unretained) IBOutlet NSTextField* personLabel;
@property (unsafe_unretained) IBOutlet NSTextField* stateLabel;
79
@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
80
@property (unsafe_unretained) IBOutlet NSImageView* personPhoto;
81

82 83 84 85 86
@property (unsafe_unretained) IBOutlet NSImageView* outgoingPhoto;
@property (unsafe_unretained) IBOutlet NSTextField* outgoingPersonLabel;
@property (unsafe_unretained) IBOutlet NSTextField* outgoingStateLabel;
@property (unsafe_unretained) IBOutlet NSTextField* outgoingId;

87 88
// Call Controls
@property (unsafe_unretained) IBOutlet NSView* controlsPanel;
89

90 91 92 93 94 95 96 97 98
@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
@property (unsafe_unretained) IBOutlet IconButton* transferButton;
@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
@property (unsafe_unretained) IBOutlet IconButton* chatButton;
99
@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
100

101 102
@property (unsafe_unretained) IBOutlet NSView* advancedPanel;
@property (unsafe_unretained) IBOutlet IconButton* advancedButton;
103
@property (assign) IBOutlet NSLayoutConstraint* callRecordButtonMarginLeft;
104

105
@property (strong) NSPopover* addToContactPopover;
106
@property (strong) NSPopover* brokerPopoverVC;
107
@property (strong) IBOutlet ChatVC* chatVC;
108

109 110 111 112 113 114 115 116
// Ringing call panel
@property (unsafe_unretained) IBOutlet NSView* ringingPanel;
@property (unsafe_unretained) IBOutlet NSImageView* incomingPersonPhoto;
@property (unsafe_unretained) IBOutlet NSTextField* incomingDisplayName;

// Outgoing call panel
@property (unsafe_unretained) IBOutlet NSView* outgoingPanel;
@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
117

118 119 120 121 122 123 124
// audio only view
@property (unsafe_unretained) IBOutlet NSView* audioCallView;
@property (unsafe_unretained) IBOutlet NSImageView* audioCallPhoto;
@property (unsafe_unretained) IBOutlet NSTextField* audioCallTime;
@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonLabel;
@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonId;

125
// Video
Alexandre Lision's avatar
Alexandre Lision committed
126
@property (unsafe_unretained) IBOutlet CallView *videoView;
127 128 129
@property (unsafe_unretained) IBOutlet CallMTKView *previewView;

@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
130 131 132

@property RendererConnectionsHolder* previewHolder;
@property RendererConnectionsHolder* videoHolder;
133
@property QMetaObject::Connection videoStarted;
134
@property QMetaObject::Connection selectedCallChanged;
135 136
@property QMetaObject::Connection messageConnection;
@property QMetaObject::Connection mediaAddedConnection;
137
@property QMetaObject::Connection profileUpdatedConnection;
138
@property NSImageView *testView;
139 140 141 142

@end

@implementation CurrentCallVC
143
@synthesize personLabel, personPhoto, stateLabel, holdOnOffButton, hangUpButton,
144
            recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
145
            muteVideoButton, muteAudioButton, controlsPanel, advancedPanel, advancedButton, headerContainer, videoView,
146 147 148
            incomingDisplayName, incomingPersonPhoto, previewView, splitView, loadingIndicator, ringingPanel,
            outgoingPanel, outgoingPersonLabel, outgoingStateLabel, outgoingPhoto, outgoingId,
            callRecordButtonMarginLeft, audioCallView, audioCallPhoto, audioCallTime, audioCallPersonLabel, audioCallPersonId, backgroundImage, bluerBackgroundEffect;
149 150 151

@synthesize previewHolder;
@synthesize videoHolder;
152 153 154 155
CVPixelBufferPoolRef pixelBufferPoolDistantView;
CVPixelBufferRef pixelBufferDistantView;
CVPixelBufferPoolRef pixelBufferPoolPreview;
CVPixelBufferRef pixelBufferPreview;
156

157 158 159
-(void) setCurrentCall:(const std::string&)callUid
          conversation:(const std::string&)convUid
               account:(const lrc::api::account::Info*)account;
160
{
161 162 163 164 165 166 167 168
    if(account == nil)
        return;

    auto* callModel = account->callModel.get();

    if (not callModel->hasCall(callUid)){
        callUid_.clear();
        return;
169
    }
170 171 172
    callUid_ = callUid;
    convUid_ = convUid;
    accountInfo_ = account;
Anthony Léonard's avatar
Anthony Léonard committed
173
    [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
174
    auto currentCall = callModel->getCall(callUid_);
175 176 177
    [muteVideoButton setHidden: currentCall.isAudioOnly ? YES: NO];
    callRecordButtonMarginLeft.constant = currentCall.isAudioOnly ? -40.0f: 10.0f;
    [previewView setHidden: YES];
178
    videoView.callId = callUid;
179 180
}

181
- (void)awakeFromNib
182
{
183 184 185 186 187 188 189 190 191
    [self.view setWantsLayer:YES];
    [controlsPanel setWantsLayer:YES];
    [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
    [controlsPanel.layer setFrame:controlsPanel.frame];

    previewHolder = [[RendererConnectionsHolder alloc] init];
    videoHolder = [[RendererConnectionsHolder alloc] init];

    [loadingIndicator setColor:[NSColor whiteColor]];
192 193 194 195
    [loadingIndicator setNumberOfLines:200];
    [loadingIndicator setWidthOfLine:4];
    [loadingIndicator setLengthOfLine:4];
    [loadingIndicator setInnerMargin:59];
196 197

    [self.videoView setCallDelegate:self];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
198 199
    CGColor* color = [[[NSColor blackColor] colorWithAlphaComponent:0.2] CGColor];
    [headerContainer.layer setBackgroundColor:color];
200 201 202 203
    [bluerBackgroundEffect setWantsLayer:YES];
    bluerBackgroundEffect.alphaValue = 0.6;
    [audioCallView setWantsLayer:YES];
    [audioCallView.layer setBackgroundColor: [[NSColor clearColor] CGColor]];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
204 205
    [backgroundImage setWantsLayer: YES];
    backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
206 207
}

208 209 210 211 212 213 214 215 216
-(void) updateDurationLabel
{
    if (accountInfo_ != nil) {
        auto* callModel = accountInfo_->callModel.get();
        if (callModel->hasCall(callUid_)) {
            auto& callStatus = callModel->getCall(callUid_).status;
            if (callStatus != lrc::api::call::Status::ENDED &&
                callStatus != lrc::api::call::Status::TERMINATING &&
                callStatus != lrc::api::call::Status::INVALID) {
217 218 219 220 221
                if(callModel->getCall(callUid_).isAudioOnly) {
                    [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
                } else {
                    [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
                }
222 223 224 225 226 227 228 229 230 231
                return;
            }
        }
    }

    // If call is not running anymore or accountInfo_ is not set for any reason
    // we stop the refresh loop
    [refreshDurationTimer invalidate];
    refreshDurationTimer = nil;
    [timeSpentLabel setHidden:YES];
232
    [audioCallView setHidden:YES];
233 234
}

235
-(void) updateCall
236
{
237 238 239 240 241
    if (accountInfo_ == nil)
        return;

    auto* callModel = accountInfo_->callModel.get();
    if (not callModel->hasCall(callUid_)) {
242 243
        return;
    }
244

245 246
    auto currentCall = callModel->getCall(callUid_);
    NSLog(@"\n status %@ \n",@(lrc::api::call::to_string(currentCall.status).c_str()));
247
    auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel);
248 249 250 251
    if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
        NSString* bestName = bestNameForConversation(*convIt, *accountInfo_->conversationModel);
        [personLabel setStringValue:bestName];
        [outgoingPersonLabel setStringValue:bestName];
252
        [audioCallPersonLabel setStringValue:bestName];
253 254 255 256 257
        NSString* ringID = bestIDForConversation(*convIt, *accountInfo_->conversationModel);
        if([bestName isEqualToString:ringID]) {
            ringID = @"";
        }
        [outgoingId setStringValue:ringID];
258
        [audioCallPersonId setStringValue:ringID];
259 260
    }

261
    [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
262
    [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
263 264 265 266 267 268
    if (refreshDurationTimer == nil)
        refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                                target:self
                                                              selector:@selector(updateDurationLabel)
                                                              userInfo:nil
                                                               repeats:YES];
269
    [stateLabel setStringValue:@(to_string(currentCall.status).c_str())];
270
    [outgoingStateLabel setStringValue:@(to_string(currentCall.status).c_str())];
271

272
    // Default values for this views
273
    [loadingIndicator setHidden:YES];
274 275 276 277
    [ringingPanel setHidden:YES];
    [outgoingPanel setHidden:YES];
    [controlsPanel setHidden:NO];
    [headerContainer setHidden:NO];
Anthony Léonard's avatar
Anthony Léonard committed
278
    [chatButton setHidden:NO];
279
    [self setBackground];
280

281
    using Status = lrc::api::call::Status;
282 283
    holdOnOffButton.image = currentCall.status == lrc::api::call::Status::PAUSED ?
    [NSImage imageNamed:@"ic_action_holdoff.png"] : [NSImage imageNamed:@"ic_action_hold.png"];
284 285 286
    switch (currentCall.status) {
        case Status::SEARCHING:
        case Status::CONNECTING:
287 288 289 290
            [headerContainer setHidden:YES];
            [outgoingPanel setHidden:NO];
            [outgoingPhoto setHidden:NO];
            [self setupContactInfo:outgoingPhoto];
291
            [loadingIndicator setHidden:NO];
292
            [controlsPanel setHidden:YES];
293
            break;
294
        case Status::INCOMING_RINGING:
295
            [outgoingPhoto setHidden:YES];
296 297 298
            [controlsPanel setHidden:YES];
            [outgoingPanel setHidden:YES];
            [self setupIncoming:callUid_];
299
            break;
300 301 302
        case Status::OUTGOING_RINGING:
            [controlsPanel setHidden:YES];
            [outgoingPanel setHidden:NO];
303 304
            [loadingIndicator setHidden:NO];
            [headerContainer setHidden:YES];
305
            break;
306 307 308
        /*case Status::CONFERENCE:
            [self setupConference:currentCall];
            break;*/
309
        case Status::PAUSED:
310 311 312 313 314 315 316 317 318
            [self.videoMTKView fillWithBlack];
            [self.previewView fillWithBlack];
            [bluerBackgroundEffect setHidden:NO];
            [backgroundImage setHidden:NO];
            [self.previewView setHidden: YES];
            [self.videoMTKView setHidden: YES];
            self.previewView.stopRendering = true;
            self.videoMTKView.stopRendering = true;
            break;
319
        case Status::INACTIVE:
320 321 322 323 324 325
            if(currentCall.isAudioOnly) {
                [self setUpAudioOnlyView];
            } else {
                [self setUpVideoCallView];
            }
            break;
326
        case Status::IN_PROGRESS:
327 328 329 330 331 332
            self.previewView.stopRendering = false;
            self.videoMTKView.stopRendering = false;
            [previewView fillWithBlack];
            [self.videoMTKView fillWithBlack];
            [self.previewView setHidden: NO];
            [self.videoMTKView setHidden: NO];
333 334 335 336 337
            if(currentCall.isAudioOnly) {
                [self setUpAudioOnlyView];
            } else {
                [self setUpVideoCallView];
            }
338
            break;
339
        case Status::CONNECTED:
340
            break;
341 342 343
        case Status::ENDED:
        case Status::TERMINATING:
        case Status::INVALID:
344 345 346 347
            break;
    }
}

348 349 350 351 352 353
-(void) setUpVideoCallView {
    [self setupContactInfo:personPhoto];
    [timeSpentLabel setHidden:NO];
    [outgoingPhoto setHidden:YES];
    [headerContainer setHidden:NO];
    [previewView setHidden: NO];
354
    [self.videoMTKView setHidden:NO];
355 356
    [bluerBackgroundEffect setHidden:YES];
    [backgroundImage setHidden:YES];
357
    [audioCallView setHidden:YES];
358 359 360 361 362
}

-(void) setUpAudioOnlyView {
    [audioCallView setHidden:NO];
    [headerContainer setHidden:YES];
363 364
    [self.previewView setHidden: YES];
    [self.videoMTKView setHidden: YES];
365 366 367 368
    [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
}

-(void) setBackground {
369 370
    auto* convModel = accountInfo_->conversationModel.get();
    auto it = getConversationFromUid(convUid_, *convModel);
371 372
    NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
    if(image) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
373 374 375 376 377
        CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
        CIContext *context = [[CIContext alloc] init];
        CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
        [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
        [clamp setValue:ciImage forKey: kCIInputImageKey];
378 379
        CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
        [bluerFilter setDefaults];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
380 381 382 383 384 385 386
        [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
        [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
        CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
        CGRect extent = [result extent];
        CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
        NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
        backgroundImage.layer.contents = bluredImage;
387 388 389 390 391 392 393
        [backgroundImage setHidden:NO];
    } else {
        [bluerBackgroundEffect setFillColor:[NSColor ringDarkGrey]];
        [backgroundImage setHidden:YES];
    }
}

394
-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
395 396 397 398 399 400
    auto* convModel = accountInfo_->conversationModel.get();
    auto convIt = getConversationFromUid(convUid_, *convModel);
    if (convIt == convModel->allFilteredConversations().end()) {
        return nil;
    }
    if(shouldDrawDefault) {
401
        auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
402 403
        QVariant photo = imgManip.conversationPhoto(*convIt, *accountInfo_, QSize(size, size), NO);
        return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
404
    }
405 406 407 408 409 410 411 412
    auto contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
    NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(contact.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
    return [[NSImage alloc] initWithData:imageData];
}

-(void) setupContactInfo:(NSImageView*)imageView
{
    [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
413 414
}

415
-(void) setupIncoming:(const std::string&) callId
416
{
417 418 419 420 421 422 423 424 425 426 427
    if (accountInfo_ == nil)
        return;

    auto* callModel = accountInfo_->callModel.get();
    if (not callModel->hasCall(callUid_)) {
        return;
    }

    auto call = callModel->getCall(callUid_);
    auto* convModel = accountInfo_->conversationModel.get();

428 429 430
    [ringingPanel setHidden:NO];
    [controlsPanel setHidden:YES];
    [headerContainer setHidden:YES];
431
    auto it = getConversationFromUid(convUid_, *convModel);
432
    if (it != convModel->allFilteredConversations().end()) {
433
        [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
434
        [incomingDisplayName setStringValue:bestNameForConversation(*it, *convModel)];
435
    }
436 437 438 439 440 441 442 443 444 445
}

-(void) setupConference:(Call*) c
{
    [videoView setShouldAcceptInteractions:YES];
    [self.chatButton setHidden:NO];
    [self.addParticipantButton setHidden:NO];
    [self.transferButton setHidden:YES];
}

446
-(void)collapseRightView
447
{
448 449 450 451 452
    NSView *right = [[splitView subviews] objectAtIndex:1];
    NSView *left  = [[splitView subviews] objectAtIndex:0];
    NSRect leftFrame = [left frame];
    [right setHidden:YES];
    [splitView display];
453 454
}

455
- (void) changeCallSelection:(std::string&)callUid
456
{
457 458
    if (accountInfo_ == nil)
        return;
459

460 461 462 463
    auto* callModel = accountInfo_->callModel.get();
    if (not callModel->hasCall(callUid)) {
        return;
    }
464 465

    QObject::disconnect(self.selectedCallChanged);
466 467 468
    self.selectedCallChanged = QObject::connect(callModel,
                                                &lrc::api::NewCallModel::callStatusChanged,
                                                [self](const std::string callId) {
469
                                                    [self updateCall];
470
                                                });
471 472 473 474
}

-(void) connectVideoSignals
{
475 476
    if (accountInfo_ == nil)
        return;
477

478 479 480 481 482
    auto* callModel = accountInfo_->callModel.get();
    QObject::disconnect(self.videoStarted);
    self.videoStarted = QObject::connect(callModel,
                                         &lrc::api::NewCallModel::remotePreviewStarted,
                                         [self](const std::string& callId, Video::Renderer* renderer) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
483
                                             [videoView setShouldAcceptInteractions:YES];
484
                                             [self.videoMTKView setHidden:NO];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
485
                                             [self mouseIsMoving: NO];
486 487 488 489 490 491
                                             [self connectVideoRenderer:renderer];
                                         });

    if (callModel->hasCall(callUid_)) {
        if (auto renderer = callModel->getRenderer(callUid_)) {
            QObject::disconnect(self.videoStarted);
492
            [self.videoMTKView setHidden:NO];
493 494
            [self connectVideoRenderer: renderer];
        }
495 496 497 498 499 500
    }
    [self connectPreviewRenderer];
}

-(void) connectPreviewRenderer
{
501 502 503
    QObject::disconnect(previewHolder.frameUpdated);
    QObject::disconnect(previewHolder.stopped);
    QObject::disconnect(previewHolder.started);
504 505
    previewHolder.started =
    QObject::connect(&Video::PreviewManager::instance(),
506 507
                     &Video::PreviewManager::previewStarted,
                     [=](Video::Renderer* renderer) {
508 509
                         [self.previewView setHidden:NO];
                         self.previewView.stopRendering = false;
510
                         QObject::disconnect(previewHolder.frameUpdated);
511 512 513 514 515 516 517 518 519 520
                         previewHolder.frameUpdated =
                         QObject::connect(renderer,
                                          &Video::Renderer::frameUpdated,
                                          [=]() {
                                              if(!renderer->isRendering()) {
                                                  return;
                                              }
                                              [self renderer:renderer renderFrameForPreviewView: self.previewView];
                         });
    });
521
    previewHolder.stopped = QObject::connect(&Video::PreviewManager::instance(),
522 523
                     &Video::PreviewManager::previewStopped,
                     [=](Video::Renderer* renderer) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
524
                        QObject::disconnect(previewHolder.frameUpdated);
525 526
                        [self.previewView setHidden:YES];
                        self.previewView.stopRendering = true;
527 528
                     });

529 530 531 532 533 534 535 536 537 538 539
    previewHolder.frameUpdated =
    QObject::connect(Video::PreviewManager::instance().previewRenderer(),
                     &Video::Renderer::frameUpdated,
                     [=]() {
                         if(!Video::PreviewManager::instance()
                            .previewRenderer()->isRendering()) {
                             return;
                         }
                         [self renderer:Video::PreviewManager::instance()
                          .previewRenderer() renderFrameForPreviewView: self.previewView];
    });
540 541 542 543
}

-(void) connectVideoRenderer: (Video::Renderer*)renderer
{
544 545 546
    QObject::disconnect(videoHolder.frameUpdated);
    QObject::disconnect(videoHolder.started);
    QObject::disconnect(videoHolder.stopped);
547 548 549 550

    if(renderer == nil)
        return;

551 552 553
    videoHolder.frameUpdated = QObject::connect(renderer,
                     &Video::Renderer::frameUpdated,
                     [=]() {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
554 555 556
                         if(!renderer->isRendering()) {
                             return;
                         }
557
                         [self renderer:renderer renderFrameForDistantView:self.videoMTKView];
558 559
                     });

560 561
    videoHolder.started =
    QObject::connect(renderer,
562 563
                     &Video::Renderer::started,
                     [=]() {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
564
                         [self mouseIsMoving: NO];
565 566 567 568
                         self.videoMTKView.stopRendering = false;
                         [self.videoMTKView setHidden:NO];
                         [bluerBackgroundEffect setHidden:YES];
                         [backgroundImage setHidden:YES];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
569
                         [videoView setShouldAcceptInteractions:YES];
570
                         QObject::disconnect(videoHolder.frameUpdated);
571 572 573 574 575 576 577 578 579
                         videoHolder.frameUpdated
                         = QObject::connect(renderer,
                                            &Video::Renderer::frameUpdated,
                                            [=]() {
                                                if(!renderer->isRendering()) {
                                                    return;
                                                }
                                                [self renderer:renderer renderFrameForDistantView:self.videoMTKView];
                                            });
580 581 582 583 584
                     });

    videoHolder.stopped = QObject::connect(renderer,
                     &Video::Renderer::stopped,
                     [=]() {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
585
                         [self mouseIsMoving: YES];
586 587 588 589
                         self.videoMTKView.stopRendering = true;
                         [self.videoMTKView setHidden:YES];
                         [bluerBackgroundEffect setHidden:NO];
                         [backgroundImage setHidden:NO];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
590
                         [videoView setShouldAcceptInteractions:NO];
591 592 593 594
                         QObject::disconnect(videoHolder.frameUpdated);
                     });
}

595
-(void) renderer: (Video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
596
{
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
    @autoreleasepool {
        auto framePtr = renderer->currentAVFrame();
        auto frame = framePtr.get();
        if(!frame || !frame->width || !frame->height) {
            return;
        }
        auto frameSize = CGSizeMake(frame->width, frame->height);
        auto rotation = 0;
        if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
            [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
                                   size: frameSize
                               rotation: rotation
                              fillFrame: true];
            return;
        }
        else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
            [view renderWithPixelBuffer: pixelBuffer
                                   size: frameSize
                               rotation: rotation
                              fillFrame: true];
        }
    }
619 620
}

621
-(void) renderer: (Video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
622
{
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 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 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
    @autoreleasepool {
        auto framePtr = renderer->currentAVFrame();
        auto frame = framePtr.get();
        if(!frame || !frame->width || !frame->height)  {
            return;
        }
        auto frameSize = CGSizeMake(frame->width, frame->height);
        auto rotation = 0;
        if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
            const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
            rotation = av_display_rotation_get(data);
        }
        if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
            [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
                                   size: frameSize
                               rotation: rotation
                              fillFrame: false];
        }
        if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
            [view renderWithPixelBuffer: pixelBuffer
                                   size: frameSize
                               rotation: rotation
                              fillFrame: false];
        }
    }
}

-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
    if(!frame || !frame->data[0] || !frame->data[1]) {
        return nil;
    }
    CVReturn theError;
    bool createPool = false;
    if (!pixelBufferPoolPreview) {
        createPool = true;
    } else {
        NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetAttributes(pixelBufferPoolPreview);
        int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
        int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
        if (width != frame->width || height != frame->height) {
            createPool = true;
        }
    }
    if (createPool) {
        CVPixelBufferPoolRelease(pixelBufferPoolPreview);
        CVPixelBufferRelease(pixelBufferPreview);
        pixelBufferPreview = nil;
        pixelBufferPoolPreview = nil;
        NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
        [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
        [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
        [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
        [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
        [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
        theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolPreview);
        if (theError != kCVReturnSuccess) {
            NSLog(@"CVPixelBufferPoolCreate Failed");
            return nil;
        }
    }
    if(!pixelBufferPreview) {
        theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolPreview, &pixelBufferPreview);
        if(theError != kCVReturnSuccess) {
            NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
            return nil;
        }
    }
    theError = CVPixelBufferLockBaseAddress(pixelBufferPreview, 0);
    if (theError != kCVReturnSuccess) {
        NSLog(@"lock error");
        return nil;
    }
    size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 0);
    size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 1);
    void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 0);
    memcpy(base, frame->data[0], bytePerRowY * frame->height);
    base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 1);
    memcpy(base, frame->data[1], bytesPerRowUV * frame->height/2);
    CVPixelBufferUnlockBaseAddress(pixelBufferPreview, 0);
    return pixelBufferPreview;
}
704

705 706 707 708 709 710 711 712 713 714 715 716 717 718
-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
    if(!frame || !frame->data[0] || !frame->data[1]) {
        return nil;
    }
    CVReturn theError;
    bool createPool = false;
    if (!pixelBufferPoolDistantView){
        createPool = true;
    }
    NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolDistantView);
    int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
    int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
    if (width != frame->width || height != frame->height) {
        createPool = true;
719
    }
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
    if (createPool) {
        CVPixelBufferPoolRelease(pixelBufferPoolDistantView);
        CVPixelBufferRelease(pixelBufferDistantView);
        pixelBufferDistantView = nil;
        pixelBufferPoolDistantView = nil;
        NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
        [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
        [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
        [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
        [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
        [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
        theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolDistantView);
        if (theError != kCVReturnSuccess) {
            return nil;
            NSLog(@"CVPixelBufferPoolCreate Failed");
        }
    }
    if(!pixelBufferDistantView) {
        theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolDistantView, &pixelBufferDistantView);
        if(theError != kCVReturnSuccess) {
            return nil;
            NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
        }
    }
    theError = CVPixelBufferLockBaseAddress(pixelBufferDistantView, 0);
    if (theError != kCVReturnSuccess) {
        return nil;
        NSLog(@"lock error");
    }
    size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 0);
    size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 1);
    void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 0);
    memcpy(base, frame->data[0], bytePerRowY * frame->height);
    base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 1);
    uint32_t size = frame->linesize[1] * frame->height / 2;
    uint8_t* dstData = new uint8_t[2 * size];
    uint8_t * firstData = new uint8_t[size];
    memcpy(firstData, frame->data[1], size);
    uint8_t * secondData  = new uint8_t[size];
    memcpy(secondData, frame->data[2], size);
    for (int i = 0; i < 2 * size; i++){
        if (i % 2 == 0){
            dstData[i] = firstData[i/2];
        }else {
            dstData[i] = secondData[i/2];
        }
    }
    memcpy(base, dstData, bytesPerRowUV * frame->height/2);
    CVPixelBufferUnlockBaseAddress(pixelBufferDistantView, 0);
    free(dstData);
    free(firstData);
    free(secondData);
    return pixelBufferDistantView;
773 774
}

775 776 777 778 779
- (void) initFrame
{
    [self.view setFrame:self.view.superview.bounds];
    [self.view setHidden:YES];
    self.view.layer.position = self.view.frame.origin;
780
    [self collapseRightView];
781 782
    self.testView = [[NSImageView alloc] initWithFrame:self.view.frame];
    [self.view addSubview:self.testView];
783 784 785 786
}

# pragma private IN/OUT animations

787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
-(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];

    [self.chatVC takeFocus];
}

808 809
-(void) cleanUp
{
810 811
    if(self.splitView.isInFullScreenMode)
        [self.splitView exitFullScreenModeWithOptions:nil];
812 813 814 815 816 817
    QObject::disconnect(videoHolder.frameUpdated);
    QObject::disconnect(videoHolder.started);
    QObject::disconnect(videoHolder.stopped);
    QObject::disconnect(previewHolder.frameUpdated);
    QObject::disconnect(previewHolder.stopped);
    QObject::disconnect(previewHolder.started);
818
    QObject::disconnect(self.messageConnection);
819

820
    [self.chatButton setHidden:YES];
821
    [self.chatButton setPressed:NO];
822
    [self collapseRightView];
Alexandre Lision's avatar
Alexandre Lision committed
823 824 825 826

    [personLabel setStringValue:@""];
    [timeSpentLabel setStringValue:@""];
    [stateLabel setStringValue:@""];
827 828 829 830 831 832 833 834 835 836
    //audio view
    [audioCallTime setStringValue:@""];
    [audioCallPersonId setStringValue:@""];
    [audioCallPersonLabel setStringValue:@""];
    [audioCallView setHidden:YES];
    [audioCallPhoto setImage:nil];
    //background view
    [bluerBackgroundEffect setHidden:NO];
    [backgroundImage setHidden:NO];
    //outgoing view
837 838
    [outgoingPersonLabel setStringValue:@""];
    [outgoingStateLabel setStringValue:@""];
839 840
    [self.previewView setHidden:YES];
    [self.videoMTKView setHidden:YES];
841 842
}

843 844 845 846 847 848 849 850 851
-(void) setupCallView
{
    if (accountInfo_ == nil)
        return;

    auto* callModel = accountInfo_->callModel.get();
    auto* convModel = accountInfo_->conversationModel.get();

    // when call comes in we want to show the controls/header
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
852 853
    [self mouseIsMoving: YES];
    [videoView setShouldAcceptInteractions: NO];
854 855 856 857 858 859 860

    [self connectVideoSignals];
    /* check if text media is already present */
    if(not callModel->hasCall(callUid_))
        return;

    [loadingIndicator setAnimates:YES];
861
    [self updateCall];
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876

    /* monitor media for messaging text messaging */
    QObject::disconnect(self.messageConnection);
    self.messageConnection = QObject::connect(convModel,
                                              &lrc::api::ConversationModel::interactionStatusUpdated,
                                              [self] (std::string convUid,
                                                      uint64_t msgId,
                                                      lrc::api::interaction::Info msg) {
                                                  if (msg.type == lrc::api::interaction::Type::TEXT) {
                                                      if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
                                                          return;
                                                      }
                                                      [self uncollapseRightView];
                                                  }
                                              });
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    //monitor for updated profile
    QObject::disconnect(self.profileUpdatedConnection);
    self.profileUpdatedConnection =
    QObject::connect(accountInfo_->contactModel.get(),
                     &lrc::api::ContactModel::contactAdded,
                     [self](const std::string &contactUri) {
                         auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
                         if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
                             return;
                         }
                         if (convIt->participants.empty()) {
                             return;

                         }
                         auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
                         if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
                             accountInfo_->conversationModel->makePermanent(convUid_);
                         [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
                         [outgoingPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
896
                         [self.delegate conversationInfoUpdatedFor:convUid_];
897 898 899 900 901 902 903
                         if(accountInfo_->callModel.get()->getCall(callUid_).isAudioOnly) {
                         [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
                             [self setBackground];
                             return;
                         }
                         [personPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
                     });
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
}

-(void) showWithAnimation:(BOOL)animate
{
    if (!animate) {
        [self.view setHidden:NO];
        [self setupCallView];
        return;
    }

    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 functionWithName:kCAMediaTimingFunctionEaseIn]];
    [CATransaction setCompletionBlock:^{
        [self setupCallView];
    }];

    [self.view.layer addAnimation:animation forKey:animation.keyPath];
    [CATransaction commit];
}

-(void) hideWithAnimation:(BOOL)animate
932 933 934 935 936
{
    if(self.view.frame.origin.x < 0) {
        return;
    }

937 938 939 940 941
    if (!animate) {
        [self.view setHidden:YES];
        return;
    }

942 943 944 945 946 947
    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];
948
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
949 950 951

    [CATransaction setCompletionBlock:^{
        [self.view setHidden:YES];
952
        // first make sure everything is disconnected
953
        [self cleanUp];
954 955 956
//        if (currentCall_) {
//            [self animateIn];
//        }
957 958
    }];
    [self.view.layer addAnimation:animation forKey:animation.keyPath];
959 960

    [self.view.layer setPosition:frame.origin];
961 962 963 964 965 966 967 968 969 970 971
    [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);
}

972
#pragma mark - Button methods
Alexandre Lision's avatar
Alexandre Lision committed
973

974 975 976
- (IBAction)hangUp:(id)sender {
    if (accountInfo_ == nil)
        return;
Alexandre Lision's avatar
Alexandre Lision committed
977

978 979
    auto* callModel = accountInfo_->callModel.get();
    callModel->hangUp(callUid_);
Alexandre Lision's avatar
Alexandre Lision committed
980 981
}

982 983 984
- (IBAction)accept:(id)sender {
    if (accountInfo_ == nil)
        return;
Alexandre Lision's avatar
Alexandre Lision committed
985

986 987 988 989 990 991 992 993
    // If we accept a conversation with a non trusted contact, we first accept it
    auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
    if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
        auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
        if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
            accountInfo_->conversationModel->makePermanent(convUid_);
    }

994
    auto* callModel = accountInfo_->callModel.get();
995

996
    callModel->accept(callUid_);
997 998
}

999 1000 1001
- (IBAction)toggleRecording:(id)sender {
    if (accountInfo_ == nil)
        return;
1002

1003
    auto* callModel = accountInfo_->callModel.get();
1004

1005
    callModel->toggleAudioRecord(callUid_);
1006
    [recordOnOffButton setPressed:!recordOnOffButton.isPressed];
1007 1008 1009
}

- (IBAction)toggleHold:(id)sender {
1010 1011 1012 1013
    if (accountInfo_ == nil)
        return;

    auto* callModel = accountInfo_->callModel.get();
1014 1015 1016 1017 1018 1019
    auto currentCall = callModel->getCall(callUid_);
    if (currentCall.status != lrc::api::call::Status::PAUSED) {
        holdOnOffButton.image = [NSImage imageNamed:@"ic_action_holdoff.png"];
    } else {
        holdOnOffButton.image = [NSImage imageNamed:@"ic_action_hold.png"];
    }
1020 1021

    callModel->togglePause(callUid_);
1022 1023
}

1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
- (IBAction)toggleAdvancedControls:(id)sender {
    [advancedButton setPressed:!advancedButton.isPressed];
    [advancedPanel setHidden:![advancedButton isPressed]];
}

- (IBAction)showDialpad:(id)sender {
    AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
    [appDelegate showDialpad];
}

Alexandre Lision's avatar
Alexandre Lision committed
1034 1035 1036 1037 1038 1039 1040 1041
-(IBAction)toggleChat:(id)sender;
{
    BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
    if (rightViewCollapsed) {
        [self uncollapseRightView];
    } else {
        [self collapseRightView];
    }
1042
    [chatButton setPressed:rightViewCollapsed];
Alexandre Lision's avatar
Alexandre Lision committed
1043 1044
}

1045 1046
- (IBAction)muteAudio:(id)sender
{
1047 1048 1049 1050
    if (accountInfo_ == nil)
        return;

    auto* callModel = accountInfo_->callModel.get();
1051 1052 1053 1054 1055 1056
    auto currentCall = callModel->getCall(callUid_);
    if (currentCall.audioMuted) {
        muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
    } else {
       muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
    }
1057
    callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
1058 1059 1060 1061
}

- (IBAction)muteVideo:(id)sender
{
1062 1063 1064
    if (accountInfo_ == nil)
        return;
    auto* callModel = accountInfo_->callModel.get();
1065 1066 1067 1068 1069 1070 1071 1072
    auto currentCall = callModel->getCall(callUid_);
    if(!currentCall.isAudioOnly) {
        if (currentCall.videoMuted) {
            muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
        } else {
            muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
        }
    }
1073
    callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
1074
}
1075 1076

- (IBAction)toggleTransferView:(id)sender {
1077

1078 1079 1080
}

- (IBAction)toggleAddParticipantView:(id)sender {
1081
    
1082
}
1083

1084 1085
#pragma mark - NSPopOverDelegate

1086 1087
- (void)popoverWillClose:(NSNotification *)notification
{
1088 1089 1090
    if (_brokerPopoverVC != nullptr) {
        [_brokerPopoverVC performClose:self];
        _brokerPopoverVC = NULL;
1091 1092 1093 1094 1095 1096 1097
    }

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

1098
    [self.transferButton setPressed:NO];
1099
    [self.addParticipantButton setState:NSOffState];
1100 1101
}

1102 1103
- (void)popoverDidClose:(NSNotification *)notification
{
1104
    [videoView setCallDelegate:self];
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
}

#pragma mark - ContactLinkedDelegate

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

Alexandre Lision's avatar
Alexandre Lision committed
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
#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]);
}


1135
# pragma mark - CallnDelegate
Alexandre Lision's avatar
Alexandre Lision committed
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150

- (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];
    }
}

1151 1152 1153 1154 1155 1156
-(void) mouseIsMoving:(BOOL) move
{
    [[controlsPanel animator] setAlphaValue:move]; // fade out
    [[headerContainer animator] setAlphaValue:move];
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
1157 1158 1159 1160 1161
- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
{
    return YES;
}

1162
@end