blob: 3c0bb3e275e889ce7691a2fe1d810404424cf37e [file] [log] [blame]
Alexandre Lision883719f2015-10-22 17:37:45 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision883719f2015-10-22 17:37:45 -04003 * 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.
18 */
19
20#import "BrokerVC.h"
21
22#import <QSortFilterProxyModel>
23#import <QItemSelectionModel>
24#import <QtMacExtras/qmacfunctions.h>
25#import <QPixmap>
Alexandre Lision89edc6a2015-11-09 11:30:47 -050026#import <QMimeData>
Alexandre Lision883719f2015-10-22 17:37:45 -040027
28//LRC
29#import <recentmodel.h>
30#import <callmodel.h>
31#import <call.h>
32#import <person.h>
33#import <globalinstances.h>
34#import <contactmethod.h>
35#import <phonedirectorymodel.h>
36
37#import "QNSTreeController.h"
38#import "delegates/ImageManipulationDelegate.h"
39
40// Display all items from peopleproxy() except current call
41class NotCurrentItemModel : public QSortFilterProxyModel
42{
43public:
44 NotCurrentItemModel(QSortFilterProxyModel* parent) : QSortFilterProxyModel(parent)
45 {
46 setSourceModel(parent);
47 }
48
49 virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
50 {
51 auto originIdx = ((QSortFilterProxyModel*)sourceModel())->mapToSource(sourceModel()->index(source_row,0,source_parent));
52 auto c = RecentModel::instance().getActiveCall(originIdx);
53
54 return (!c || (c && (c->state() != Call::State::CURRENT))) &&
55 QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
56 }
57};
58@interface BrokerVC ()
59
60@property BrokerMode mode;
61@property (unsafe_unretained) IBOutlet NSOutlineView *smartView;
62@property (strong) QNSTreeController *treeController;
63@property QSortFilterProxyModel* recentFilterModel;
64
65@end
66
67@implementation BrokerVC
68
69// Tags for views
70NSInteger const IMAGE_TAG = 100;
71NSInteger const DISPLAYNAME_TAG = 200;
72NSInteger const DETAILS_TAG = 300;
73NSInteger const CALL_BUTTON_TAG = 400;
74NSInteger const TXT_BUTTON_TAG = 500;
75
76- (instancetype)initWithMode:(BrokerMode)m {
77 self = [super init];
78 if (self) {
79 [self setMode:m];
80 }
81 return self;
82}
83
84- (NSString *)nibName
85{
86 return @"Broker";
87}
88
89- (void)dealloc
90{
91 delete _recentFilterModel;
92}
93
94- (void)loadView
95{
96 [super loadView];
97 _recentFilterModel = new NotCurrentItemModel(RecentModel::instance().peopleProxy());
98 _treeController = [[QNSTreeController alloc] initWithQModel:_recentFilterModel];
99
100 [_treeController setAvoidsEmptySelection:NO];
101 [_treeController setChildrenKeyPath:@"children"];
102
103 [_smartView bind:@"content" toObject:_treeController withKeyPath:@"arrangedObjects" options:nil];
104 [_smartView bind:@"sortDescriptors" toObject:_treeController withKeyPath:@"sortDescriptors" options:nil];
105 [_smartView bind:@"selectionIndexPaths" toObject:_treeController withKeyPath:@"selectionIndexPaths" options:nil];
106 [_smartView setTarget:self];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500107
108 if ([self mode] == BrokerMode::TRANSFER) {
109 [_smartView setDoubleAction:@selector(placeTransfer:)];
110 } else {
111 [_smartView setDoubleAction:@selector(addParticipant:)];
112 }
113
Alexandre Lision883719f2015-10-22 17:37:45 -0400114}
115
116// -------------------------------------------------------------------------------
117// transfer on click on Person or ContactMethod
118// -------------------------------------------------------------------------------
119- (void)placeTransfer:(id)sender
120{
121 auto current = CallModel::instance().selectedCall();
122
123 if (!current || [_treeController selectedNodes].count == 0)
124 return;
125
126 QModelIndex qIdx = [_treeController toQIdx:[_treeController selectedNodes][0]];
127 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(_recentFilterModel->mapToSource(qIdx));
128
129 auto transfer = RecentModel::instance().getActiveCall(originIdx);
130 if (transfer) { //realise an attended transfer between the two calls
131 CallModel::instance().attendedTransfer(current, transfer);
132 return;
133 }
134
135 ContactMethod* m = nil;
136 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
137 if (contactmethods.size() > 0) { // Before calling check if we properly extracted at least one contact method
138 m = contactmethods.first();
139 CallModel::instance().transfer(current, m);
140 }
141}
142
143// -------------------------------------------------------------------------------
144// transfer to unknown URI
145// -------------------------------------------------------------------------------
146- (void) transferTo:(NSString*) uri
147{
148 auto current = CallModel::instance().selectedCall();
149 if (!current)
150 return;
151 auto number = PhoneDirectoryModel::instance().getNumber(QString::fromNSString(uri));
152 CallModel::instance().transfer(current, number);
153}
154
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500155// -------------------------------------------------------------------------------
156// place a call to the future participant on click on Person or ContactMethod
157// -------------------------------------------------------------------------------
158- (void)addParticipant:(id)sender
159{
160 auto current = CallModel::instance().selectedCall();
161
162 if (!current || [_treeController selectedNodes].count == 0)
163 return;
164
165 QModelIndex qIdx = [_treeController toQIdx:[_treeController selectedNodes][0]];
166 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(_recentFilterModel->mapToSource(qIdx));
167
168 auto participant = RecentModel::instance().getActiveCall(originIdx);
169 if (participant) { //join this call with the current one
170 QModelIndexList source_list;
171 source_list << CallModel::instance().getIndex(current);
172 auto idx_call_dest = CallModel::instance().getIndex(participant);
173 auto mimeData = CallModel::instance().mimeData(source_list);
174 auto action = Call::DropAction::Conference;
175 mimeData->setProperty("dropAction", action);
176
177 if (CallModel::instance().dropMimeData(mimeData, Qt::MoveAction, idx_call_dest.row(), idx_call_dest.column(), idx_call_dest.parent())) {
178 NSLog(@"OK");
179 } else {
180 NSLog(@"could not drop mime data");
181 }
182 return;
183 }
184
185 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
186 if (contactmethods.size() > 0) { // Before calling check if we properly extracted at least one contact method
187 auto call = CallModel::instance().dialingCall(contactmethods.first());
188 call->setParentCall(current);
189 call << Call::Action::ACCEPT;
190 CallModel::instance().selectCall(call);
191 }
192}
193
194// -------------------------------------------------------------------------------
195// place a call to the future participant with entered URI
196// -------------------------------------------------------------------------------
197- (void) addParticipantFromUri:(NSString*) uri
198{
199 auto current = CallModel::instance().selectedCall();
200 if (!current)
201 return;
202 auto number = PhoneDirectoryModel::instance().getNumber(QString::fromNSString(uri));
203 auto dialing = CallModel::instance().dialingCall(number);
204 dialing->setParentCall(current);
205 dialing << Call::Action::ACCEPT;
206 CallModel::instance().selectCall(dialing);
207}
208
Alexandre Lision883719f2015-10-22 17:37:45 -0400209#pragma mark - NSOutlineViewDelegate methods
210
211// -------------------------------------------------------------------------------
212// shouldSelectItem:item
213// -------------------------------------------------------------------------------
214- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
215{
216 return YES;
217}
218
219// -------------------------------------------------------------------------------
220// shouldEditTableColumn:tableColumn:item
221//
222// Decide to allow the edit of the given outline view "item".
223// -------------------------------------------------------------------------------
224- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
225{
226 return NO;
227}
228
229// -------------------------------------------------------------------------------
230// View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
231// -------------------------------------------------------------------------------
232- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
233{
234 auto qIdx = [_treeController toQIdx:((NSTreeNode*)item)];
235 NSTableCellView *result;
236
237 if (!qIdx.parent().isValid()) {
238 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
239 } else {
240 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
241 }
242
243 auto finalIdx = RecentModel::instance().peopleProxy()->mapToSource(_recentFilterModel->mapToSource(qIdx));
244
245 NSTextField* details = [result viewWithTag:DETAILS_TAG];
246 if (auto call = RecentModel::instance().getActiveCall(finalIdx)) {
247 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
248 } else {
249 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
250 }
251 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
252 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
253 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision196545b2016-05-13 17:05:13 -0400254 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
Alexandre Lision883719f2015-10-22 17:37:45 -0400255 return result;
256}
257
258// -------------------------------------------------------------------------------
259// View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
260// -------------------------------------------------------------------------------
261- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
262{
263 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
264}
265
266- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
267{
268 QModelIndex qIdx = [_treeController toQIdx:((NSTreeNode*)item)];
269 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 45.0;
270}
271
272#pragma mark - NSTextFieldDelegate
273
274- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
275{
276 if (commandSelector == @selector(insertNewline:)) {
277 if([fieldEditor.textStorage.string isNotEqualTo:@""]) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500278
279 if ([self mode] == BrokerMode::TRANSFER) {
280 [self transferTo:fieldEditor.textStorage.string];
281 } else {
282 [self addParticipantFromUri:fieldEditor.textStorage.string];
283 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400284 return YES;
285 }
286 }
287
288 return NO;
289}
290
291- (void)controlTextDidChange:(NSNotification *) notification
292{
293 NSTextView *textView = notification.userInfo[@"NSFieldEditor"];
294 _recentFilterModel->setFilterRegExp(QRegExp(QString::fromNSString(textView.textStorage.string), Qt::CaseInsensitive, QRegExp::FixedString));
295 [_smartView scrollToBeginningOfDocument:nil];
296}
297
298
299@end