blob: f6e02e3e34d2b9452e5bcea817edc79868ec2e58 [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];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400264 NSString* displayNameString = qIdx.data((int)Ring::Role::Name).toString().toNSString();
265 if(displayNameString.length == 0) {
266 QVector<ContactMethod*> contactMethods = RecentModel::instance().getContactMethods(qIdx);
267 if(contactMethods.count() > 0) {
268 ContactMethod* method = contactMethods[0];
269 for(int i = 0; i < contactMethods.count(); i++) {
270 if (contactMethods[i]->lastUsed() > method->lastUsed()) {
271 method = contactMethods[i];
272 }
273 }
274 displayNameString = method->getBestId().toNSString();
275 }
276 }
277 [displayName setStringValue:displayNameString];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400278 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400279
280 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400281 return result;
282}
283
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500284- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
285{
286 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
287}
288
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400289- (IBAction)callClickedAtRow:(id)sender {
290 NSInteger row = [smartView rowForView:sender];
291 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
292 [self placeCall:nil];
293}
294
295- (IBAction)hangUpClickedAtRow:(id)sender {
296 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500297 id callNode = [smartView itemAtRow:row];
298 auto callIdx = [treeController toQIdx:((NSTreeNode*)callNode)];
299
300 if (callIdx.isValid()) {
301 auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(callIdx));
302 call << Call::Action::REFUSE;
303 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400304}
305
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400306- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
307{
308 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500309 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 50.0;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400310}
311
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500312- (IBAction)placeCallFromSearchField:(id)sender
313{
314 if ([searchField stringValue].length == 0) {
315 return;
316 }
Alexandre Lision45c18672016-11-02 15:24:45 -0400317 [self processSearchFieldInputAndStartCall:YES];
318}
319
320- (void) startCallFromURI:(const URI&) uri
321{
322 auto cm = PhoneDirectoryModel::instance().getNumber(uri);
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500323 auto c = CallModel::instance().dialingCall();
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500324 c->setPeerContactMethod(cm);
325 c << Call::Action::ACCEPT;
326 CallModel::instance().selectCall(c);
327}
328
Alexandre Lision45c18672016-11-02 15:24:45 -0400329- (void) startConversationFromURI:(const URI&) uri
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400330{
Alexandre Lision45c18672016-11-02 15:24:45 -0400331 auto cm = PhoneDirectoryModel::instance().getNumber(uri);
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500332 time_t currentTime;
333 ::time(&currentTime);
334 cm->setLastUsed(currentTime);
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500335 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(RecentModel::instance().peopleProxy()->index(0, 0));
336 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400337}
338
Alexandre Lision45c18672016-11-02 15:24:45 -0400339- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
340{
341 NSAlert* alert = [NSAlert alertWithMessageText:title
342 defaultButton:@"Ok"
343 alternateButton:nil
344 otherButton:nil
345 informativeTextWithFormat:message];
346
347 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
348}
349
350- (void) processSearchFieldInputAndStartCall:(BOOL) shouldCall
351{
352 auto ringAccountList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::RING);
353 auto sipAccountList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::SIP);
354 BOOL hasValidRingAccount = NO;
355 BOOL hasValidSIPAccount = NO;
356
357 NSString* noValidAccountTitle = NSLocalizedString(@"No valid account available",
358 @"Alert dialog title");
359 NSString* noValidAccountMessage = NSLocalizedString(@"Make sure you have at least one valid account",
360 @"Alert dialo message");
361
362 Q_FOREACH(auto account, ringAccountList) {
363 if (account->isEnabled() && account->registrationState() == Account::RegistrationState::READY) {
364 hasValidRingAccount = YES;
365 }
366 }
367
368 Q_FOREACH(auto account, sipAccountList) {
369 if (account->isEnabled() && account->registrationState() == Account::RegistrationState::READY) {
370 hasValidSIPAccount = YES;
371 }
372 }
373
374 const auto* numberEntered = [searchField stringValue];
375 URI uri = URI(numberEntered.UTF8String);
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400376 [self clearSearchField];
Alexandre Lision45c18672016-11-02 15:24:45 -0400377
378 if (hasValidRingAccount) {
379 if (uri.protocolHint() == URI::ProtocolHint::RING) {
380 // If it is a RingID start the conversation or the call
381 if (shouldCall) {
382 [self startCallFromURI:uri];
383 } else {
384 [self startConversationFromURI:uri];
385 }
386 } else {
387 // If it's not a ringID and the user has a valid Ring account do a search on the blockchain
388 // If the user wants to make SIP calls, he can unregister his ring account
389 QString usernameToLookup = uri.userinfo();
390 QObject::disconnect(usernameLookupConnection);
391 usernameLookupConnection = QObject::connect(&NameDirectory::instance(),
392 &NameDirectory::registeredNameFound,
393 [self, usernameToLookup, hasValidSIPAccount, shouldCall] (const Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
394 if (usernameToLookup.compare(name) != 0) {
395 //That is not our lookup.
396 return;
397 }
398
399 switch(status) {
400 case NameDirectory::LookupStatus::SUCCESS: {
401 URI uri = URI("ring:" + address);
402 if (shouldCall) {
403 [self startCallFromURI:uri];
404 } else {
405 [self startConversationFromURI:uri];
406 }
407 break;
408 }
409 case NameDirectory::LookupStatus::INVALID_NAME:
410 case NameDirectory::LookupStatus::ERROR:
411 case NameDirectory::LookupStatus::NOT_FOUND: {
412 // The lookup on the blockchain has failed for this user
413 // Check if the user has a valid sip account to use instead
414 if (hasValidSIPAccount) {
415 if (shouldCall) {
416 [self startCallFromURI:usernameToLookup];
417 } else {
418 [self startConversationFromURI:usernameToLookup];
419 }
420 } else {
421 [self displayErrorModalWithTitle:NSLocalizedString(@"Entered name not found",
422 @"Alert dialog title")
423 WithMessage:NSLocalizedString(@"The username you entered do not match a RingID on the network",
424 @"Alert dialog title")];
425 }
426 break;
427 }
428 }
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400429 });
430
Alexandre Lision45c18672016-11-02 15:24:45 -0400431 NameDirectory::instance().lookupName(nullptr, QString(), usernameToLookup);
432 }
433 } else if (hasValidSIPAccount) {
434 if (uri.protocolHint() == URI::ProtocolHint::RING) {
435 // If it is a RingID and no valid account is available, present error
436 [self displayErrorModalWithTitle:noValidAccountTitle
437 WithMessage:noValidAccountMessage];
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400438 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400439 }
440 if (shouldCall) {
441 [self startCallFromURI:uri];
442 } else {
443 [self startConversationFromURI:uri];
444 }
445 } else {
446 [self displayErrorModalWithTitle:noValidAccountTitle
447 WithMessage:noValidAccountMessage];
448 }
449}
450
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400451- (void) clearSearchField
452{
453 [searchField setStringValue:@""];
454 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
455}
456
Alexandre Lision61db3552015-10-22 19:12:52 -0400457- (void) addToContact
458{
459 if ([treeController selectedNodes].count == 0)
460 return;
461
462 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
463 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
464 auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
465 if (contactmethod.isEmpty())
466 return;
467
468 if (addToContactPopover != nullptr) {
469 [addToContactPopover performClose:self];
470 addToContactPopover = NULL;
471 } else if (contactmethod.first()) {
472 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
473 [editorVC setMethodToLink:contactmethod.first()];
474 [editorVC setContactLinkedDelegate:self];
475 addToContactPopover = [[NSPopover alloc] init];
476 [addToContactPopover setContentSize:editorVC.view.frame.size];
477 [addToContactPopover setContentViewController:editorVC];
478 [addToContactPopover setAnimates:YES];
479 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
480 [addToContactPopover setDelegate:self];
481
482 [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
483 ofView:smartView preferredEdge:NSMaxXEdge];
484 }
485}
486
Alexandre Lision72a669e2016-09-14 17:52:16 -0400487/**
488 Copy a NSString in the general Pasteboard
489
490 @param sender the NSObject containing the represented object to copy
491 */
492- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400493{
494 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
495 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
496 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
497}
498
499- (void) callNumber:(id) sender
500{
501 Call* c = CallModel::instance().dialingCall();
502 c->setDialNumber(QString::fromNSString([sender representedObject]));
503 c << Call::Action::ACCEPT;
504}
505
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400506#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400507
508- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
509{
510 if (commandSelector == @selector(insertNewline:)) {
511 if([[searchField stringValue] isNotEqualTo:@""]) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400512 [self processSearchFieldInputAndStartCall:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400513 return YES;
514 }
515 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400516 return NO;
517}
518
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500519- (void)controlTextDidChange:(NSNotification *) notification
520{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500521 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
522}
523
Alexandre Lision61db3552015-10-22 19:12:52 -0400524#pragma mark - NSPopOverDelegate
525
526- (void)popoverDidClose:(NSNotification *)notification
527{
528 if (addToContactPopover != nullptr) {
529 [addToContactPopover performClose:self];
530 addToContactPopover = NULL;
531 }
532}
533
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400534
Alexandre Lision61db3552015-10-22 19:12:52 -0400535#pragma mark - ContactLinkedDelegate
536
537- (void)contactLinked
538{
539 if (addToContactPopover != nullptr) {
540 [addToContactPopover performClose:self];
541 addToContactPopover = NULL;
542 }
543}
544
545#pragma mark - KeyboardShortcutDelegate
546
547- (void) onAddShortcut
548{
549 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
550 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
551 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
552 if (contactmethods.isEmpty())
553 return;
554
555 auto contactmethod = contactmethods.first();
556 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
557 [self addToContact];
558 }
559}
560
561#pragma mark - ContextMenuDelegate
562
Alexandre Lision12946a72016-03-08 13:39:34 -0500563- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision61db3552015-10-22 19:12:52 -0400564{
Alexandre Lision12946a72016-03-08 13:39:34 -0500565 auto qIdx = [treeController toQIdx:item];
566
567 if (!qIdx.isValid()) {
568 return nil;
569 }
570
Alexandre Lision61db3552015-10-22 19:12:52 -0400571 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
572 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
573 if (contactmethods.isEmpty())
574 return nil;
575
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400576 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
577
578 if (contactmethods.size() == 1
579 && !contactmethods.first()->contact()
580 || contactmethods.first()->contact()->isPlaceHolder()) {
581
Alexandre Lision61db3552015-10-22 19:12:52 -0400582 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
583 action:@selector(addToContact)
584 keyEquivalent:@"a"
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400585 atIndex:theMenu.itemArray.count];
Alexandre Lision72a669e2016-09-14 17:52:16 -0400586 } else if (auto person = contactmethods.first()->contact()) {
587 NSMenuItem* copyNameItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy name", @"Contextual menu action")
588 action:@selector(copyStringToPasteboard:)
589 keyEquivalent:@""];
590
591 [copyNameItem setRepresentedObject:person->formattedName().toNSString()];
592 [theMenu addItem:copyNameItem];
Alexandre Lision61db3552015-10-22 19:12:52 -0400593 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400594
595 NSMenu* copySubmenu = [[NSMenu alloc] init];
596 NSMenu* callSubmenu = [[NSMenu alloc] init];
597
Alexandre Lision72a669e2016-09-14 17:52:16 -0400598 for (auto cm : contactmethods) {
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400599 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
Alexandre Lision72a669e2016-09-14 17:52:16 -0400600 action:@selector(copyStringToPasteboard:)
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400601 keyEquivalent:@""];
602
603 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
604 [copySubmenu addItem:tmpCopyItem];
605
606 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
607 action:@selector(callNumber:)
608 keyEquivalent:@""];
609 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
610 [callSubmenu addItem:tmpCallItem];
611 }
612
Alexandre Lision72a669e2016-09-14 17:52:16 -0400613 NSMenuItem* copyNumberItem = [[NSMenuItem alloc] init];
614 [copyNumberItem setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
615 [copyNumberItem setSubmenu:copySubmenu];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400616
617 NSMenuItem* callItems = [[NSMenuItem alloc] init];
618 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
619 [callItems setSubmenu:callSubmenu];
620
Alexandre Lision72a669e2016-09-14 17:52:16 -0400621 [theMenu insertItem:copyNumberItem atIndex:theMenu.itemArray.count];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400622 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
623 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
624
625 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400626}
627
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400628@end