blob: ad6681e9b61a194e8b4eebb6eafe802f9129497e [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 Lision58cab672015-06-09 15:25:40 -040092@property (unsafe_unretained) IBOutlet NSView *previewView;
Alexandre Lisionc5148052015-03-04 15:10:35 -050093
94@property RendererConnectionsHolder* previewHolder;
95@property RendererConnectionsHolder* videoHolder;
Alexandre Lisionef6333a2015-03-24 12:30:31 -040096@property QMetaObject::Connection videoStarted;
Alexandre Lision4d9905e2016-01-22 12:01:36 -050097@property QMetaObject::Connection selectedCallChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -040098@property QMetaObject::Connection messageConnection;
99@property QMetaObject::Connection mediaAddedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500100
101@end
102
103@implementation CurrentCallVC
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400104@synthesize personLabel, actionHash, stateLabel, holdOnOffButton, hangUpButton,
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500105 recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Alexandre Lision883719f2015-10-22 17:37:45 -0400106 muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView,
Alexandre Lision22d615b2016-01-25 12:54:34 -0500107 previewView, splitView, loadingIndicator;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500108
109@synthesize previewHolder;
110@synthesize videoHolder;
111
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400112- (void) updateAllActions
Alexandre Lisionc5148052015-03-04 15:10:35 -0500113{
Alexandre Lisionf7e11142015-12-15 10:58:11 -0500114 for (int i = 0 ; i < CallModel::instance().userActionModel()->rowCount() ; i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400115 [self updateActionAtIndex:i];
116 }
117}
118
119- (void) updateActionAtIndex:(int) row
120{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400121 const QModelIndex& idx = CallModel::instance().userActionModel()->index(row,0);
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400122 UserActionModel::Action action = qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION));
123 NSButton* a = actionHash[(int) action];
Alexandre Lision266fca02015-09-28 14:47:05 -0400124 if (a) {
125 [a setHidden:!(idx.flags() & Qt::ItemIsEnabled)];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500126 [a setHighlighted:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? YES : NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500127 }
128}
129
130-(void) updateCall
131{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400132 QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400133 if (!callIdx.isValid()) {
134 return;
135 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500136 auto current = CallModel::instance().getCall(callIdx);
137
Alexandre Lision58cab672015-06-09 15:25:40 -0400138 [personLabel setStringValue:callIdx.data(Qt::DisplayRole).toString().toNSString()];
139 [timeSpentLabel setStringValue:callIdx.data((int)Call::Role::Length).toString().toNSString()];
Alexandre Lision57227772016-01-15 17:19:36 -0500140 [stateLabel setStringValue:callIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500141
Alexandre Lision2db8f472015-07-22 15:05:46 -0400142 auto contactmethod = qvariant_cast<Call*>(callIdx.data(static_cast<int>(Call::Role::Object)))->peerContactMethod();
143 BOOL shouldShow = (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder());
144 [self.addContactButton setHidden:!shouldShow];
145
Alexandre Lision58cab672015-06-09 15:25:40 -0400146 Call::State state = callIdx.data((int)Call::Role::State).value<Call::State>();
Alexandre Lision266fca02015-09-28 14:47:05 -0400147
148 // Default values for this views
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400149 [loadingIndicator setHidden:YES];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500150
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::NEW:
Alexandre Lisione6dbf092015-04-11 17:19:35 -0400158 break;
Alexandre Lision22d615b2016-01-25 12:54:34 -0500159 case Call::State::DIALING:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500160 case Call::State::INITIALIZATION:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400161 case Call::State::CONNECTED:
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400162 [loadingIndicator setHidden:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500163 break;
164 case Call::State::RINGING:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500165 break;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500166 case Call::State::CONFERENCE:
167 [videoView setShouldAcceptInteractions:YES];
168 [self.chatButton setHidden:NO];
169 [self.addParticipantButton setHidden:NO];
170 [self.transferButton setHidden:YES];
171 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500172 case Call::State::CURRENT:
Alexandre Lision74dd47f2015-04-14 13:47:42 -0400173 [videoView setShouldAcceptInteractions:YES];
Alexandre Lision266fca02015-09-28 14:47:05 -0400174 [self.chatButton setHidden:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500175 [self.addParticipantButton setHidden:NO];
176 [self.transferButton setHidden:NO];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500177 break;
178 case Call::State::HOLD:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500179 break;
180 case Call::State::BUSY:
Alexandre Lisionc5148052015-03-04 15:10:35 -0500181 break;
182 case Call::State::OVER:
Alexandre Lision266fca02015-09-28 14:47:05 -0400183 case Call::State::FAILURE:
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400184 if(self.splitView.isInFullScreenMode)
185 [self.splitView exitFullScreenModeWithOptions:nil];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500186 break;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500187 }
188
189}
190
191- (void)awakeFromNib
192{
193 NSLog(@"INIT CurrentCall VC");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400194 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500195
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400196 actionHash[ (int)UserActionModel::Action::ACCEPT] = pickUpButton;
197 actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
198 actionHash[ (int)UserActionModel::Action::RECORD] = recordOnOffButton;
199 actionHash[ (int)UserActionModel::Action::HANGUP] = hangUpButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400200 actionHash[ (int)UserActionModel::Action::MUTE_AUDIO] = muteAudioButton;
201 actionHash[ (int)UserActionModel::Action::MUTE_VIDEO] = muteVideoButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500202
Alexandre Lisionc5148052015-03-04 15:10:35 -0500203 [videoView setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500204 [videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
205 [videoView.layer setFrame:videoView.frame];
206 [videoView.layer setContentsGravity:kCAGravityResizeAspect];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500207
Alexandre Lisionc5148052015-03-04 15:10:35 -0500208 [previewView setWantsLayer:YES];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500209 [previewView.layer setBackgroundColor:[NSColor blackColor].CGColor];
210 [previewView.layer setContentsGravity:kCAGravityResizeAspectFill];
211 [previewView.layer setFrame:previewView.frame];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500212
213 [controlsPanel setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500214 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
215 [controlsPanel.layer setFrame:controlsPanel.frame];
216
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400217 previewHolder = [[RendererConnectionsHolder alloc] init];
218 videoHolder = [[RendererConnectionsHolder alloc] init];
219
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400220 [loadingIndicator setColor:[NSColor whiteColor]];
221 [loadingIndicator setNumberOfLines:100];
222 [loadingIndicator setWidthOfLine:2];
223 [loadingIndicator setLengthOfLine:2];
224 [loadingIndicator setInnerMargin:30];
225
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400226 [self.videoView setCallDelegate:self];
Alexandre Lision58cab672015-06-09 15:25:40 -0400227
Alexandre Lisionc5148052015-03-04 15:10:35 -0500228 [self connect];
229}
230
231- (void) connect
232{
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500233 QObject::connect(RecentModel::instance().selectionModel(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500234 &QItemSelectionModel::currentChanged,
235 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500236 auto call = RecentModel::instance().getActiveCall(current);
237 if(!current.isValid() || !call) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500238 return;
239 }
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400240
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500241 [self changeCallSelection:call];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500242
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400243 if (call->state() == Call::State::HOLD) {
244 call << Call::Action::HOLD;
245 }
246
Alexandre Lision58cab672015-06-09 15:25:40 -0400247 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500248 [self updateCall];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400249 [self updateAllActions];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500250 });
251
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400252 QObject::connect(CallModel::instance().userActionModel(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500253 &QAbstractItemModel::dataChanged,
254 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500255 const int first(topLeft.row()),last(bottomRight.row());
256 for(int i = first; i <= last;i++) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400257 [self updateActionAtIndex:i];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500258 }
259 });
260
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400261 QObject::connect(&CallModel::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500262 &CallModel::callStateChanged,
263 [self](Call* c, Call::State state) {
Alexandre Lision57227772016-01-15 17:19:36 -0500264 auto current = CallModel::instance().selectionModel()->currentIndex();
265 if (!current.isValid())
266 [self animateOut];
267 else if (CallModel::instance().getIndex(c) == current) {
268 if (c->state() == Call::State::OVER) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500269 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision57227772016-01-15 17:19:36 -0500270 } else {
271 [self updateCall];
272 }
273 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500274 });
275
276 QObject::connect(&CallModel::instance(),
277 &CallModel::incomingCall,
278 [self](Call* c) {
279 [self changeCallSelection:c];
280 [self animateIn];
281 });
282}
283
284- (void) changeCallSelection:(Call* )c
285{
286 QObject::disconnect(self.selectedCallChanged);
287 CallModel::instance().selectCall(c);
288 self.selectedCallChanged = QObject::connect(CallModel::instance().selectedCall(),
289 &Call::changed,
290 [=]() {
291 [self updateCall];
292 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500293}
294
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400295- (void) monitorIncomingTextMessages:(Media::Text*) media
296{
297 /* connect to incoming chat messages to open the chat view */
298 QObject::disconnect(self.messageConnection);
299 self.messageConnection = QObject::connect(media,
300 &Media::Text::messageReceived,
301 [self] (const QMap<QString,QString>& m) {
302 if([[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]])
303 [self uncollapseRightView];
304 });
305}
306
Alexandre Lisionc5148052015-03-04 15:10:35 -0500307-(void) connectVideoSignals
308{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400309 QModelIndex idx = CallModel::instance().selectionModel()->currentIndex();
310 Call* call = CallModel::instance().getCall(idx);
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500311 QObject::disconnect(self.videoStarted);
Alexandre Lision58cab672015-06-09 15:25:40 -0400312 self.videoStarted = QObject::connect(call,
Alexandre Lisionc5148052015-03-04 15:10:35 -0500313 &Call::videoStarted,
314 [=](Video::Renderer* renderer) {
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400315 NSLog(@"Video started!");
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400316 [self connectVideoRenderer:renderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500317 });
318
319 if(call->videoRenderer())
320 {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500321 [self connectVideoRenderer:call->videoRenderer()];
322 }
323
324 [self connectPreviewRenderer];
325
326}
327
328-(void) connectPreviewRenderer
329{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400330 QObject::disconnect(previewHolder.frameUpdated);
331 QObject::disconnect(previewHolder.stopped);
332 QObject::disconnect(previewHolder.started);
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400333 previewHolder.started = QObject::connect(&Video::PreviewManager::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500334 &Video::PreviewManager::previewStarted,
335 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500336 QObject::disconnect(previewHolder.frameUpdated);
337 previewHolder.frameUpdated = QObject::connect(renderer,
338 &Video::Renderer::frameUpdated,
339 [=]() {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400340 [self renderer:Video::PreviewManager::instance().previewRenderer()
Alexandre Lisionc5148052015-03-04 15:10:35 -0500341 renderFrameForView:previewView];
342 });
343 });
344
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400345 previewHolder.stopped = QObject::connect(&Video::PreviewManager::instance(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500346 &Video::PreviewManager::previewStopped,
347 [=](Video::Renderer* renderer) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500348 QObject::disconnect(previewHolder.frameUpdated);
349 [previewView.layer setContents:nil];
350 });
351
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400352 previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance().previewRenderer(),
Alexandre Lisionc5148052015-03-04 15:10:35 -0500353 &Video::Renderer::frameUpdated,
354 [=]() {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400355 [self renderer:Video::PreviewManager::instance().previewRenderer()
Alexandre Lisionc5148052015-03-04 15:10:35 -0500356 renderFrameForView:previewView];
357 });
358}
359
360-(void) connectVideoRenderer: (Video::Renderer*)renderer
361{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400362 QObject::disconnect(videoHolder.frameUpdated);
363 QObject::disconnect(videoHolder.started);
364 QObject::disconnect(videoHolder.stopped);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500365 videoHolder.frameUpdated = QObject::connect(renderer,
366 &Video::Renderer::frameUpdated,
367 [=]() {
368 [self renderer:renderer renderFrameForView:videoView];
369 });
370
371 videoHolder.started = QObject::connect(renderer,
372 &Video::Renderer::started,
373 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500374 QObject::disconnect(videoHolder.frameUpdated);
375 videoHolder.frameUpdated = QObject::connect(renderer,
376 &Video::Renderer::frameUpdated,
377 [=]() {
378 [self renderer:renderer renderFrameForView:videoView];
379 });
380 });
381
382 videoHolder.stopped = QObject::connect(renderer,
383 &Video::Renderer::stopped,
384 [=]() {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500385 QObject::disconnect(videoHolder.frameUpdated);
386 [videoView.layer setContents:nil];
387 });
388}
389
390-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
391{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500392 QSize res = renderer->size();
393
Alexandre Lision6731e132015-10-14 14:29:06 -0400394 auto frame_ptr = renderer->currentFrame();
395 auto frame_data = frame_ptr.ptr;
396 if (!frame_data)
397 return;
398
Alexandre Lisionc5148052015-03-04 15:10:35 -0500399
400 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
Alexandre Lision6731e132015-10-14 14:29:06 -0400401 CGContextRef newContext = CGBitmapContextCreate(frame_data,
Alexandre Lisionc5148052015-03-04 15:10:35 -0500402 res.width(),
403 res.height(),
404 8,
405 4*res.width(),
406 colorSpace,
407 kCGImageAlphaPremultipliedLast);
408
409
410 CGImageRef newImage = CGBitmapContextCreateImage(newContext);
411
412 /*We release some components*/
413 CGContextRelease(newContext);
414 CGColorSpaceRelease(colorSpace);
415
416 [CATransaction begin];
417 view.layer.contents = (__bridge id)newImage;
418 [CATransaction commit];
419
420 CFRelease(newImage);
421}
422
423- (void) initFrame
424{
425 [self.view setFrame:self.view.superview.bounds];
426 [self.view setHidden:YES];
427 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400428 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500429}
430
431# pragma private IN/OUT animations
432
433-(void) animateIn
434{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500435 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
436 [self.view setHidden:NO];
437
438 [CATransaction begin];
439 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
440 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
441 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
442 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500443 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500444 [CATransaction setCompletionBlock:^{
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400445
446 // when call comes in we want to show the controls/header
447 [self mouseIsMoving:YES];
448
Alexandre Lisionc5148052015-03-04 15:10:35 -0500449 [self connectVideoSignals];
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400450 /* check if text media is already present */
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400451 if(!CallModel::instance().selectedCall())
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400452 return;
Alexandre Lision21666f32015-09-22 17:04:36 -0400453
Alexandre Lision22d615b2016-01-25 12:54:34 -0500454 [loadingIndicator setAnimates:YES];
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500455 [self updateCall];
456
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400457 if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
458 Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::IN);
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400459 [self monitorIncomingTextMessages:text];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400460 } else if (CallModel::instance().selectedCall()->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
461 Media::Text *text = CallModel::instance().selectedCall()->firstMedia<Media::Text>(Media::Media::Direction::OUT);
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400462 [self monitorIncomingTextMessages:text];
463 } else {
464 /* monitor media for messaging text messaging */
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400465 self.mediaAddedConnection = QObject::connect(CallModel::instance().selectedCall(),
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400466 &Call::mediaAdded,
467 [self] (Media::Media* media) {
468 if (media->type() == Media::Media::Type::TEXT) { [self monitorIncomingTextMessages:(Media::Text*)media];
469 QObject::disconnect(self.mediaAddedConnection);
470 }
471 });
472 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500473 }];
474 [self.view.layer addAnimation:animation forKey:animation.keyPath];
475
476 [CATransaction commit];
477}
478
479-(void) cleanUp
480{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400481 QObject::disconnect(videoHolder.frameUpdated);
482 QObject::disconnect(videoHolder.started);
483 QObject::disconnect(videoHolder.stopped);
484 QObject::disconnect(previewHolder.frameUpdated);
485 QObject::disconnect(previewHolder.stopped);
486 QObject::disconnect(previewHolder.started);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500487 [videoView.layer setContents:nil];
488 [previewView.layer setContents:nil];
Alexandre Lision883719f2015-10-22 17:37:45 -0400489
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500490 [_brokerPopoverVC performClose:self];
Alexandre Lision883719f2015-10-22 17:37:45 -0400491 [self.addToContactPopover performClose:self];
Alexandre Lision0e66aea2015-11-02 16:36:30 -0500492
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500493 [self.chatButton setHidden:YES];
494 [self.addParticipantButton setHidden:YES];
495 [self.transferButton setHidden:YES];
496
Alexandre Lision883719f2015-10-22 17:37:45 -0400497 [self.chatButton setState:NSOffState];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500498 [self.mergeCallsButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -0400499 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500500
501 [personLabel setStringValue:@""];
502 [timeSpentLabel setStringValue:@""];
503 [stateLabel setStringValue:@""];
504 [self.addContactButton setHidden:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500505}
506
507-(void) animateOut
508{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500509 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500510 return;
511 }
512
513 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
514 [CATransaction begin];
515 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
516 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
517 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
518 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500519 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500520
521 [CATransaction setCompletionBlock:^{
522 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400523 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500524 [self cleanUp];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500525 if (RecentModel::instance().getActiveCall(RecentModel::instance().selectionModel()->currentIndex())) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500526 [self animateIn];
527 }
528 }];
529 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400530
531 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500532 [CATransaction commit];
533}
534
535/**
536 * Debug purpose
537 */
538-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
539{
540 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
541}
542
Alexandre Lision58cab672015-06-09 15:25:40 -0400543-(void)collapseRightView
544{
545 NSView *right = [[splitView subviews] objectAtIndex:1];
546 NSView *left = [[splitView subviews] objectAtIndex:0];
547 NSRect leftFrame = [left frame];
548 [right setHidden:YES];
549 [splitView display];
550}
Alexandre Lisionc5148052015-03-04 15:10:35 -0500551
Alexandre Lision58cab672015-06-09 15:25:40 -0400552-(void)uncollapseRightView
553{
554 NSView *left = [[splitView subviews] objectAtIndex:0];
555 NSView *right = [[splitView subviews] objectAtIndex:1];
556 [right setHidden:NO];
557
558 CGFloat dividerThickness = [splitView dividerThickness];
559
560 // get the different frames
561 NSRect leftFrame = [left frame];
562 NSRect rightFrame = [right frame];
563
564 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
565 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
566 [left setFrameSize:leftFrame.size];
567 [right setFrame:rightFrame];
568 [splitView display];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -0400569
570 [self.chatVC takeFocus];
Alexandre Lision58cab672015-06-09 15:25:40 -0400571}
572
573
574#pragma mark - Button methods
575
Alexandre Lision2db8f472015-07-22 15:05:46 -0400576- (IBAction)addToContact:(NSButton*) sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400577 auto contactmethod = CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->peerContactMethod();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400578
579 if (self.addToContactPopover != nullptr) {
580 [self.addToContactPopover performClose:self];
581 self.addToContactPopover = NULL;
582 } else if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
583 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
584 [editorVC setMethodToLink:contactmethod];
585 [editorVC setContactLinkedDelegate:self];
586 self.addToContactPopover = [[NSPopover alloc] init];
587 [self.addToContactPopover setContentSize:editorVC.view.frame.size];
588 [self.addToContactPopover setContentViewController:editorVC];
589 [self.addToContactPopover setAnimates:YES];
590 [self.addToContactPopover setBehavior:NSPopoverBehaviorTransient];
591 [self.addToContactPopover setDelegate:self];
592
593 [self.addToContactPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSMaxXEdge];
594 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400595
596 [videoView setCallDelegate:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400597}
598
Alexandre Lisionc5148052015-03-04 15:10:35 -0500599- (IBAction)hangUp:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400600 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500601}
602
603- (IBAction)accept:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400604 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::ACCEPT;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500605}
606
607- (IBAction)toggleRecording:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400608 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::RECORD_AUDIO;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500609}
610
611- (IBAction)toggleHold:(id)sender {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400612 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::HOLD;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500613}
614
Alexandre Lision58cab672015-06-09 15:25:40 -0400615-(IBAction)toggleChat:(id)sender;
616{
617 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
618 if (rightViewCollapsed) {
619 [self uncollapseRightView];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400620 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex())->addOutgoingMedia<Media::Text>();
Alexandre Lision58cab672015-06-09 15:25:40 -0400621 } else {
622 [self collapseRightView];
623 }
624 [chatButton setState:rightViewCollapsed];
625}
626
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400627- (IBAction)muteAudio:(id)sender
628{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400629 UserActionModel* uam = CallModel::instance().userActionModel();
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400630 uam << UserActionModel::Action::MUTE_AUDIO;
631}
632
633- (IBAction)muteVideo:(id)sender
634{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400635 UserActionModel* uam = CallModel::instance().userActionModel();
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400636 uam << UserActionModel::Action::MUTE_VIDEO;
637}
Alexandre Lision883719f2015-10-22 17:37:45 -0400638
639- (IBAction)toggleTransferView:(id)sender {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500640 if (_brokerPopoverVC != nullptr) {
641 [_brokerPopoverVC performClose:self];
642 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -0400643 [self.transferButton setState:NSOffState];
644 } else {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500645 auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::TRANSFER];
646 _brokerPopoverVC = [[NSPopover alloc] init];
647 [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
648 [_brokerPopoverVC setContentViewController:brokerVC];
649 [_brokerPopoverVC setAnimates:YES];
650 [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
651 [_brokerPopoverVC setDelegate:self];
652 [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
Alexandre Lision883719f2015-10-22 17:37:45 -0400653 [videoView setCallDelegate:nil];
654 }
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400655}
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400656
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500657- (IBAction)toggleAddParticipantView:(id)sender {
658 if (_brokerPopoverVC != nullptr) {
659 [_brokerPopoverVC performClose:self];
660 _brokerPopoverVC = NULL;
661 [self.addParticipantButton setState:NSOffState];
662 } else {
663 auto* brokerVC = [[BrokerVC alloc] initWithMode:BrokerMode::CONFERENCE];
664 _brokerPopoverVC = [[NSPopover alloc] init];
665 [_brokerPopoverVC setContentSize:brokerVC.view.frame.size];
666 [_brokerPopoverVC setContentViewController:brokerVC];
667 [_brokerPopoverVC setAnimates:YES];
668 [_brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
669 [_brokerPopoverVC setDelegate:self];
670 [_brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
671 [videoView setCallDelegate:nil];
672 }
673}
674
675/**
676 * Merge current call with its parent call
677 */
678- (IBAction)mergeCalls:(id)sender
679{
680 auto current = CallModel::instance().selectedCall();
681 current->joinToParent();
682}
683
Alexandre Lision2db8f472015-07-22 15:05:46 -0400684#pragma mark - NSPopOverDelegate
685
Alexandre Lision266fca02015-09-28 14:47:05 -0400686- (void)popoverWillClose:(NSNotification *)notification
687{
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500688 if (_brokerPopoverVC != nullptr) {
689 [_brokerPopoverVC performClose:self];
690 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -0400691 }
Alexandre Lision266fca02015-09-28 14:47:05 -0400692
Alexandre Lision2db8f472015-07-22 15:05:46 -0400693 if (self.addToContactPopover != nullptr) {
694 [self.addToContactPopover performClose:self];
695 self.addToContactPopover = NULL;
696 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400697
Alexandre Lision883719f2015-10-22 17:37:45 -0400698 [self.addContactButton setState:NSOffState];
699 [self.transferButton setState:NSOffState];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500700 [self.addParticipantButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -0400701}
702
703- (void)popoverDidClose:(NSNotification *)notification
704{
705 [videoView setCallDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400706}
707
708#pragma mark - ContactLinkedDelegate
709
710- (void)contactLinked
711{
712 if (self.addToContactPopover != nullptr) {
713 [self.addToContactPopover performClose:self];
714 self.addToContactPopover = NULL;
715 }
716}
717
Alexandre Lision58cab672015-06-09 15:25:40 -0400718#pragma mark - NSSplitViewDelegate
719
720/* 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.
721 */
722- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
723{
724 NSView* rightView = [[splitView subviews] objectAtIndex:1];
725 return ([subview isEqual:rightView]);
726}
727
728
729- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
730{
731 NSView* rightView = [[splitView subviews] objectAtIndex:1];
732 return ([subview isEqual:rightView]);
733}
734
735
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400736# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -0400737
738- (void) callShouldToggleFullScreen
739{
740 if(self.splitView.isInFullScreenMode)
741 [self.splitView exitFullScreenModeWithOptions:nil];
742 else {
743 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
744 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
745 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
746 NSFullScreenModeApplicationPresentationOptions, nil];
747
748 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
749 }
750}
751
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400752-(void) mouseIsMoving:(BOOL) move
753{
754 [[controlsPanel animator] setAlphaValue:move]; // fade out
755 [[headerContainer animator] setAlphaValue:move];
756}
757
Alexandre Lisionc5148052015-03-04 15:10:35 -0500758@end