blob: 9c56b9e3b238e29f07614033c36f84acb7b9d6fd [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;
Kateryna Kostiuk6a536362017-05-02 14:26:12 -040076NSInteger const RING_ID_LABEL = 700;
Kateryna Kostiukd9039e92017-05-24 14:29:54 -040077NSInteger const PRESENCE_TAG = 800;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040078
79- (void)awakeFromNib
80{
81 NSLog(@"INIT SmartView VC");
82
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040083 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040084 [treeController setAvoidsEmptySelection:NO];
85 [treeController setChildrenKeyPath:@"children"];
86
87 [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
88 [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
89 [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
90 [smartView setTarget:self];
Alexandre Lision89edc6a2015-11-09 11:30:47 -050091 [smartView setAction:@selector(selectRow:)];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040092 [smartView setDoubleAction:@selector(placeCall:)];
93
Alexandre Lision61db3552015-10-22 19:12:52 -040094 [smartView setContextMenuDelegate:self];
95 [smartView setShortcutsDelegate:self];
96
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040097 QObject::connect(RecentModel::instance().peopleProxy(),
Alexandre Lisionee098462015-10-22 17:22:50 -040098 &QAbstractItemModel::dataChanged,
99 [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
100 for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
101 {
102 [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
103 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
104 }
105 });
106
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500107 QObject::connect(RecentModel::instance().selectionModel(),
108 &QItemSelectionModel::currentChanged,
109 [=](const QModelIndex &current, const QModelIndex &previous) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500110 if(!current.isValid()) {
111 [smartView deselectAll:nil];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500112 return;
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500113 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500114
115 auto proxyIdx = RecentModel::instance().peopleProxy()->mapFromSource(current);
116 if (proxyIdx.isValid()) {
117 [treeController setSelectionQModelIndex:proxyIdx];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500118 [tabbar selectTabViewItemAtIndex:0];
119 [smartView scrollRowToVisible:proxyIdx.row()];
120 }
121 });
122
Alexandre Lision25fc82a2016-05-08 17:30:40 -0400123 QObject::connect(RecentModel::instance().peopleProxy(),
124 &QAbstractItemModel::rowsInserted,
125 [=](const QModelIndex &parent, int first, int last) {
126 Q_UNUSED(parent)
127 Q_UNUSED(first)
128 Q_UNUSED(last)
129 [smartView scrollRowToVisible:0];
130 });
131
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -0400132 QObject::connect(AvailableAccountModel::instance().selectionModel(),
133 &QItemSelectionModel::currentChanged,
134 [self](const QModelIndex& idx){
135 [self clearSearchField];
136 });
137
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400138 [self.view setWantsLayer:YES];
139 [self.view setLayer:[CALayer layer]];
140 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
141
142 [searchField setWantsLayer:YES];
143 [searchField setLayer:[CALayer layer]];
144 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
145}
146
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500147-(void) selectRow:(id)sender
148{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500149 if ([treeController selectedNodes].count == 0) {
150 RecentModel::instance().selectionModel()->clearCurrentIndex();
151 return;
152 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500153 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
154 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
155 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
156}
157
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400158- (void)placeCall:(id)sender
159{
160 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
161 ContactMethod* m = nil;
162
163 // Double click on an ongoing call
164 if (qIdx.parent().isValid()) {
165 return;
166 }
167
168 if([[treeController selectedNodes] count] > 0) {
169 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
170 m = qvariant_cast<ContactMethod*>(var);
171 if (!m) {
172 // test if it is a person
173 QVariant var = qIdx.data((int)Person::Role::Object);
174 if (var.isValid()) {
175 Person *c = var.value<Person*>();
176 if (c->phoneNumbers().size() > 0) {
177 m = c->phoneNumbers().first();
178 }
179 }
180 }
181 }
182
183 // Before calling check if we properly extracted a contact method and that
184 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400185 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500186 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400187 c->setPeerContactMethod(m);
188 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500189 CallModel::instance().selectCall(c);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400190 }
191}
192
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400193- (void)showHistory
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500194{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500195 [tabbar selectTabViewItemAtIndex:1];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400196}
197
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400198- (void)showContacts
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500199{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500200 [tabbar selectTabViewItemAtIndex:2];
201}
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500202
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400203- (void)showSmartlist
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500204{
205 [tabbar selectTabViewItemAtIndex:0];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400206}
207
208#pragma mark - NSOutlineViewDelegate methods
209
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400210- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
211{
212 return YES;
213}
214
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400215- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
216{
217 return NO;
218}
219
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400220- (void)outlineViewSelectionDidChange:(NSNotification *)notification
221{
222 if ([treeController selectedNodes].count <= 0) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500223 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400224 return;
225 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400226}
227
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400228- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
229{
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400230 QModelIndex proxyIdx = [treeController toQIdx:((NSTreeNode*)item)];
231 QModelIndex qIdx = RecentModel::instance().peopleProxy()->mapToSource(proxyIdx);
232
233 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400234 if (!qIdx.parent().isValid()) {
235 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
236 NSTextField* details = [result viewWithTag:DETAILS_TAG];
237
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500238 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
239 [((ContextualTableCellView*) result) setContextualsControls:controls];
Kateryna Kostiuk3c960bf2017-06-01 12:11:54 -0400240 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400241
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400242 if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400243 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400244 [((ContextualTableCellView*) result) setActiveState:YES];
245 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400246 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400247 [((ContextualTableCellView*) result) setActiveState:NO];
248 }
249
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500250 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
251 int unread = qIdx.data((int)Ring::Role::UnreadTextMessageCount).toInt();
252 [unreadCount setHidden:(unread == 0)];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500253 [unreadCount setStringValue:qIdx.data((int)Ring::Role::UnreadTextMessageCount).toString().toNSString()];
254
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400255 } else {
256 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500257 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CANCEL_BUTTON_TAG]];
258 [((ContextualTableCellView*) result) setContextualsControls:controls];
Kateryna Kostiuk3c960bf2017-06-01 12:11:54 -0400259 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500260 [((ContextualTableCellView*) result) setActiveState:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400261 NSTextField* details = [result viewWithTag:DETAILS_TAG];
262
263 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
264 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400265
266 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400267 NSString* displayNameString = qIdx.data((int)Ring::Role::Name).toString().toNSString();
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400268 NSString* displayIDString = qIdx.data((int)Person::Role::IdOfLastCMUsed).toString().toNSString();
269 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
270 [displayName setStringValue:displayIDString];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400271 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400272 else {
273 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
274 [displayName setStringValue:displayNameString];
275 displayIDString = [NSString stringWithFormat:@"%@%@%@",@"(", displayIDString, @")"];
276 [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
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400296- (IBAction)callClickedAtRow:(id)sender {
297 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
Alexandre Lision72a669e2016-09-14 17:52:16 -0400480/**
481 Copy a NSString in the general Pasteboard
482
483 @param sender the NSObject containing the represented object to copy
484 */
485- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400486{
487 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
488 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
489 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
490}
491
492- (void) callNumber:(id) sender
493{
494 Call* c = CallModel::instance().dialingCall();
495 c->setDialNumber(QString::fromNSString([sender representedObject]));
496 c << Call::Action::ACCEPT;
497}
498
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400499#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400500
501- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
502{
503 if (commandSelector == @selector(insertNewline:)) {
504 if([[searchField stringValue] isNotEqualTo:@""]) {
Alexandre Lision45c18672016-11-02 15:24:45 -0400505 [self processSearchFieldInputAndStartCall:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400506 return YES;
507 }
508 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400509 return NO;
510}
511
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500512- (void)controlTextDidChange:(NSNotification *) notification
513{
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500514 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
515}
516
Alexandre Lision61db3552015-10-22 19:12:52 -0400517#pragma mark - NSPopOverDelegate
518
519- (void)popoverDidClose:(NSNotification *)notification
520{
521 if (addToContactPopover != nullptr) {
522 [addToContactPopover performClose:self];
523 addToContactPopover = NULL;
524 }
525}
526
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400527
Alexandre Lision61db3552015-10-22 19:12:52 -0400528#pragma mark - ContactLinkedDelegate
529
530- (void)contactLinked
531{
532 if (addToContactPopover != nullptr) {
533 [addToContactPopover performClose:self];
534 addToContactPopover = NULL;
535 }
536}
537
538#pragma mark - KeyboardShortcutDelegate
539
540- (void) onAddShortcut
541{
542 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
543 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
544 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
545 if (contactmethods.isEmpty())
546 return;
547
548 auto contactmethod = contactmethods.first();
549 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
550 [self addToContact];
551 }
552}
553
554#pragma mark - ContextMenuDelegate
555
Alexandre Lision12946a72016-03-08 13:39:34 -0500556- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision61db3552015-10-22 19:12:52 -0400557{
Alexandre Lision12946a72016-03-08 13:39:34 -0500558 auto qIdx = [treeController toQIdx:item];
559
560 if (!qIdx.isValid()) {
561 return nil;
562 }
563
Alexandre Lision61db3552015-10-22 19:12:52 -0400564 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
565 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
566 if (contactmethods.isEmpty())
567 return nil;
568
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400569 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
570
571 if (contactmethods.size() == 1
572 && !contactmethods.first()->contact()
573 || contactmethods.first()->contact()->isPlaceHolder()) {
574
Alexandre Lision61db3552015-10-22 19:12:52 -0400575 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
576 action:@selector(addToContact)
577 keyEquivalent:@"a"
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400578 atIndex:theMenu.itemArray.count];
Alexandre Lision72a669e2016-09-14 17:52:16 -0400579 } else if (auto person = contactmethods.first()->contact()) {
580 NSMenuItem* copyNameItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy name", @"Contextual menu action")
581 action:@selector(copyStringToPasteboard:)
582 keyEquivalent:@""];
583
584 [copyNameItem setRepresentedObject:person->formattedName().toNSString()];
585 [theMenu addItem:copyNameItem];
Alexandre Lision61db3552015-10-22 19:12:52 -0400586 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400587
588 NSMenu* copySubmenu = [[NSMenu alloc] init];
589 NSMenu* callSubmenu = [[NSMenu alloc] init];
590
Alexandre Lision72a669e2016-09-14 17:52:16 -0400591 for (auto cm : contactmethods) {
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400592 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
Alexandre Lision72a669e2016-09-14 17:52:16 -0400593 action:@selector(copyStringToPasteboard:)
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400594 keyEquivalent:@""];
595
596 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
597 [copySubmenu addItem:tmpCopyItem];
598
599 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
600 action:@selector(callNumber:)
601 keyEquivalent:@""];
602 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
603 [callSubmenu addItem:tmpCallItem];
604 }
605
Alexandre Lision72a669e2016-09-14 17:52:16 -0400606 NSMenuItem* copyNumberItem = [[NSMenuItem alloc] init];
607 [copyNumberItem setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
608 [copyNumberItem setSubmenu:copySubmenu];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400609
610 NSMenuItem* callItems = [[NSMenuItem alloc] init];
611 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
612 [callItems setSubmenu:callSubmenu];
613
Alexandre Lision72a669e2016-09-14 17:52:16 -0400614 [theMenu insertItem:copyNumberItem atIndex:theMenu.itemArray.count];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400615 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
616 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
617
618 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400619}
620
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400621@end