blob: e234abeaa12bd674ee572fb821a7c0ef61c2e61f [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lisionc5148052015-03-04 15:10:35 -05003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lisionc5148052015-03-04 15:10:35 -050018 */
19#import "CurrentCallVC.h"
20
21#import <QuartzCore/QuartzCore.h>
22
23#import <call.h>
24#import <callmodel.h>
Alexandre Lision89edc6a2015-11-09 11:30:47 -050025#import <recentmodel.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050026#import <useractionmodel.h>
Alexandre Lision89edc6a2015-11-09 11:30:47 -050027#import <QMimeData>
Alexandre Lisionc5148052015-03-04 15:10:35 -050028#import <contactmethod.h>
29#import <qabstractitemmodel.h>
30#import <QItemSelectionModel>
31#import <QItemSelection>
Alexandre Lisionc5148052015-03-04 15:10:35 -050032#import <video/previewmanager.h>
33#import <video/renderer.h>
Alexandre Lision58cab672015-06-09 15:25:40 -040034#import <media/text.h>
Alexandre Lision2db8f472015-07-22 15:05:46 -040035#import <person.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050036
Alexandre Lisionf47a2562015-06-15 15:48:29 -040037#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040038#import "views/CallView.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040039#import "PersonLinkerVC.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040040#import "ChatVC.h"
Alexandre Lision883719f2015-10-22 17:37:45 -040041#import "BrokerVC.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040042
Alexandre Lisionc5148052015-03-04 15:10:35 -050043@interface RendererConnectionsHolder : NSObject
44
45@property QMetaObject::Connection frameUpdated;
46@property QMetaObject::Connection started;
47@property QMetaObject::Connection stopped;
48
49@end
50
51@implementation RendererConnectionsHolder
52
53@end
54
Alexandre Lision2db8f472015-07-22 15:05:46 -040055@interface CurrentCallVC () <NSPopoverDelegate, ContactLinkedDelegate>
Alexandre Lisionc5148052015-03-04 15:10:35 -050056
Alexandre Lision89edc6a2015-11-09 11:30:47 -050057// Header info
58@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision883719f2015-10-22 17:37:45 -040059@property (unsafe_unretained) IBOutlet NSTextField* personLabel;
60@property (unsafe_unretained) IBOutlet NSTextField* stateLabel;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050061@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
62
63// Call Controls
64@property (unsafe_unretained) IBOutlet NSView* controlsPanel;
Alexandre Lision883719f2015-10-22 17:37:45 -040065@property (unsafe_unretained) IBOutlet NSButton* holdOnOffButton;
66@property (unsafe_unretained) IBOutlet NSButton* hangUpButton;
67@property (unsafe_unretained) IBOutlet NSButton* recordOnOffButton;
68@property (unsafe_unretained) IBOutlet NSButton* pickUpButton;
69@property (unsafe_unretained) IBOutlet NSButton* muteAudioButton;
70@property (unsafe_unretained) IBOutlet NSButton* muteVideoButton;
71@property (unsafe_unretained) IBOutlet NSButton* addContactButton;
Alexandre Lision883719f2015-10-22 17:37:45 -040072@property (unsafe_unretained) IBOutlet NSButton* transferButton;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050073@property (unsafe_unretained) IBOutlet NSButton* addParticipantButton;
74@property (unsafe_unretained) IBOutlet NSButton* chatButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040075
Alexandre Lisionf47a2562015-06-15 15:48:29 -040076@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040077
Alexandre Lision89edc6a2015-11-09 11:30:47 -050078// Join call panel
79@property (unsafe_unretained) IBOutlet NSView* joinPanel;
80@property (unsafe_unretained) IBOutlet NSButton* mergeCallsButton;
81
Alexandre Lision883719f2015-10-22 17:37:45 -040082@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050083
Alexandre Lision2db8f472015-07-22 15:05:46 -040084@property (strong) NSPopover* addToContactPopover;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050085@property (strong) NSPopover* brokerPopoverVC;
Alexandre Lision883719f2015-10-22 17:37:45 -040086@property (strong) IBOutlet ChatVC* chatVC;
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -040087
Alexandre Lisionc5148052015-03-04 15:10:35 -050088@property QHash<int, NSButton*> actionHash;
89
90// Video
Alexandre Lision58cab672015-06-09 15:25:40 -040091@property (unsafe_unretained) IBOutlet CallView *videoView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050092@property CALayer* videoLayer;
Alexandre Lision58cab672015-06-09 15:25:40 -040093@property (unsafe_unretained) IBOutlet NSView *previewView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050094@property CALayer* previewLayer;
95
96@property RendererConnectionsHolder* previewHolder;
97@property RendererConnectionsHolder* videoHolder;
Alexandre Lisionef6333a2015-03-24 12:30:31 -040098@property QMetaObject::Connection videoStarted;
Alexandre Lisionb65c0272015-07-22 15:51:29 -040099@property QMetaObject::Connection messageConnection;
100@property QMetaObject::Connection mediaAddedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500101
102@end
103
104@implementation CurrentCallVC
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400105@synthesize personLabel, actionHash, stateLabel, holdOnOffButton, hangUpButton,
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500106 recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Alexandre Lision883719f2015-10-22 17:37:45 -0400107 muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView,
108 videoLayer, previewLayer, previewView, splitView, loadingIndicator;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500109
110@synthesize previewHolder;
111@synthesize videoHolder;
112
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400113- (void) updateAllActions
Alexandre Lisionc5148052015-03-04 15:10:35 -0500114{
Alexandre Lisionf7e11142015-12-15 10:58:11 -0500115 for (int i = 0 ; i < CallModel::instance().userActionModel()->rowCount() ; i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400116 [self updateActionAtIndex:i];
117 }
118}
119
120- (void) updateActionAtIndex:(int) row
121{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400122 const QModelIndex& idx = CallModel::instance().userActionModel()->index(row,0);
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400123 UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
124 NSButton* a = actionHash[(int) action];
Alexandre Lision266fca02015-09-28 14:47:05 -0400125 if (a) {
126 [a setHidden:!(idx.flags() & Qt::ItemIsEnabled)];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400127 [a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500128 }
129}
130
131-(void) updateCall
132{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400133 QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400134 if (!callIdx.isValid()) {
135 return;
136 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500137 auto current = CallModel::instance().getCall(callIdx);
138
Alexandre Lision58cab672015-06-09 15:25:40 -0400139 [personLabel setStringValue:callIdx.data(Qt::DisplayRole).toString().toNSString()];
140 [timeSpentLabel setStringValue:callIdx.data((int)Call::Role::Length).toString().toNSString()];
Alexandre Lision57227772016-01-15 17:19:36 -0500141 [stateLabel setStringValue:callIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500142
Alexandre Lision2db8f472015-07-22 15:05:46 -0400143 auto contactmethod = qvariant_cast<Call*>(callIdx.data(static_cast<int>(Call::Role::Object)))->peerContactMethod();
144 BOOL shouldShow = (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder());
145 [self.addContactButton setHidden:!shouldShow];
146
Alexandre Lision58cab672015-06-09 15:25:40 -0400147 Call::State state = callIdx.data((int)Call::Role::State).value<Call::State>();
Alexandre Lision266fca02015-09-28 14:47:05 -0400148
149 // Default values for this views
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400150 [loadingIndicator setHidden:YES];
Alexandre Lision266fca02015-09-28 14:47:05 -0400151 [videoView setShouldAcceptInteractions:NO];
152
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500153 [self.controlsPanel setHidden:current->hasParentCall()];
154 [self.joinPanel setHidden:!current->hasParentCall()];
Alexandre Lision266fca02015-09-28 14:47:05 -0400155
Alexandre Lisionc5148052015-03-04 15:10:35 -0500156 switch (state) {
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400157 case Call::State::DIALING:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400158 [loadingIndicator setHidden:NO];
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400159 break;
160 case Call::State::NEW:
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400161 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500162 case Call::State::INITIALIZATION:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400163 [loadingIndicator setHidden:NO];
164 break;
165 case Call::State::CONNECTED:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400166 [loadingIndicator setHidden:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500167 break;
168 case Call::State::RINGING:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500169 break;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500170 case Call::State::CONFERENCE:
171 [videoView setShouldAcceptInteractions:YES];
172 [self.chatButton setHidden:NO];
173 [self.addParticipantButton setHidden:NO];
174 [self.transferButton setHidden:YES];
175 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500176 case Call::State::CURRENT:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400177 [videoView setShouldAcceptInteractions:YES];
Alexandre Lision266fca02015-09-28 14:47:05 -0400178 [self.chatButton setHidden:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500179 [self.addParticipantButton setHidden:NO];
180 [self.transferButton setHidden:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500181 break;
182 case Call::State::HOLD:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500183 break;
184 case Call::State::BUSY:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500185 break;
186 case Call::State::OVER:
Alexandre Lision266fca02015-09-28 14:47:05 -0400187 case Call::State::FAILURE:
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400188 if(self.splitView.isInFullScreenMode)
189 [self.splitView exitFullScreenModeWithOptions:nil];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500190 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500191 }
192
193}
194
195- (void)awakeFromNib
196{
197 NSLog(@"INIT CurrentCall VC");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400198 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500199 [self.view setLayer:[CALayer layer]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500200
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400201 actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
202 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
203 actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
204 actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400205 actionHash[ (int)UserActionModel::Action::MUTE_AUDIO] = muteAudioButton;
206 actionHash[ (int)UserActionModel::Action::MUTE_VIDEO] = muteVideoButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500207
Alexandre Lisionc5148052015-03-04 15:10:35 -0500208 videoLayer = [CALayer layer];
209 [videoView setWantsLayer:YES];
210 [videoView setLayer:videoLayer];
211 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
212 [videoView.layer setFrame:videoView.frame];
213 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500214
215 previewLayer = [CALayer layer];
216 [previewView setWantsLayer:YES];
217 [previewView setLayer:previewLayer];
218 [previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400219 [previewLayer setContentsGravity:kCAGravityResizeAspectFill];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500220 [previewLayer setFrame:previewView.frame];
221
222 [controlsPanel setWantsLayer:YES];
223 [controlsPanel setLayer:[CALayer layer]];
224 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
225 [controlsPanel.layer setFrame:controlsPanel.frame];
226
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400227 previewHolder = [[RendererConnectionsHolder alloc] init];
228 videoHolder = [[RendererConnectionsHolder alloc] init];
229
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400230 [loadingIndicator setColor:[NSColor whiteColor]];
231 [loadingIndicator setNumberOfLines:100];
232 [loadingIndicator setWidthOfLine:2];
233 [loadingIndicator setLengthOfLine:2];
234 [loadingIndicator setInnerMargin:30];
235
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400236 [self.videoView setCallDelegate:self];
Alexandre Lision58cab672015-06-09 15:25:40 -0400237
Alexandre Lisionc5148052015-03-04 15:10:35 -0500238 [self connect];
239}
240
241- (void) connect
242{
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500243 QObject::connect(RecentModel::instance().selectionModel(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500244 &QItemSelectionModel::currentChanged,
245 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500246 auto call = RecentModel::instance().getActiveCall(current);
247 if(!current.isValid() || !call) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500248 [self animateOut];
249 return;
250 }
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400251
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500252 CallModel::instance().selectCall(call);
253
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400254 if (call->state() == Call::State::HOLD) {
255 call << Call::Action::HOLD;
256 }
257
Alexandre Lision58cab672015-06-09 15:25:40 -0400258 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500259 [self updateCall];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400260 [self updateAllActions];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500261 [self animateOut];
262 });
263
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400264 QObject::connect(CallModel::instance().userActionModel(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500265 &QAbstractItemModel::dataChanged,
266 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500267 const int first(topLeft.row()),last(bottomRight.row());
268 for(int i = first; i <= last;i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400269 [self updateActionAtIndex:i];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500270 }
271 });
272
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400273 QObject::connect(&CallModel::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500274 &CallModel::callStateChanged,
275 [self](Call* c, Call::State state) {
Alexandre Lision57227772016-01-15 17:19:36 -0500276 auto current = CallModel::instance().selectionModel()->currentIndex();
277 if (!current.isValid())
278 [self animateOut];
279 else if (CallModel::instance().getIndex(c) == current) {
280 if (c->state() == Call::State::OVER) {
281 [self animateOut];
282 } else {
283 [self updateCall];
284 }
285 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500286 });
287}
288
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400289- (void) monitorIncomingTextMessages:(Media::Text*) media
290{
291 /* connect to incoming chat messages to open the chat view */
292 QObject::disconnect(self.messageConnection);
293 self.messageConnection = QObject::connect(media,
294 &Media::Text::messageReceived,
295 [self] (const QMap<QString,QString>& m) {
296 if([[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]])
297 [self uncollapseRightView];
298 });
299}
300
Alexandre Lisionc5148052015-03-04 15:10:35 -0500301-(void) connectVideoSignals
302{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400303 QModelIndex idx = CallModel::instance().selectionModel()->currentIndex();
304 Call* call = CallModel::instance().getCall(idx);
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500305 QObject::disconnect(self.videoStarted);
Alexandre Lision58cab672015-06-09 15:25:40 -0400306 self.videoStarted = QObject::connect(call,
Alexandre Lisionc5148052015-03-04 15:10:35 -0500307 &Call::videoStarted,
308 [=](Video::Renderer* renderer) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400309 NSLog(@"Video started!");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400310 [self connectVideoRenderer:renderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500311 });
312
313 if(call->videoRenderer())
314 {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500315 [self connectVideoRenderer:call->videoRenderer()];
316 }
317
318 [self connectPreviewRenderer];
319
320}
321
322-(void) connectPreviewRenderer
323{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400324 QObject::disconnect(previewHolder.frameUpdated);
325 QObject::disconnect(previewHolder.stopped);
326 QObject::disconnect(previewHolder.started);
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400327 previewHolder.started = QObject::connect(&Video::PreviewManager::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500328 &Video::PreviewManager::previewStarted,
329 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500330 QObject::disconnect(previewHolder.frameUpdated);
331 previewHolder.frameUpdated = QObject::connect(renderer,
332 &Video::Renderer::frameUpdated,
333 [=]() {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400334 [self renderer:Video::PreviewManager::instance().previewRenderer()
Alexandre Lisionc5148052015-03-04 15:10:35 -0500335 renderFrameForView:previewView];
336 });
337 });
338
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400339 previewHolder.stopped = QObject::connect(&Video::PreviewManager::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500340 &Video::PreviewManager::previewStopped,
341 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500342 QObject::disconnect(previewHolder.frameUpdated);
343 [previewView.layer setContents:nil];
344 });
345
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400346 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance().previewRenderer(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500347 &Video::Renderer::frameUpdated,
348 [=]() {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400349 [self renderer:Video::PreviewManager::instance().previewRenderer()
Alexandre Lisionc5148052015-03-04 15:10:35 -0500350 renderFrameForView:previewView];
351 });
352}
353
354-(void) connectVideoRenderer: (Video::Renderer*)renderer
355{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400356 QObject::disconnect(videoHolder.frameUpdated);
357 QObject::disconnect(videoHolder.started);
358 QObject::disconnect(videoHolder.stopped);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500359 videoHolder.frameUpdated = QObject::connect(renderer,
360 &Video::Renderer::frameUpdated,
361 [=]() {
362 [self renderer:renderer renderFrameForView:videoView];
363 });
364
365 videoHolder.started = QObject::connect(renderer,
366 &Video::Renderer::started,
367 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500368 QObject::disconnect(videoHolder.frameUpdated);
369 videoHolder.frameUpdated = QObject::connect(renderer,
370 &Video::Renderer::frameUpdated,
371 [=]() {
372 [self renderer:renderer renderFrameForView:videoView];
373 });
374 });
375
376 videoHolder.stopped = QObject::connect(renderer,
377 &Video::Renderer::stopped,
378 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500379 QObject::disconnect(videoHolder.frameUpdated);
380 [videoView.layer setContents:nil];
381 });
382}
383
384-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
385{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500386 QSize res = renderer->size();
387
Alexandre Lision6731e132015-10-14 14:29:06 -0400388 auto frame_ptr = renderer->currentFrame();
389 auto frame_data = frame_ptr.ptr;
390 if (!frame_data)
391 return;
392
Alexandre Lisionc5148052015-03-04 15:10:35 -0500393
394 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
Alexandre Lision6731e132015-10-14 14:29:06 -0400395 CGContextRef newContext = CGBitmapContextCreate(frame_data,
Alexandre Lisionc5148052015-03-04 15:10:35 -0500396 res.width(),
397 res.height(),
398 8,
399 4*res.width(),
400 colorSpace,
401 kCGImageAlphaPremultipliedLast);
402
403
404 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
405
406 /*We release some components*/
407 CGContextRelease(newContext);
408 CGColorSpaceRelease(colorSpace);
409
410 [CATransaction begin];
411 view.layer.contents = (__bridge id)newImage;
412 [CATransaction commit];
413
414 CFRelease(newImage);
415}
416
417- (void) initFrame
418{
419 [self.view setFrame:self.view.superview.bounds];
420 [self.view setHidden:YES];
421 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400422 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500423}
424
425# pragma private IN/OUT animations
426
427-(void) animateIn
428{
429 NSLog(@"animateIn");
430 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
431 [self.view setHidden:NO];
432
433 [CATransaction begin];
434 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
435 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
436 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
437 [animation setDuration:0.2f];
438 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
439 [CATransaction setCompletionBlock:^{
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400440
441 // when call comes in we want to show the controls/header
442 [self mouseIsMoving:YES];
443
Alexandre Lisionc5148052015-03-04 15:10:35 -0500444 [self connectVideoSignals];
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400445 /* check if text media is already present */
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400446 if(!CallModel::instance().selectedCall())
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400447 return;
Alexandre Lision21666f32015-09-22 17:04:36 -0400448
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400449 QObject::connect(CallModel::instance().selectedCall(),
Alexandre Lision21666f32015-09-22 17:04:36 -0400450 &Call::changed,
451 [=]() {
452 [self updateCall];
453 });
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400454 if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
455 Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::IN);
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400456 [self monitorIncomingTextMessages:text];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400457 } else if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
458 Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::OUT);
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400459 [self monitorIncomingTextMessages:text];
460 } else {
461 /* monitor media for messaging text messaging */
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400462 self.mediaAddedConnection = QObject::connect(CallModel::instance().selectedCall(),
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400463 &Call::mediaAdded,
464 [self] (Media::Media* media) {
465 if (media->type() == Media::Media::Type::TEXT) { [self monitorIncomingTextMessages:(Media::Text*)media];
466 QObject::disconnect(self.mediaAddedConnection);
467 }
468 });
469 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500470 }];
471 [self.view.layer addAnimation:animation forKey:animation.keyPath];
472
473 [CATransaction commit];
474}
475
476-(void) cleanUp
477{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400478 QObject::disconnect(videoHolder.frameUpdated);
479 QObject::disconnect(videoHolder.started);
480 QObject::disconnect(videoHolder.stopped);
481 QObject::disconnect(previewHolder.frameUpdated);
482 QObject::disconnect(previewHolder.stopped);
483 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500484 [videoView.layer setContents:nil];
485 [previewView.layer setContents:nil];
Alexandre Lision883719f2015-10-22 17:37:45 -0400486
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500487 [_brokerPopoverVC performClose:self];
Alexandre Lision883719f2015-10-22 17:37:45 -0400488 [self.addToContactPopover performClose:self];
Alexandre Lision0e66aea2015-11-02 16:36:30 -0500489
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500490 [self.chatButton setHidden:YES];
491 [self.addParticipantButton setHidden:YES];
492 [self.transferButton setHidden:YES];
493
Alexandre Lision883719f2015-10-22 17:37:45 -0400494 [self.chatButton setState:NSOffState];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500495 [self.mergeCallsButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -0400496 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500497
498 [personLabel setStringValue:@""];
499 [timeSpentLabel setStringValue:@""];
500 [stateLabel setStringValue:@""];
501 [self.addContactButton setHidden:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500502}
503
504-(void) animateOut
505{
506 NSLog(@"animateOut");
507 if(self.view.frame.origin.x < 0) {
508 NSLog(@"Already hidden");
Alexandre Lisionc5148052015-03-04 15:10:35 -0500509 return;
510 }
511
512 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
513 [CATransaction begin];
514 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
515 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
516 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
517 [animation setDuration:0.2f];
518 [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
519
520 [CATransaction setCompletionBlock:^{
521 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400522 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500523 [self cleanUp];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500524 if (RecentModel::instance().getActiveCall(RecentModel::instance().selectionModel()->currentIndex())) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500525 [self animateIn];
526 }
527 }];
528 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400529
530 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500531 [CATransaction commit];
532}
533
534/**
535 * Debug purpose
536 */
537-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
538{
539 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
540}
541
Alexandre Lision58cab672015-06-09 15:25:40 -0400542-(void)collapseRightView
543{
544 NSView *right = [[splitView subviews] objectAtIndex:1];
545 NSView *left = [[splitView subviews] objectAtIndex:0];
546 NSRect leftFrame = [left frame];
547 [right setHidden:YES];
548 [splitView display];
549}
Alexandre Lisionc5148052015-03-04 15:10:35 -0500550
Alexandre Lision58cab672015-06-09 15:25:40 -0400551-(void)uncollapseRightView
552{
553 NSView *left = [[splitView subviews] objectAtIndex:0];
554 NSView *right = [[splitView subviews] objectAtIndex:1];
555 [right setHidden:NO];
556
557 CGFloat dividerThickness = [splitView dividerThickness];
558
559 // get the different frames
560 NSRect leftFrame = [left frame];
561 NSRect rightFrame = [right frame];
562
563 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
564 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
565 [left setFrameSize:leftFrame.size];
566 [right setFrame:rightFrame];
567 [splitView display];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -0400568
569 [self.chatVC takeFocus];
Alexandre Lision58cab672015-06-09 15:25:40 -0400570}
571
572
573#pragma mark - Button methods
574
Alexandre Lision2db8f472015-07-22 15:05:46 -0400575- (IBAction)addToContact:(NSButton*) sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400576 auto contactmethod = CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->peerContactMethod();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400577
578 if (self.addToContactPopover != nullptr) {
579 [self.addToContactPopover performClose:self];
580 self.addToContactPopover = NULL;
581 } else if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
582 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
583 [editorVC setMethodToLink:contactmethod];
584 [editorVC setContactLinkedDelegate:self];
585 self.addToContactPopover = [[NSPopover alloc] init];
586 [self.addToContactPopover setContentSize:editorVC.view.frame.size];
587 [self.addToContactPopover setContentViewController:editorVC];
588 [self.addToContactPopover setAnimates:YES];
589 [self.addToContactPopover setBehavior:NSPopoverBehaviorTransient];
590 [self.addToContactPopover setDelegate:self];
591
592 [self.addToContactPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSMaxXEdge];
593 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400594
595 [videoView setCallDelegate:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400596}
597
Alexandre Lisionc5148052015-03-04 15:10:35 -0500598- (IBAction)hangUp:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400599 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500600}
601
602- (IBAction)accept:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400603 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::ACCEPT;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500604}
605
606- (IBAction)toggleRecording:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400607 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::RECORD_AUDIO;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500608}
609
610- (IBAction)toggleHold:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400611 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::HOLD;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500612}
613
Alexandre Lision58cab672015-06-09 15:25:40 -0400614-(IBAction)toggleChat:(id)sender;
615{
616 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
617 if (rightViewCollapsed) {
618 [self uncollapseRightView];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400619 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->addOutgoingMedia<Media::Text>();
Alexandre Lision58cab672015-06-09 15:25:40 -0400620 } else {
621 [self collapseRightView];
622 }
623 [chatButton setState:rightViewCollapsed];
624}
625
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400626- (IBAction)muteAudio:(id)sender
627{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400628 UserActionModel* uam = CallModel::instance().userActionModel();
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400629 uam << UserActionModel::Action::MUTE_AUDIO;
630}
631
632- (IBAction)muteVideo:(id)sender
633{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400634 UserActionModel* uam = CallModel::instance().userActionModel();
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400635 uam << UserActionModel::Action::MUTE_VIDEO;
636}
Alexandre Lision883719f2015-10-22 17:37:45 -0400637
638- (IBAction)toggleTransferView:(id)sender {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500639 if (_brokerPopoverVC != nullptr) {
640 [_brokerPopoverVC performClose:self];
641 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -0400642 [self.transferButton setState:NSOffState];
643 } else {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500644 auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::TRANSFER];
645 _brokerPopoverVC = [[NSPopover alloc] init];
646 [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
647 [_brokerPopoverVC setContentViewController:brokerVC];
648 [_brokerPopoverVC setAnimates:YES];
649 [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
650 [_brokerPopoverVC setDelegate:self];
651 [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
Alexandre Lision883719f2015-10-22 17:37:45 -0400652 [videoView setCallDelegate:nil];
653 }
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400654}
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400655
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500656- (IBAction)toggleAddParticipantView:(id)sender {
657 if (_brokerPopoverVC != nullptr) {
658 [_brokerPopoverVC performClose:self];
659 _brokerPopoverVC = NULL;
660 [self.addParticipantButton setState:NSOffState];
661 } else {
662 auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::CONFERENCE];
663 _brokerPopoverVC = [[NSPopover alloc] init];
664 [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
665 [_brokerPopoverVC setContentViewController:brokerVC];
666 [_brokerPopoverVC setAnimates:YES];
667 [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
668 [_brokerPopoverVC setDelegate:self];
669 [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
670 [videoView setCallDelegate:nil];
671 }
672}
673
674/**
675 * Merge current call with its parent call
676 */
677- (IBAction)mergeCalls:(id)sender
678{
679 auto current = CallModel::instance().selectedCall();
680 current->joinToParent();
681}
682
Alexandre Lision2db8f472015-07-22 15:05:46 -0400683#pragma mark - NSPopOverDelegate
684
Alexandre Lision266fca02015-09-28 14:47:05 -0400685- (void)popoverWillClose:(NSNotification *)notification
686{
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500687 if (_brokerPopoverVC != nullptr) {
688 [_brokerPopoverVC performClose:self];
689 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -0400690 }
Alexandre Lision266fca02015-09-28 14:47:05 -0400691
Alexandre Lision2db8f472015-07-22 15:05:46 -0400692 if (self.addToContactPopover != nullptr) {
693 [self.addToContactPopover performClose:self];
694 self.addToContactPopover = NULL;
695 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400696
Alexandre Lision883719f2015-10-22 17:37:45 -0400697 [self.addContactButton setState:NSOffState];
698 [self.transferButton setState:NSOffState];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500699 [self.addParticipantButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -0400700}
701
702- (void)popoverDidClose:(NSNotification *)notification
703{
704 [videoView setCallDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400705}
706
707#pragma mark - ContactLinkedDelegate
708
709- (void)contactLinked
710{
711 if (self.addToContactPopover != nullptr) {
712 [self.addToContactPopover performClose:self];
713 self.addToContactPopover = NULL;
714 }
715}
716
Alexandre Lision58cab672015-06-09 15:25:40 -0400717#pragma mark - NSSplitViewDelegate
718
719/* 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.
720 */
721- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
722{
723 NSView* rightView = [[splitView subviews] objectAtIndex:1];
724 return ([subview isEqual:rightView]);
725}
726
727
728- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
729{
730 NSView* rightView = [[splitView subviews] objectAtIndex:1];
731 return ([subview isEqual:rightView]);
732}
733
734
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400735# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -0400736
737- (void) callShouldToggleFullScreen
738{
739 if(self.splitView.isInFullScreenMode)
740 [self.splitView exitFullScreenModeWithOptions:nil];
741 else {
742 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
743 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
744 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
745 NSFullScreenModeApplicationPresentationOptions, nil];
746
747 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
748 }
749}
750
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400751-(void) mouseIsMoving:(BOOL) move
752{
753 [[controlsPanel animator] setAlphaValue:move]; // fade out
754 [[headerContainer animator] setAlphaValue:move];
755}
756
Alexandre Lisionc5148052015-03-04 15:10:35 -0500757@end