blob: 948346a02ed8fa33c184798e48f7019decc80fc6 [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 Lision45c18672016-11-02 15:24:45 -040059
60 /* Pending ring usernames lookup for the search entry */
61 QMetaObject::Connection usernameLookupConnection;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040062}
63
64@end
65
66@implementation SmartViewVC
67
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -040068@synthesize tabbar;
69
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040070// Tags for views
Alexandre Lision57914fa2016-02-10 14:52:24 -050071NSInteger const IMAGE_TAG = 100;
72NSInteger const DISPLAYNAME_TAG = 200;
73NSInteger const DETAILS_TAG = 300;
74NSInteger const CALL_BUTTON_TAG = 400;
75NSInteger const TXT_BUTTON_TAG = 500;
76NSInteger const CANCEL_BUTTON_TAG = 600;
Kateryna Kostiuk6a536362017-05-02 14:26:12 -040077NSInteger const RING_ID_LABEL = 700;
Kateryna Kostiukd9039e92017-05-24 14:29:54 -040078NSInteger const PRESENCE_TAG = 800;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040079
80- (void)awakeFromNib
81{
82 NSLog(@"INIT SmartView VC");
83
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040084 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040085 [treeController setAvoidsEmptySelection:NO];
86 [treeController setChildrenKeyPath:@"children"];
87
88 [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
89 [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
90 [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
91 [smartView setTarget:self];
Alexandre Lision89edc6a2015-11-09 11:30:47 -050092 [smartView setAction:@selector(selectRow:)];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040093 [smartView setDoubleAction:@selector(placeCall:)];
94
Alexandre Lision61db3552015-10-22 19:12:52 -040095 [smartView setContextMenuDelegate:self];
96 [smartView setShortcutsDelegate:self];
97
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040098 QObject::connect(RecentModel::instance().peopleProxy(),
Alexandre Lisionee098462015-10-22 17:22:50 -040099 &QAbstractItemModel::dataChanged,
100 [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
101 for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
102 {
103 [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
104 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
105 }
106 });
107
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500108 QObject::connect(RecentModel::instance().selectionModel(),
109 &QItemSelectionModel::currentChanged,
110 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500111 if(!current.isValid()) {
112 [smartView deselectAll:nil];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500113 return;
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500114 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500115
116 auto proxyIdx = RecentModel::instance().peopleProxy()->mapFromSource(current);
117 if (proxyIdx.isValid()) {
118 [treeController setSelectionQModelIndex:proxyIdx];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500119 [tabbar selectTabViewItemAtIndex:0];
120 [smartView scrollRowToVisible:proxyIdx.row()];
121 }
122 });
123
Alexandre Lision25fc82a2016-05-08 17:30:40 -0400124 QObject::connect(RecentModel::instance().peopleProxy(),
125 &QAbstractItemModel::rowsInserted,
126 [=](const QModelIndex &parent, int first, int last) {
127 Q_UNUSED(parent)
128 Q_UNUSED(first)
129 Q_UNUSED(last)
130 [smartView scrollRowToVisible:0];
131 });
132
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -0400133 QObject::connect(AvailableAccountModel::instance().selectionModel(),
134 &QItemSelectionModel::currentChanged,
135 [self](const QModelIndex& idx){
136 [self clearSearchField];
137 });
138
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400139 [self.view setWantsLayer:YES];
140 [self.view setLayer:[CALayer layer]];
141 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
142
143 [searchField setWantsLayer:YES];
144 [searchField setLayer:[CALayer layer]];
145 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
146}
147
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500148-(void) selectRow:(id)sender
149{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500150 if ([treeController selectedNodes].count == 0) {
151 RecentModel::instance().selectionModel()->clearCurrentIndex();
152 return;
153 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500154 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
155 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
156 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
157}
158
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400159- (void)placeCall:(id)sender
160{
161 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
162 ContactMethod* m = nil;
163
164 // Double click on an ongoing call
165 if (qIdx.parent().isValid()) {
166 return;
167 }
168
169 if([[treeController selectedNodes] count] > 0) {
170 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
171 m = qvariant_cast<ContactMethod*>(var);
172 if (!m) {
173 // test if it is a person
174 QVariant var = qIdx.data((int)Person::Role::Object);
175 if (var.isValid()) {
176 Person *c = var.value<Person*>();
177 if (c->phoneNumbers().size() > 0) {
178 m = c->phoneNumbers().first();
179 }
180 }
181 }
182 }
183
184 // Before calling check if we properly extracted a contact method and that
185 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400186 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500187 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400188 c->setPeerContactMethod(m);
189 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500190 CallModel::instance().selectCall(c);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400191 }
192}
193
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400194- (void)showHistory
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500195{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500196 [tabbar selectTabViewItemAtIndex:1];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400197}
198
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400199- (void)showContacts
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500200{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500201 [tabbar selectTabViewItemAtIndex:2];
202}
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500203
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400204- (void)showSmartlist
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500205{
206 [tabbar selectTabViewItemAtIndex:0];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400207}
208
209#pragma mark - NSOutlineViewDelegate methods
210
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400211- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
212{
213 return YES;
214}
215
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400216- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
217{
218 return NO;
219}
220
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400221- (void)outlineViewSelectionDidChange:(NSNotification *)notification
222{
223 if ([treeController selectedNodes].count <= 0) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500224 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400225 return;
226 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400227}
228
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400229- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
230{
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400231 QModelIndex proxyIdx = [treeController toQIdx:((NSTreeNode*)item)];
232 QModelIndex qIdx = RecentModel::instance().peopleProxy()->mapToSource(proxyIdx);
233
234 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400235 if (!qIdx.parent().isValid()) {
236 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
237 NSTextField* details = [result viewWithTag:DETAILS_TAG];
238
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500239 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
240 [((ContextualTableCellView*) result) setContextualsControls:controls];
Kateryna Kostiuk3c960bf2017-06-01 12:11:54 -0400241 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400242
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400243 if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400244 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400245 [((ContextualTableCellView*) result) setActiveState:YES];
246 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400247 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400248 [((ContextualTableCellView*) result) setActiveState:NO];
249 }
250
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500251 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
252 int unread = qIdx.data((int)Ring::Role::UnreadTextMessageCount).toInt();
253 [unreadCount setHidden:(unread == 0)];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500254 [unreadCount setStringValue:qIdx.data((int)Ring::Role::UnreadTextMessageCount).toString().toNSString()];
255
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400256 } else {
257 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500258 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CANCEL_BUTTON_TAG]];
259 [((ContextualTableCellView*) result) setContextualsControls:controls];
Kateryna Kostiuk3c960bf2017-06-01 12:11:54 -0400260 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500261 [((ContextualTableCellView*) result) setActiveState:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400262 NSTextField* details = [result viewWithTag:DETAILS_TAG];
263
264 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
265 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400266
267 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400268 NSString* displayNameString = qIdx.data((int)Ring::Role::Name).toString().toNSString();
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400269 NSString* displayIDString = qIdx.data((int)Person::Role::IdOfLastCMUsed).toString().toNSString();
270 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
271 [displayName setStringValue:displayIDString];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400272 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400273 else {
274 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
275 [displayName setStringValue:displayNameString];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400276 [displayRingID setStringValue:displayIDString];
277 }
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)))];
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400281
282 NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
283 if (qIdx.data(static_cast<int>(Ring::Role::IsPresent)).value<bool>()) {
284 [presenceView setHidden:NO];
285 } else {
286 [presenceView setHidden:YES];
287 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400288 return result;
289}
290
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500291- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
292{
293 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
294}
295
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -0400296- (void)startCallForRow:(id)sender {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400297 NSInteger row = [smartView rowForView:sender];
298 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
299 [self placeCall:nil];
300}
301
302- (IBAction)hangUpClickedAtRow:(id)sender {
303 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500304 id callNode = [smartView itemAtRow:row];
305 auto callIdx = [treeController toQIdx:((NSTreeNode*)callNode)];
306
307 if (callIdx.isValid()) {
308 auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(callIdx));
309 call << Call::Action::REFUSE;
310 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400311}
312
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400313- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
314{
315 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500316 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 50.0;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400317}
318
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500319- (IBAction)placeCallFromSearchField:(id)sender
320{
321 if ([searchField stringValue].length == 0) {
322 return;
323 }
Alexandre Lision45c18672016-11-02 15:24:45 -0400324 [self processSearchFieldInputAndStartCall:YES];
325}
326
327- (void) startCallFromURI:(const URI&) uri
328{
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400329 auto cm = PhoneDirectoryModel::instance().getNumber(uri, [self chosenAccount]);
330 if(!cm->account() && [self chosenAccount]) {
331 cm->setAccount([self chosenAccount]);
332 }
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500333 auto c = CallModel::instance().dialingCall();
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500334 c->setPeerContactMethod(cm);
335 c << Call::Action::ACCEPT;
336 CallModel::instance().selectCall(c);
337}
338
Alexandre Lision45c18672016-11-02 15:24:45 -0400339- (void) startConversationFromURI:(const URI&) uri
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400340{
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400341 auto cm = PhoneDirectoryModel::instance().getNumber(uri, [self chosenAccount]);
342 if(!cm->account() && [self chosenAccount]) {
343 cm->setAccount([self chosenAccount]);
344 }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500345 time_t currentTime;
346 ::time(&currentTime);
347 cm->setLastUsed(currentTime);
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500348 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(RecentModel::instance().peopleProxy()->index(0, 0));
349 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400350}
351
Alexandre Lision45c18672016-11-02 15:24:45 -0400352- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
353{
354 NSAlert* alert = [NSAlert alertWithMessageText:title
355 defaultButton:@"Ok"
356 alternateButton:nil
357 otherButton:nil
358 informativeTextWithFormat:message];
359
360 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
361}
362
363- (void) processSearchFieldInputAndStartCall:(BOOL) shouldCall
364{
Alexandre Lision45c18672016-11-02 15:24:45 -0400365 NSString* noValidAccountTitle = NSLocalizedString(@"No valid account available",
366 @"Alert dialog title");
367 NSString* noValidAccountMessage = NSLocalizedString(@"Make sure you have at least one valid account",
368 @"Alert dialo message");
369
Alexandre Lision45c18672016-11-02 15:24:45 -0400370 const auto* numberEntered = [searchField stringValue];
371 URI uri = URI(numberEntered.UTF8String);
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400372 [self clearSearchField];
Alexandre Lision45c18672016-11-02 15:24:45 -0400373
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400374 if ([self chosenAccount] && [self chosenAccount]->protocol() == Account::Protocol::RING) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400375 if (uri.protocolHint() == URI::ProtocolHint::RING) {
376 // If it is a RingID start the conversation or the call
377 if (shouldCall) {
378 [self startCallFromURI:uri];
379 } else {
380 [self startConversationFromURI:uri];
381 }
382 } else {
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400383 // If it's not a ringID and the user choosen account is a Ring account do a search on the blockchain
Alexandre Lision45c18672016-11-02 15:24:45 -0400384 QString usernameToLookup = uri.userinfo();
385 QObject::disconnect(usernameLookupConnection);
386 usernameLookupConnection = QObject::connect(&NameDirectory::instance(),
387 &NameDirectory::registeredNameFound,
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400388 [self,usernameToLookup,shouldCall] (const Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400389 if (usernameToLookup.compare(name) != 0) {
390 //That is not our lookup.
391 return;
392 }
Alexandre Lision45c18672016-11-02 15:24:45 -0400393 switch(status) {
394 case NameDirectory::LookupStatus::SUCCESS: {
395 URI uri = URI("ring:" + address);
396 if (shouldCall) {
397 [self startCallFromURI:uri];
398 } else {
399 [self startConversationFromURI:uri];
400 }
401 break;
402 }
403 case NameDirectory::LookupStatus::INVALID_NAME:
404 case NameDirectory::LookupStatus::ERROR:
405 case NameDirectory::LookupStatus::NOT_FOUND: {
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400406 [self displayErrorModalWithTitle:NSLocalizedString(@"Entered name not found",
407 @"Alert dialog title")
408 WithMessage:NSLocalizedString(@"The username you entered do not match a RingID on the network",
409 @"Alert dialog title")];
Alexandre Lision45c18672016-11-02 15:24:45 -0400410 }
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400411 break;
Alexandre Lision45c18672016-11-02 15:24:45 -0400412 }
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400413 });
414
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400415 NameDirectory::instance().lookupName([self chosenAccount], QString(), usernameToLookup);
Alexandre Lision45c18672016-11-02 15:24:45 -0400416 }
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400417 } else if ([self chosenAccount] && [self chosenAccount]->protocol() == Account::Protocol::SIP) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400418 if (uri.protocolHint() == URI::ProtocolHint::RING) {
419 // If it is a RingID and no valid account is available, present error
420 [self displayErrorModalWithTitle:noValidAccountTitle
421 WithMessage:noValidAccountMessage];
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400422 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400423 }
424 if (shouldCall) {
425 [self startCallFromURI:uri];
426 } else {
427 [self startConversationFromURI:uri];
428 }
429 } else {
430 [self displayErrorModalWithTitle:noValidAccountTitle
431 WithMessage:noValidAccountMessage];
432 }
433}
434
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400435-(Account* ) chosenAccount
436{
437 auto idx = AvailableAccountModel::instance().selectionModel()->currentIndex();
438 if (idx.isValid()) {
439 return idx.data(static_cast<int>(Ring::Role::Object)).value<Account*>();
440 }
441 return nullptr;
442}
443
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400444- (void) clearSearchField
445{
446 [searchField setStringValue:@""];
447 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
448}
449
Alexandre Lision61db3552015-10-22 19:12:52 -0400450- (void) addToContact
451{
452 if ([treeController selectedNodes].count == 0)
453 return;
454
455 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
456 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
457 auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
458 if (contactmethod.isEmpty())
459 return;
460
461 if (addToContactPopover != nullptr) {
462 [addToContactPopover performClose:self];
463 addToContactPopover = NULL;
464 } else if (contactmethod.first()) {
465 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
466 [editorVC setMethodToLink:contactmethod.first()];
467 [editorVC setContactLinkedDelegate:self];
468 addToContactPopover = [[NSPopover alloc] init];
469 [addToContactPopover setContentSize:editorVC.view.frame.size];
470 [addToContactPopover setContentViewController:editorVC];
471 [addToContactPopover setAnimates:YES];
472 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
473 [addToContactPopover setDelegate:self];
474
475 [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
476 ofView:smartView preferredEdge:NSMaxXEdge];
477 }
478}
479
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400480- (void) addContactForRow:(id) sender
481{
482 NSInteger row = [smartView rowForItem:[sender representedObject]];
483 if(row < 0) {
484 return;
485 }
486 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
487 [self addToContact];
488}
489
Alexandre Lision72a669e2016-09-14 17:52:16 -0400490/**
491 Copy a NSString in the general Pasteboard
492
493 @param sender the NSObject containing the represented object to copy
494 */
495- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400496{
497 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
498 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
499 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
500}
501
502- (void) callNumber:(id) sender
503{
504 Call* c = CallModel::instance().dialingCall();
505 c->setDialNumber(QString::fromNSString([sender representedObject]));
506 c << Call::Action::ACCEPT;
507}
508
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400509#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400510
511- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
512{
513 if (commandSelector == @selector(insertNewline:)) {
514 if([[searchField stringValue] isNotEqualTo:@""]) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400515 [self processSearchFieldInputAndStartCall:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400516 return YES;
517 }
518 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400519 return NO;
520}
521
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500522- (void)controlTextDidChange:(NSNotification *) notification
523{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500524 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
525}
526
Alexandre Lision61db3552015-10-22 19:12:52 -0400527#pragma mark - NSPopOverDelegate
528
529- (void)popoverDidClose:(NSNotification *)notification
530{
531 if (addToContactPopover != nullptr) {
532 [addToContactPopover performClose:self];
533 addToContactPopover = NULL;
534 }
535}
536
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400537
Alexandre Lision61db3552015-10-22 19:12:52 -0400538#pragma mark - ContactLinkedDelegate
539
540- (void)contactLinked
541{
542 if (addToContactPopover != nullptr) {
543 [addToContactPopover performClose:self];
544 addToContactPopover = NULL;
545 }
546}
547
548#pragma mark - KeyboardShortcutDelegate
549
550- (void) onAddShortcut
551{
552 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
553 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
554 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
555 if (contactmethods.isEmpty())
556 return;
557
558 auto contactmethod = contactmethods.first();
559 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
560 [self addToContact];
561 }
562}
563
564#pragma mark - ContextMenuDelegate
565
Alexandre Lision12946a72016-03-08 13:39:34 -0500566- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision61db3552015-10-22 19:12:52 -0400567{
Alexandre Lision12946a72016-03-08 13:39:34 -0500568 auto qIdx = [treeController toQIdx:item];
569
570 if (!qIdx.isValid()) {
571 return nil;
572 }
573
Alexandre Lision61db3552015-10-22 19:12:52 -0400574 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
575 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
576 if (contactmethods.isEmpty())
577 return nil;
578
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400579 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
580
581 if (contactmethods.size() == 1
582 && !contactmethods.first()->contact()
583 || contactmethods.first()->contact()->isPlaceHolder()) {
584
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400585 NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
586 action:@selector(addContactForRow:)
587 keyEquivalent:@""];
588 [addContactItem setRepresentedObject:item];
589 [theMenu addItem:addContactItem];
Alexandre Lision72a669e2016-09-14 17:52:16 -0400590 } else if (auto person = contactmethods.first()->contact()) {
591 NSMenuItem* copyNameItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy name", @"Contextual menu action")
592 action:@selector(copyStringToPasteboard:)
593 keyEquivalent:@""];
594
595 [copyNameItem setRepresentedObject:person->formattedName().toNSString()];
596 [theMenu addItem:copyNameItem];
Alexandre Lision61db3552015-10-22 19:12:52 -0400597 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400598
599 NSMenu* copySubmenu = [[NSMenu alloc] init];
600 NSMenu* callSubmenu = [[NSMenu alloc] init];
601
Alexandre Lision72a669e2016-09-14 17:52:16 -0400602 for (auto cm : contactmethods) {
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400603 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
Alexandre Lision72a669e2016-09-14 17:52:16 -0400604 action:@selector(copyStringToPasteboard:)
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400605 keyEquivalent:@""];
606
607 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
608 [copySubmenu addItem:tmpCopyItem];
609
610 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
611 action:@selector(callNumber:)
612 keyEquivalent:@""];
613 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
614 [callSubmenu addItem:tmpCallItem];
615 }
616
Alexandre Lision72a669e2016-09-14 17:52:16 -0400617 NSMenuItem* copyNumberItem = [[NSMenuItem alloc] init];
618 [copyNumberItem setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
619 [copyNumberItem setSubmenu:copySubmenu];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400620
621 NSMenuItem* callItems = [[NSMenuItem alloc] init];
622 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
623 [callItems setSubmenu:callSubmenu];
624
Alexandre Lision72a669e2016-09-14 17:52:16 -0400625 [theMenu insertItem:copyNumberItem atIndex:theMenu.itemArray.count];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400626 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
627 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
628
629 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400630}
631
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400632@end