Commit c514805c authored by Alexandre Lision's avatar Alexandre Lision Committed by Alexandre Lision

ui: add call screen interface

- in/out animation
- switch calls in conversation tree
- basic controls over call: accept/hangup/hold
- video

Refs #67611

Change-Id: I1d302d2539cb473e27ea469781b72f44963eb0a0
parent f5fc479f
......@@ -38,6 +38,7 @@ SET(ringclient_SRCS
AccAdvancedVC.mm
AccSecurityVC.mm
AccountsVC.mm
CurrentCallVC.mm
AudioPrefsVC.mm
VideoPrefsVC.mm
GeneralPrefsVC.mm
......@@ -47,6 +48,7 @@ SET(ringclient_SRCS
SET(ringclient_FORMS
MainMenu.xib
RingWindow.xib
CurrentCall.xib
GeneralPrefs.xib
Accounts.xib
AccGeneral.xib
......@@ -62,6 +64,7 @@ SET(ringclient_FORMS
SET(ringclient_HDRS
AppDelegate.h
RingWindowController.h
CurrentCallVC.h
ConversationsViewController.h
PreferencesViewController.h
AccGeneralVC.h
......
......@@ -34,12 +34,9 @@
#import "QNSTreeController.h"
@interface ConversationsViewController : NSViewController <NSOutlineViewDelegate> {
NSOutlineView *conversationsView;
}
@property QNSTreeController *treeController;
@property (assign) IBOutlet NSOutlineView *conversationsView;
}
@end
#endif // CONVERSATIONSVC_H
\ No newline at end of file
#endif // CONVERSATIONSVC_H
......@@ -30,29 +30,30 @@
#import "ConversationsViewController.h"
#import <callmodel.h>
#import <QtCore/qitemselectionmodel.h>
#import "CurrentCallVC.h"
#define COLUMNID_CONVERSATIONS @"ConversationsColumn" // the single column name in our outline view
@interface ConversationsViewController ()
@property CurrentCallVC* currentVC;
@property (assign) IBOutlet NSView *currentCallView;
@property QNSTreeController *treeController;
@property (assign) IBOutlet NSOutlineView *conversationsView;
@end
@implementation ConversationsViewController
@synthesize conversationsView;
@synthesize treeController;
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
NSLog(@"INIT Conversations VC");
}
return self;
}
@synthesize currentVC;
@synthesize currentCallView;
- (void)awakeFromNib
{
NSLog(@"awakeFromNib");
NSLog(@"INIT Conversations VC");
treeController = [[QNSTreeController alloc] initWithQModel:CallModel::instance()];
......@@ -65,6 +66,24 @@
NSInteger idx = [conversationsView columnWithIdentifier:COLUMNID_CONVERSATIONS];
[[[[self.conversationsView tableColumns] objectAtIndex:idx] headerCell] setStringValue:@"Conversations"];
QObject::connect(CallModel::instance(),
&QAbstractItemModel::dataChanged,
[=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
NSLog(@"data changed for call tree %d, %d", topLeft.row(), bottomRight.row());
[conversationsView reloadDataForRowIndexes:
[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(topLeft.row(), bottomRight.row() + 1)]
columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, conversationsView.tableColumns.count)]];
});
currentVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
[currentCallView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[[currentVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[currentCallView addSubview:[self.currentVC view]];
[currentVC initFrame];
}
#pragma mark - NSOutlineViewDelegate methods
......@@ -83,24 +102,8 @@
- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSCell *returnCell = [tableColumn dataCell];
if(item == nil)
return returnCell;
if ([[tableColumn identifier] isEqualToString:COLUMNID_CONVERSATIONS])
{
NSIndexPath* idx = ((NSTreeNode*)item).indexPath;
NSUInteger myArray[[idx length]];
[idx getIndexes:myArray];
NSLog(@"dataCellForTableColumn, indexPath: %lu", (unsigned long)myArray[0]);
QModelIndex qIdx = CallModel::instance()->index(myArray[0], 0);
QVariant test = CallModel::instance()->data(qIdx, Qt::DisplayRole);
}
return returnCell;
}
......@@ -160,7 +163,13 @@
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
// ask the tree controller for the current selection
NSLog(@"outlineViewSelectionDidChange!!");
if([[treeController selectedNodes] count] > 0) {
QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
//Update details view by changing selection
CallModel::instance()->selectionModel()->setCurrentIndex(qIdx, QItemSelectionModel::ClearAndSelect);
} else {
CallModel::instance()->selectionModel()->clearCurrentIndex();
}
}
......
This diff is collapsed.
/*
* Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
* 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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#ifndef CURRENTCALLVC_H
#define CURRENTCALLVC_H
#import <Cocoa/Cocoa.h>
class Call;
@interface CurrentCallVC : NSViewController {
}
- (void) initFrame;
@end
#endif // CURRENTCALLVC_H
\ No newline at end of file
/*
* Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
* 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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#import "CurrentCallVC.h"
#import <QuartzCore/QuartzCore.h>
#import <call.h>
#import <callmodel.h>
#import <useractionmodel.h>
#import <contactmethod.h>
#import <qabstractitemmodel.h>
#import <QItemSelectionModel>
#import <QItemSelection>
#import <video/previewmanager.h>
#import <video/renderer.h>
/** FrameReceiver class - delegate for AVCaptureSession
*/
@interface RendererConnectionsHolder : NSObject
@property QMetaObject::Connection frameUpdated;
@property QMetaObject::Connection started;
@property QMetaObject::Connection stopped;
@end
@implementation RendererConnectionsHolder
@end
@interface CurrentCallVC ()
@property (assign) IBOutlet NSTextField *personLabel;
@property (assign) IBOutlet NSTextField *stateLabel;
@property (assign) IBOutlet NSButton *holdOnOffButton;
@property (assign) IBOutlet NSButton *hangUpButton;
@property (assign) IBOutlet NSButton *recordOnOffButton;
@property (assign) IBOutlet NSButton *pickUpButton;
@property (assign) IBOutlet NSTextField *timeSpentLabel;
@property (assign) IBOutlet NSView *controlsPanel;
@property QHash<int, NSButton*> actionHash;
// Video
@property (assign) IBOutlet NSView *videoView;
@property CALayer* videoLayer;
@property (assign) IBOutlet NSView *previewView;
@property CALayer* previewLayer;
@property RendererConnectionsHolder* previewHolder;
@property RendererConnectionsHolder* videoHolder;
@end
@implementation CurrentCallVC
@synthesize personLabel;
@synthesize actionHash;
@synthesize stateLabel;
@synthesize holdOnOffButton;
@synthesize hangUpButton;
@synthesize recordOnOffButton;
@synthesize pickUpButton;
@synthesize timeSpentLabel;
@synthesize controlsPanel;
@synthesize videoView;
@synthesize videoLayer;
@synthesize previewLayer;
@synthesize previewView;
@synthesize previewHolder;
@synthesize videoHolder;
- (void) updateActions
{
for(int i = 0 ; i <= CallModel::instance()->userActionModel()->rowCount() ; i++) {
const QModelIndex& idx = CallModel::instance()->userActionModel()->index(i,0);
NSButton* a = actionHash[(int)qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION))];
if (a != nil) {
[a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
[a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
}
}
}
-(void) updateCall
{
QModelIndex callIdx = CallModel::instance()->selectionModel()->currentIndex();
[personLabel setStringValue:CallModel::instance()->data(callIdx, Qt::DisplayRole).toString().toNSString()];
[timeSpentLabel setStringValue:CallModel::instance()->data(callIdx, (int)Call::Role::Length).toString().toNSString()];
Call::State state = CallModel::instance()->data(callIdx, (int)Call::Role::State).value<Call::State>();
switch (state) {
case Call::State::INITIALIZATION:
[stateLabel setStringValue:@"Initializing"];
break;
case Call::State::RINGING:
[stateLabel setStringValue:@"Ringing"];
break;
case Call::State::CURRENT:
[stateLabel setStringValue:@"Current"];
break;
case Call::State::HOLD:
[stateLabel setStringValue:@"On Hold"];
break;
case Call::State::BUSY:
[stateLabel setStringValue:@"Busy"];
break;
case Call::State::OVER:
[stateLabel setStringValue:@"Finished"];
break;
case Call::State::FAILURE:
[stateLabel setStringValue:@"Failure"];
break;
default:
break;
}
}
- (void)awakeFromNib
{
NSLog(@"INIT CurrentCall VC");
[self.view setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
[self.view setLayer:[CALayer layer]];
//self.view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
[controlsPanel setWantsLayer:YES];
[controlsPanel setLayer:[CALayer layer]];
[controlsPanel.layer setZPosition:2.0];
[controlsPanel.layer setBackgroundColor:[NSColor whiteColor].CGColor];
actionHash[ (int)UserActionModel::Action::ACCEPT ] = pickUpButton;
actionHash[ (int)UserActionModel::Action::HOLD ] = holdOnOffButton;
actionHash[ (int)UserActionModel::Action::RECORD ] = recordOnOffButton;
actionHash[ (int)UserActionModel::Action::HANGUP ] = hangUpButton;
//actionHash[ (int)UserActionModel::Action::MUTE_AUDIO ] = action_mute_capture;
//actionHash[ (int)UserActionModel::Action::SERVER_TRANSFER ] = action_transfer;
videoLayer = [CALayer layer];
[videoView setWantsLayer:YES];
[videoView setLayer:videoLayer];
[videoView.layer setBackgroundColor:[NSColor blackColor].CGColor];
[videoView.layer setFrame:videoView.frame];
[videoView.layer setContentsGravity:kCAGravityResizeAspect];
//[videoView.layer setBounds:CGRectMake(0, 0, videoView.frame.size.width, videoView.frame.size.height)];
previewLayer = [CALayer layer];
[previewView setWantsLayer:YES];
[previewView setLayer:previewLayer];
[previewLayer setBackgroundColor:[NSColor blackColor].CGColor];
[previewLayer setContentsGravity:kCAGravityResizeAspect];
[previewLayer setFrame:previewView.frame];
[controlsPanel setWantsLayer:YES];
[controlsPanel setLayer:[CALayer layer]];
[controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
[controlsPanel.layer setFrame:controlsPanel.frame];
[self connect];
}
- (void) connect
{
QObject::connect(CallModel::instance()->selectionModel(),
&QItemSelectionModel::currentChanged,
[=](const QModelIndex &current, const QModelIndex &previous) {
NSLog(@"selection changed!");
if(!current.isValid()) {
[self animateOut];
return;
}
[self updateCall];
[self updateActions];
[self animateOut];
});
QObject::connect(CallModel::instance(),
&QAbstractItemModel::dataChanged,
[=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
NSLog(@"data changed!");
[self updateCall];
});
QObject::connect(CallModel::instance()->userActionModel(),
&QAbstractItemModel::dataChanged,
[=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
NSLog(@"useraction changed");
const int first(topLeft.row()),last(bottomRight.row());
for(int i = first; i <= last;i++) {
const QModelIndex& idx = CallModel::instance()->userActionModel()->index(i,0);
NSButton* a = actionHash[(int)qvariant_cast<UserActionModel::Action>(idx.data(UserActionModel::Role::ACTION))];
if (a) {
[a setEnabled:(idx.flags() & Qt::ItemIsEnabled)];
[a setState:(idx.data(Qt::CheckStateRole) == Qt::Checked) ? NSOnState : NSOffState];
}
}
});
QObject::connect(CallModel::instance(),
&CallModel::callStateChanged,
[self](Call* c, Call::State state) {
NSLog(@"callStateChanged");
[self updateCall];
});
}
-(void) connectVideoSignals
{
QModelIndex idx = CallModel::instance()->selectionModel()->currentIndex();
Call* call = CallModel::instance()->getCall(idx);
QObject::connect(call,
&Call::videoStarted,
[=](Video::Renderer* renderer) {
NSLog(@"Video started!");
[self connectVideoRenderer:renderer];
});
if(call->videoRenderer())
{
NSLog(@"GONNA CONNECT TO FRAMES");
[self connectVideoRenderer:call->videoRenderer()];
}
[self connectPreviewRenderer];
}
-(void) connectPreviewRenderer
{
previewHolder.started = QObject::connect(Video::PreviewManager::instance(),
&Video::PreviewManager::previewStarted,
[=](Video::Renderer* renderer) {
NSLog(@"Preview started");
QObject::disconnect(previewHolder.frameUpdated);
previewHolder.frameUpdated = QObject::connect(renderer,
&Video::Renderer::frameUpdated,
[=]() {
[self renderer:Video::PreviewManager::instance()->previewRenderer()
renderFrameForView:previewView];
});
});
previewHolder.stopped = QObject::connect(Video::PreviewManager::instance(),
&Video::PreviewManager::previewStopped,
[=](Video::Renderer* renderer) {
NSLog(@"Preview stopped");
QObject::disconnect(previewHolder.frameUpdated);
[previewView.layer setContents:nil];
});
previewHolder.frameUpdated = QObject::connect(Video::PreviewManager::instance()->previewRenderer(),
&Video::Renderer::frameUpdated,
[=]() {
[self renderer:Video::PreviewManager::instance()->previewRenderer()
renderFrameForView:previewView];
});
}
-(void) connectVideoRenderer: (Video::Renderer*)renderer
{
videoHolder.frameUpdated = QObject::connect(renderer,
&Video::Renderer::frameUpdated,
[=]() {
[self renderer:renderer renderFrameForView:videoView];
});
videoHolder.started = QObject::connect(renderer,
&Video::Renderer::started,
[=]() {
NSLog(@"Renderer started");
QObject::disconnect(videoHolder.frameUpdated);
videoHolder.frameUpdated = QObject::connect(renderer,
&Video::Renderer::frameUpdated,
[=]() {
[self renderer:renderer renderFrameForView:videoView];
});
});
videoHolder.stopped = QObject::connect(renderer,
&Video::Renderer::stopped,
[=]() {
NSLog(@"Renderer stopped");
QObject::disconnect(videoHolder.frameUpdated);
[videoView.layer setContents:nil];
});
}
-(void) renderer: (Video::Renderer*)renderer renderFrameForView:(NSView*) view
{
const QByteArray& data = renderer->currentFrame();
QSize res = renderer->size();
auto buf = reinterpret_cast<const unsigned char*>(data.data());
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef newContext = CGBitmapContextCreate((void *)buf,
res.width(),
res.height(),
8,
4*res.width(),
colorSpace,
kCGImageAlphaPremultipliedLast);
CGImageRef newImage = CGBitmapContextCreateImage(newContext);
/*We release some components*/
CGContextRelease(newContext);
CGColorSpaceRelease(colorSpace);
[CATransaction begin];
view.layer.contents = (__bridge id)newImage;
[CATransaction commit];
CFRelease(newImage);
}
- (void) initFrame
{
[self.view setFrame:self.view.superview.bounds];
[self.view setHidden:YES];
self.view.layer.position = self.view.frame.origin;
}
# pragma private IN/OUT animations
-(void) animateIn
{
NSLog(@"animateIn");
CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
[self.view setHidden:NO];
[CATransaction begin];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
[animation setFromValue:[NSValue valueWithPoint:frame.origin]];
[animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
[animation setDuration:0.2f];
[animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
[CATransaction setCompletionBlock:^{
NSLog(@"COMPLETION IN");
[self connectVideoSignals];
}];
[self.view.layer addAnimation:animation forKey:animation.keyPath];
[CATransaction commit];
}
-(void) cleanUp
{
[videoView.layer setContents:nil];
[previewView.layer setContents:nil];
}
-(void) animateOut
{
NSLog(@"animateOut");
if(self.view.frame.origin.x < 0) {
NSLog(@"Already hidden");
if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
[self animateIn];
}
return;
}
CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
[CATransaction begin];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
[animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
[animation setToValue:[NSValue valueWithPoint:frame.origin]];
[animation setDuration:0.2f];
[animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.7 :0.9 :1 :1]];
[CATransaction setCompletionBlock:^{
[self.view setHidden:YES];
[self cleanUp];
if (CallModel::instance()->selectionModel()->currentIndex().isValid()) {
[self animateIn];
}
}];
[self.view.layer addAnimation:animation forKey:animation.keyPath];
[CATransaction commit];
}