blob: 97f5d81b91364fb0c36f8d1b49452d85a2956539 [file] [log] [blame]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision4dfcafc2015-08-20 12:43:23 -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 "SmartViewVC.h"
21
22//Qt
23#import <QtMacExtras/qmacfunctions.h>
24#import <QPixmap>
25#import <QIdentityProxyModel>
26#import <QItemSelectionModel>
27
28//LRC
29#import <recentmodel.h>
30#import <callmodel.h>
31#import <call.h>
Alexandre Lision45c18672016-11-02 15:24:45 -040032#import <uri.h>
Alexandre Lisiond14bda32015-10-13 11:34:29 -040033#import <itemdataroles.h>
Alexandre Lision45c18672016-11-02 15:24:45 -040034#import <namedirectory.h>
35#import <accountmodel.h>
36#import <account.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040037#import <person.h>
38#import <contactmethod.h>
39#import <globalinstances.h>
Alexandre Lision0f66bd32016-01-18 11:30:45 -050040#import <phonedirectorymodel.h>
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -040041#import <AvailableAccountModel.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040042
43#import "QNSTreeController.h"
44#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040045#import "views/HoverTableRowView.h"
Alexandre Lision61db3552015-10-22 19:12:52 -040046#import "PersonLinkerVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050047#import "views/IconButton.h"
Alexandre Lision61db3552015-10-22 19:12:52 -040048#import "views/RingOutlineView.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040049#import "views/ContextualTableCellView.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040050
Alexandre Lision61db3552015-10-22 19:12:52 -040051@interface SmartViewVC () <NSOutlineViewDelegate, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> {
Alexandre Lision0f66bd32016-01-18 11:30:45 -050052
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040053 QNSTreeController *treeController;
Alexandre Lision61db3552015-10-22 19:12:52 -040054 NSPopover* addToContactPopover;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040055
56 //UI elements
Alexandre Lision61db3552015-10-22 19:12:52 -040057 __unsafe_unretained IBOutlet RingOutlineView* smartView;
58 __unsafe_unretained IBOutlet NSSearchField* searchField;
Alexandre Lision61db3552015-10-22 19:12:52 -040059 __unsafe_unretained IBOutlet NSTabView* tabbar;
Alexandre Lision45c18672016-11-02 15:24:45 -040060
61 /* Pending ring usernames lookup for the search entry */
62 QMetaObject::Connection usernameLookupConnection;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040063}
64
65@end
66
67@implementation SmartViewVC
68
69// Tags for views
Alexandre Lision57914fa2016-02-10 14:52:24 -050070NSInteger 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;
75NSInteger const CANCEL_BUTTON_TAG = 600;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040076
77- (void)awakeFromNib
78{
79 NSLog(@"INIT SmartView VC");
80
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040081 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040082 [treeController setAvoidsEmptySelection:NO];
83 [treeController setChildrenKeyPath:@"children"];
84
85 [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
86 [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
87 [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
88 [smartView setTarget:self];
Alexandre Lision89edc6a2015-11-09 11:30:47 -050089 [smartView setAction:@selector(selectRow:)];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040090 [smartView setDoubleAction:@selector(placeCall:)];
91
Alexandre Lision61db3552015-10-22 19:12:52 -040092 [smartView setContextMenuDelegate:self];
93 [smartView setShortcutsDelegate:self];
94
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040095 QObject::connect(RecentModel::instance().peopleProxy(),
Alexandre Lisionee098462015-10-22 17:22:50 -040096 &QAbstractItemModel::dataChanged,
97 [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
98 for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
99 {
100 [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
101 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
102 }
103 });
104
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500105 QObject::connect(RecentModel::instance().selectionModel(),
106 &QItemSelectionModel::currentChanged,
107 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500108 if(!current.isValid()) {
109 [smartView deselectAll:nil];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500110 return;
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500111 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500112
113 auto proxyIdx = RecentModel::instance().peopleProxy()->mapFromSource(current);
114 if (proxyIdx.isValid()) {
115 [treeController setSelectionQModelIndex:proxyIdx];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500116 [tabbar selectTabViewItemAtIndex:0];
117 [smartView scrollRowToVisible:proxyIdx.row()];
118 }
119 });
120
Alexandre Lision25fc82a2016-05-08 17:30:40 -0400121 QObject::connect(RecentModel::instance().peopleProxy(),
122 &QAbstractItemModel::rowsInserted,
123 [=](const QModelIndex &parent, int first, int last) {
124 Q_UNUSED(parent)
125 Q_UNUSED(first)
126 Q_UNUSED(last)
127 [smartView scrollRowToVisible:0];
128 });
129
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -0400130 QObject::connect(AvailableAccountModel::instance().selectionModel(),
131 &QItemSelectionModel::currentChanged,
132 [self](const QModelIndex& idx){
133 [self clearSearchField];
134 });
135
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400136 [self.view setWantsLayer:YES];
137 [self.view setLayer:[CALayer layer]];
138 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
139
140 [searchField setWantsLayer:YES];
141 [searchField setLayer:[CALayer layer]];
142 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
143}
144
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500145-(void) selectRow:(id)sender
146{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500147 if ([treeController selectedNodes].count == 0) {
148 RecentModel::instance().selectionModel()->clearCurrentIndex();
149 return;
150 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500151 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
152 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
153 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
154}
155
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400156- (void)placeCall:(id)sender
157{
158 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
159 ContactMethod* m = nil;
160
161 // Double click on an ongoing call
162 if (qIdx.parent().isValid()) {
163 return;
164 }
165
166 if([[treeController selectedNodes] count] > 0) {
167 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
168 m = qvariant_cast<ContactMethod*>(var);
169 if (!m) {
170 // test if it is a person
171 QVariant var = qIdx.data((int)Person::Role::Object);
172 if (var.isValid()) {
173 Person *c = var.value<Person*>();
174 if (c->phoneNumbers().size() > 0) {
175 m = c->phoneNumbers().first();
176 }
177 }
178 }
179 }
180
181 // Before calling check if we properly extracted a contact method and that
182 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400183 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500184 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400185 c->setPeerContactMethod(m);
186 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500187 CallModel::instance().selectCall(c);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400188 }
189}
190
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400191- (void)showHistory
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500192{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500193 [tabbar selectTabViewItemAtIndex:1];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400194}
195
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400196- (void)showContacts
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500197{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500198 [tabbar selectTabViewItemAtIndex:2];
199}
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500200
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400201- (void)showSmartlist
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500202{
203 [tabbar selectTabViewItemAtIndex:0];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400204}
205
206#pragma mark - NSOutlineViewDelegate methods
207
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400208- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
209{
210 return YES;
211}
212
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400213- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
214{
215 return NO;
216}
217
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400218- (void)outlineViewSelectionDidChange:(NSNotification *)notification
219{
220 if ([treeController selectedNodes].count <= 0) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500221 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400222 return;
223 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400224}
225
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400226- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
227{
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400228 QModelIndex proxyIdx = [treeController toQIdx:((NSTreeNode*)item)];
229 QModelIndex qIdx = RecentModel::instance().peopleProxy()->mapToSource(proxyIdx);
230
231 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400232 if (!qIdx.parent().isValid()) {
233 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
234 NSTextField* details = [result viewWithTag:DETAILS_TAG];
235
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500236 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
237 [((ContextualTableCellView*) result) setContextualsControls:controls];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400238
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400239 if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400240 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400241 [((ContextualTableCellView*) result) setActiveState:YES];
242 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400243 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400244 [((ContextualTableCellView*) result) setActiveState:NO];
245 }
246
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500247 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
248 int unread = qIdx.data((int)Ring::Role::UnreadTextMessageCount).toInt();
249 [unreadCount setHidden:(unread == 0)];
250 [unreadCount.layer setCornerRadius:5.0f];
251 [unreadCount setStringValue:qIdx.data((int)Ring::Role::UnreadTextMessageCount).toString().toNSString()];
252
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400253 } else {
254 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500255 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CANCEL_BUTTON_TAG]];
256 [((ContextualTableCellView*) result) setContextualsControls:controls];
257 [((ContextualTableCellView*) result) setActiveState:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400258 NSTextField* details = [result viewWithTag:DETAILS_TAG];
259
260 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
261 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400262
263 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500264 [displayName setStringValue:qIdx.data((int)Ring::Role::Name).toString().toNSString()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400265 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400266
267 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400268 return result;
269}
270
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500271- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
272{
273 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
274}
275
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400276- (IBAction)callClickedAtRow:(id)sender {
277 NSInteger row = [smartView rowForView:sender];
278 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
279 [self placeCall:nil];
280}
281
282- (IBAction)hangUpClickedAtRow:(id)sender {
283 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500284 id callNode = [smartView itemAtRow:row];
285 auto callIdx = [treeController toQIdx:((NSTreeNode*)callNode)];
286
287 if (callIdx.isValid()) {
288 auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(callIdx));
289 call << Call::Action::REFUSE;
290 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400291}
292
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400293- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
294{
295 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500296 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 50.0;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400297}
298
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500299- (IBAction)placeCallFromSearchField:(id)sender
300{
301 if ([searchField stringValue].length == 0) {
302 return;
303 }
Alexandre Lision45c18672016-11-02 15:24:45 -0400304 [self processSearchFieldInputAndStartCall:YES];
305}
306
307- (void) startCallFromURI:(const URI&) uri
308{
309 auto cm = PhoneDirectoryModel::instance().getNumber(uri);
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500310 auto c = CallModel::instance().dialingCall();
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500311 c->setPeerContactMethod(cm);
312 c << Call::Action::ACCEPT;
313 CallModel::instance().selectCall(c);
314}
315
Alexandre Lision45c18672016-11-02 15:24:45 -0400316- (void) startConversationFromURI:(const URI&) uri
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400317{
Alexandre Lision45c18672016-11-02 15:24:45 -0400318 auto cm = PhoneDirectoryModel::instance().getNumber(uri);
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500319 time_t currentTime;
320 ::time(&currentTime);
321 cm->setLastUsed(currentTime);
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500322 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(RecentModel::instance().peopleProxy()->index(0, 0));
323 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400324}
325
Alexandre Lision45c18672016-11-02 15:24:45 -0400326- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
327{
328 NSAlert* alert = [NSAlert alertWithMessageText:title
329 defaultButton:@"Ok"
330 alternateButton:nil
331 otherButton:nil
332 informativeTextWithFormat:message];
333
334 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
335}
336
337- (void) processSearchFieldInputAndStartCall:(BOOL) shouldCall
338{
339 auto ringAccountList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::RING);
340 auto sipAccountList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::SIP);
341 BOOL hasValidRingAccount = NO;
342 BOOL hasValidSIPAccount = NO;
343
344 NSString* noValidAccountTitle = NSLocalizedString(@"No valid account available",
345 @"Alert dialog title");
346 NSString* noValidAccountMessage = NSLocalizedString(@"Make sure you have at least one valid account",
347 @"Alert dialo message");
348
349 Q_FOREACH(auto account, ringAccountList) {
350 if (account->isEnabled() && account->registrationState() == Account::RegistrationState::READY) {
351 hasValidRingAccount = YES;
352 }
353 }
354
355 Q_FOREACH(auto account, sipAccountList) {
356 if (account->isEnabled() && account->registrationState() == Account::RegistrationState::READY) {
357 hasValidSIPAccount = YES;
358 }
359 }
360
361 const auto* numberEntered = [searchField stringValue];
362 URI uri = URI(numberEntered.UTF8String);
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400363 [self clearSearchField];
Alexandre Lision45c18672016-11-02 15:24:45 -0400364
365 if (hasValidRingAccount) {
366 if (uri.protocolHint() == URI::ProtocolHint::RING) {
367 // If it is a RingID start the conversation or the call
368 if (shouldCall) {
369 [self startCallFromURI:uri];
370 } else {
371 [self startConversationFromURI:uri];
372 }
373 } else {
374 // If it's not a ringID and the user has a valid Ring account do a search on the blockchain
375 // If the user wants to make SIP calls, he can unregister his ring account
376 QString usernameToLookup = uri.userinfo();
377 QObject::disconnect(usernameLookupConnection);
378 usernameLookupConnection = QObject::connect(&NameDirectory::instance(),
379 &NameDirectory::registeredNameFound,
380 [self, usernameToLookup, hasValidSIPAccount, shouldCall] (const Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
381 if (usernameToLookup.compare(name) != 0) {
382 //That is not our lookup.
383 return;
384 }
385
386 switch(status) {
387 case NameDirectory::LookupStatus::SUCCESS: {
388 URI uri = URI("ring:" + address);
389 if (shouldCall) {
390 [self startCallFromURI:uri];
391 } else {
392 [self startConversationFromURI:uri];
393 }
394 break;
395 }
396 case NameDirectory::LookupStatus::INVALID_NAME:
397 case NameDirectory::LookupStatus::ERROR:
398 case NameDirectory::LookupStatus::NOT_FOUND: {
399 // The lookup on the blockchain has failed for this user
400 // Check if the user has a valid sip account to use instead
401 if (hasValidSIPAccount) {
402 if (shouldCall) {
403 [self startCallFromURI:usernameToLookup];
404 } else {
405 [self startConversationFromURI:usernameToLookup];
406 }
407 } else {
408 [self displayErrorModalWithTitle:NSLocalizedString(@"Entered name not found",
409 @"Alert dialog title")
410 WithMessage:NSLocalizedString(@"The username you entered do not match a RingID on the network",
411 @"Alert dialog title")];
412 }
413 break;
414 }
415 }
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400416 });
417
Alexandre Lision45c18672016-11-02 15:24:45 -0400418 NameDirectory::instance().lookupName(nullptr, QString(), usernameToLookup);
419 }
420 } else if (hasValidSIPAccount) {
421 if (uri.protocolHint() == URI::ProtocolHint::RING) {
422 // If it is a RingID and no valid account is available, present error
423 [self displayErrorModalWithTitle:noValidAccountTitle
424 WithMessage:noValidAccountMessage];
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400425 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400426 }
427 if (shouldCall) {
428 [self startCallFromURI:uri];
429 } else {
430 [self startConversationFromURI:uri];
431 }
432 } else {
433 [self displayErrorModalWithTitle:noValidAccountTitle
434 WithMessage:noValidAccountMessage];
435 }
436}
437
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400438- (void) clearSearchField
439{
440 [searchField setStringValue:@""];
441 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
442}
443
Alexandre Lision61db3552015-10-22 19:12:52 -0400444- (void) addToContact
445{
446 if ([treeController selectedNodes].count == 0)
447 return;
448
449 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
450 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
451 auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
452 if (contactmethod.isEmpty())
453 return;
454
455 if (addToContactPopover != nullptr) {
456 [addToContactPopover performClose:self];
457 addToContactPopover = NULL;
458 } else if (contactmethod.first()) {
459 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
460 [editorVC setMethodToLink:contactmethod.first()];
461 [editorVC setContactLinkedDelegate:self];
462 addToContactPopover = [[NSPopover alloc] init];
463 [addToContactPopover setContentSize:editorVC.view.frame.size];
464 [addToContactPopover setContentViewController:editorVC];
465 [addToContactPopover setAnimates:YES];
466 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
467 [addToContactPopover setDelegate:self];
468
469 [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
470 ofView:smartView preferredEdge:NSMaxXEdge];
471 }
472}
473
Alexandre Lision72a669e2016-09-14 17:52:16 -0400474/**
475 Copy a NSString in the general Pasteboard
476
477 @param sender the NSObject containing the represented object to copy
478 */
479- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400480{
481 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
482 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
483 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
484}
485
486- (void) callNumber:(id) sender
487{
488 Call* c = CallModel::instance().dialingCall();
489 c->setDialNumber(QString::fromNSString([sender representedObject]));
490 c << Call::Action::ACCEPT;
491}
492
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400493#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400494
495- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
496{
497 if (commandSelector == @selector(insertNewline:)) {
498 if([[searchField stringValue] isNotEqualTo:@""]) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400499 [self processSearchFieldInputAndStartCall:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400500 return YES;
501 }
502 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400503 return NO;
504}
505
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500506- (void)controlTextDidChange:(NSNotification *) notification
507{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500508 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
509}
510
Alexandre Lision61db3552015-10-22 19:12:52 -0400511#pragma mark - NSPopOverDelegate
512
513- (void)popoverDidClose:(NSNotification *)notification
514{
515 if (addToContactPopover != nullptr) {
516 [addToContactPopover performClose:self];
517 addToContactPopover = NULL;
518 }
519}
520
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400521
Alexandre Lision61db3552015-10-22 19:12:52 -0400522#pragma mark - ContactLinkedDelegate
523
524- (void)contactLinked
525{
526 if (addToContactPopover != nullptr) {
527 [addToContactPopover performClose:self];
528 addToContactPopover = NULL;
529 }
530}
531
532#pragma mark - KeyboardShortcutDelegate
533
534- (void) onAddShortcut
535{
536 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
537 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
538 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
539 if (contactmethods.isEmpty())
540 return;
541
542 auto contactmethod = contactmethods.first();
543 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
544 [self addToContact];
545 }
546}
547
548#pragma mark - ContextMenuDelegate
549
Alexandre Lision12946a72016-03-08 13:39:34 -0500550- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision61db3552015-10-22 19:12:52 -0400551{
Alexandre Lision12946a72016-03-08 13:39:34 -0500552 auto qIdx = [treeController toQIdx:item];
553
554 if (!qIdx.isValid()) {
555 return nil;
556 }
557
Alexandre Lision61db3552015-10-22 19:12:52 -0400558 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
559 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
560 if (contactmethods.isEmpty())
561 return nil;
562
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400563 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
564
565 if (contactmethods.size() == 1
566 && !contactmethods.first()->contact()
567 || contactmethods.first()->contact()->isPlaceHolder()) {
568
Alexandre Lision61db3552015-10-22 19:12:52 -0400569 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
570 action:@selector(addToContact)
571 keyEquivalent:@"a"
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400572 atIndex:theMenu.itemArray.count];
Alexandre Lision72a669e2016-09-14 17:52:16 -0400573 } else if (auto person = contactmethods.first()->contact()) {
574 NSMenuItem* copyNameItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy name", @"Contextual menu action")
575 action:@selector(copyStringToPasteboard:)
576 keyEquivalent:@""];
577
578 [copyNameItem setRepresentedObject:person->formattedName().toNSString()];
579 [theMenu addItem:copyNameItem];
Alexandre Lision61db3552015-10-22 19:12:52 -0400580 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400581
582 NSMenu* copySubmenu = [[NSMenu alloc] init];
583 NSMenu* callSubmenu = [[NSMenu alloc] init];
584
Alexandre Lision72a669e2016-09-14 17:52:16 -0400585 for (auto cm : contactmethods) {
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400586 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
Alexandre Lision72a669e2016-09-14 17:52:16 -0400587 action:@selector(copyStringToPasteboard:)
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400588 keyEquivalent:@""];
589
590 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
591 [copySubmenu addItem:tmpCopyItem];
592
593 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
594 action:@selector(callNumber:)
595 keyEquivalent:@""];
596 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
597 [callSubmenu addItem:tmpCallItem];
598 }
599
Alexandre Lision72a669e2016-09-14 17:52:16 -0400600 NSMenuItem* copyNumberItem = [[NSMenuItem alloc] init];
601 [copyNumberItem setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
602 [copyNumberItem setSubmenu:copySubmenu];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400603
604 NSMenuItem* callItems = [[NSMenuItem alloc] init];
605 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
606 [callItems setSubmenu:callSubmenu];
607
Alexandre Lision72a669e2016-09-14 17:52:16 -0400608 [theMenu insertItem:copyNumberItem atIndex:theMenu.itemArray.count];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400609 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
610 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
611
612 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400613}
614
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400615@end