blob: d0264589d3c3295ea4fbb7067cdae392ca8bb2e8 [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 Lisiond14bda32015-10-13 11:34:29 -040032#import <itemdataroles.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040033#import <person.h>
34#import <contactmethod.h>
35#import <globalinstances.h>
Alexandre Lision0f66bd32016-01-18 11:30:45 -050036#import <phonedirectorymodel.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040037
38#import "QNSTreeController.h"
39#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040040#import "views/HoverTableRowView.h"
Alexandre Lision61db3552015-10-22 19:12:52 -040041#import "PersonLinkerVC.h"
42#import "views/RingOutlineView.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040043#import "views/ContextualTableCellView.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040044
Alexandre Lision61db3552015-10-22 19:12:52 -040045@interface SmartViewVC () <NSOutlineViewDelegate, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> {
Alexandre Lision0f66bd32016-01-18 11:30:45 -050046
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040047 QNSTreeController *treeController;
Alexandre Lision61db3552015-10-22 19:12:52 -040048 NSPopover* addToContactPopover;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040049
50 //UI elements
Alexandre Lision61db3552015-10-22 19:12:52 -040051 __unsafe_unretained IBOutlet RingOutlineView* smartView;
52 __unsafe_unretained IBOutlet NSSearchField* searchField;
53 __unsafe_unretained IBOutlet NSButton* showContactsButton;
54 __unsafe_unretained IBOutlet NSButton* showHistoryButton;
55 __unsafe_unretained IBOutlet NSTabView* tabbar;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040056}
57
58@end
59
60@implementation SmartViewVC
61
62// Tags for views
63NSInteger const IMAGE_TAG = 100;
64NSInteger const DISPLAYNAME_TAG = 200;
65NSInteger const DETAILS_TAG = 300;
66NSInteger const CALL_BUTTON_TAG = 400;
67NSInteger const TXT_BUTTON_TAG = 500;
68
69- (void)awakeFromNib
70{
71 NSLog(@"INIT SmartView VC");
72
Alexandre Lision0f66bd32016-01-18 11:30:45 -050073 RecentModel::instance().peopleProxy()->setFilterRole((int)Ring::Role::Name);
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040074 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040075 [treeController setAvoidsEmptySelection:NO];
76 [treeController setChildrenKeyPath:@"children"];
77
78 [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
79 [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
80 [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
81 [smartView setTarget:self];
Alexandre Lision89edc6a2015-11-09 11:30:47 -050082 [smartView setAction:@selector(selectRow:)];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040083 [smartView setDoubleAction:@selector(placeCall:)];
84
Alexandre Lision61db3552015-10-22 19:12:52 -040085 [smartView setContextMenuDelegate:self];
86 [smartView setShortcutsDelegate:self];
87
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040088 QObject::connect(RecentModel::instance().peopleProxy(),
Alexandre Lisionee098462015-10-22 17:22:50 -040089 &QAbstractItemModel::dataChanged,
90 [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
91 for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
92 {
93 [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
94 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
95 }
96 });
97
Alexandre Lision89edc6a2015-11-09 11:30:47 -050098 QObject::connect(RecentModel::instance().selectionModel(),
99 &QItemSelectionModel::currentChanged,
100 [=](const QModelIndex &current, const QModelIndex &previous) {
101 if(!current.isValid())
102 return;
103
104 auto proxyIdx = RecentModel::instance().peopleProxy()->mapFromSource(current);
105 if (proxyIdx.isValid()) {
106 [treeController setSelectionQModelIndex:proxyIdx];
107
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500108 [showContactsButton setHighlighted:NO];
109 [showHistoryButton setHighlighted:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500110 [tabbar selectTabViewItemAtIndex:0];
111 [smartView scrollRowToVisible:proxyIdx.row()];
112 }
113 });
114
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400115 [self.view setWantsLayer:YES];
116 [self.view setLayer:[CALayer layer]];
117 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
118
119 [searchField setWantsLayer:YES];
120 [searchField setLayer:[CALayer layer]];
121 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
122}
123
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500124-(void) selectRow:(id)sender
125{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500126 if ([treeController selectedNodes].count == 0) {
127 RecentModel::instance().selectionModel()->clearCurrentIndex();
128 return;
129 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500130 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
131 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
132 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
133}
134
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400135- (void)placeCall:(id)sender
136{
137 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
138 ContactMethod* m = nil;
139
140 // Double click on an ongoing call
141 if (qIdx.parent().isValid()) {
142 return;
143 }
144
145 if([[treeController selectedNodes] count] > 0) {
146 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
147 m = qvariant_cast<ContactMethod*>(var);
148 if (!m) {
149 // test if it is a person
150 QVariant var = qIdx.data((int)Person::Role::Object);
151 if (var.isValid()) {
152 Person *c = var.value<Person*>();
153 if (c->phoneNumbers().size() > 0) {
154 m = c->phoneNumbers().first();
155 }
156 }
157 }
158 }
159
160 // Before calling check if we properly extracted a contact method and that
161 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400162 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500163 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400164 c->setPeerContactMethod(m);
165 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500166 CallModel::instance().selectCall(c);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400167 }
168}
169
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500170- (IBAction)showHistory:(NSButton*)sender
171{
172 [showContactsButton setHighlighted:NO];
173 [showHistoryButton setHighlighted:![sender isHighlighted]];
174
175 if (![sender isHighlighted]) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400176 [tabbar selectTabViewItemAtIndex:0];
177 } else {
178 [tabbar selectTabViewItemAtIndex:1];
179 }
180}
181
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500182- (IBAction)showContacts:(NSButton*)sender
183{
184 [showContactsButton setHighlighted:![sender isHighlighted]];
185 [showHistoryButton setHighlighted:NO];
186
187 if (![sender isHighlighted]) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400188 [tabbar selectTabViewItemAtIndex:0];
189 } else {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400190 [tabbar selectTabViewItemAtIndex:2];
191 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400192}
193
194#pragma mark - NSOutlineViewDelegate methods
195
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400196- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
197{
198 return YES;
199}
200
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400201- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
202{
203 return NO;
204}
205
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400206- (void)outlineViewSelectionDidChange:(NSNotification *)notification
207{
208 if ([treeController selectedNodes].count <= 0) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500209 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400210 return;
211 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400212}
213
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400214- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
215{
216 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
217 NSTableCellView *result;
218 if (!qIdx.parent().isValid()) {
219 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
220 NSTextField* details = [result viewWithTag:DETAILS_TAG];
221
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500222 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
223 [((ContextualTableCellView*) result) setContextualsControls:controls];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400224
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400225 if (auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400226 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400227 [((ContextualTableCellView*) result) setActiveState:YES];
228 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400229 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400230 [((ContextualTableCellView*) result) setActiveState:NO];
231 }
232
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500233 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
234 int unread = qIdx.data((int)Ring::Role::UnreadTextMessageCount).toInt();
235 [unreadCount setHidden:(unread == 0)];
236 [unreadCount.layer setCornerRadius:5.0f];
237 [unreadCount setStringValue:qIdx.data((int)Ring::Role::UnreadTextMessageCount).toString().toNSString()];
238
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400239 } else {
240 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
241 NSTextField* details = [result viewWithTag:DETAILS_TAG];
242
243 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
244 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400245
246 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500247 [displayName setStringValue:qIdx.data((int)Ring::Role::Name).toString().toNSString()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400248 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
249 Person* p = qvariant_cast<Person*>(qIdx.data((int)Person::Role::Object));
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500250 QVariant photo = GlobalInstances::pixmapManipulator().contactPhoto(p, QSize(50,50));
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400251 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
252 return result;
253}
254
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500255- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
256{
257 [outlineView scrollRowToVisible:0];
258}
259
260- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
261{
262 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
263}
264
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400265- (IBAction)callClickedAtRow:(id)sender {
266 NSInteger row = [smartView rowForView:sender];
267 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
268 [self placeCall:nil];
269}
270
271- (IBAction)hangUpClickedAtRow:(id)sender {
272 NSInteger row = [smartView rowForView:sender];
273 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400274 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400275}
276
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400277- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
278{
279 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
280 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 45.0;
281}
282
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500283- (void) startConversationFromSearchField
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400284{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500285 auto cm = PhoneDirectoryModel::instance().getNumber(QString::fromNSString([searchField stringValue]));
286 time_t currentTime;
287 ::time(&currentTime);
288 cm->setLastUsed(currentTime);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400289}
290
Alexandre Lision61db3552015-10-22 19:12:52 -0400291- (void) addToContact
292{
293 if ([treeController selectedNodes].count == 0)
294 return;
295
296 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
297 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
298 auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
299 if (contactmethod.isEmpty())
300 return;
301
302 if (addToContactPopover != nullptr) {
303 [addToContactPopover performClose:self];
304 addToContactPopover = NULL;
305 } else if (contactmethod.first()) {
306 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
307 [editorVC setMethodToLink:contactmethod.first()];
308 [editorVC setContactLinkedDelegate:self];
309 addToContactPopover = [[NSPopover alloc] init];
310 [addToContactPopover setContentSize:editorVC.view.frame.size];
311 [addToContactPopover setContentViewController:editorVC];
312 [addToContactPopover setAnimates:YES];
313 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
314 [addToContactPopover setDelegate:self];
315
316 [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
317 ofView:smartView preferredEdge:NSMaxXEdge];
318 }
319}
320
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400321- (void) copyNumberToPasteboard:(id) sender
322{
323 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
324 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
325 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
326}
327
328- (void) callNumber:(id) sender
329{
330 Call* c = CallModel::instance().dialingCall();
331 c->setDialNumber(QString::fromNSString([sender representedObject]));
332 c << Call::Action::ACCEPT;
333}
334
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400335#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400336
337- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
338{
339 if (commandSelector == @selector(insertNewline:)) {
340 if([[searchField stringValue] isNotEqualTo:@""]) {
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500341 [self startConversationFromSearchField];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400342 return YES;
343 }
344 }
345
346 return NO;
347}
348
Alexandre Lision61db3552015-10-22 19:12:52 -0400349#pragma mark - NSPopOverDelegate
350
351- (void)popoverDidClose:(NSNotification *)notification
352{
353 if (addToContactPopover != nullptr) {
354 [addToContactPopover performClose:self];
355 addToContactPopover = NULL;
356 }
357}
358
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400359- (void)controlTextDidChange:(NSNotification *) notification
360{
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500361 RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400362}
363
Alexandre Lision61db3552015-10-22 19:12:52 -0400364#pragma mark - ContactLinkedDelegate
365
366- (void)contactLinked
367{
368 if (addToContactPopover != nullptr) {
369 [addToContactPopover performClose:self];
370 addToContactPopover = NULL;
371 }
372}
373
374#pragma mark - KeyboardShortcutDelegate
375
376- (void) onAddShortcut
377{
378 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
379 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
380 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
381 if (contactmethods.isEmpty())
382 return;
383
384 auto contactmethod = contactmethods.first();
385 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
386 [self addToContact];
387 }
388}
389
390#pragma mark - ContextMenuDelegate
391
392- (NSMenu*) contextualMenuForIndex:(NSIndexPath*) path
393{
394 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
395 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
396 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
397 if (contactmethods.isEmpty())
398 return nil;
399
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400400 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
401
402 if (contactmethods.size() == 1
403 && !contactmethods.first()->contact()
404 || contactmethods.first()->contact()->isPlaceHolder()) {
405
Alexandre Lision61db3552015-10-22 19:12:52 -0400406 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
407 action:@selector(addToContact)
408 keyEquivalent:@"a"
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400409 atIndex:theMenu.itemArray.count];
Alexandre Lision61db3552015-10-22 19:12:52 -0400410 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400411
412 NSMenu* copySubmenu = [[NSMenu alloc] init];
413 NSMenu* callSubmenu = [[NSMenu alloc] init];
414
415 for(auto cm : contactmethods) {
416 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
417 action:@selector(copyNumberToPasteboard:)
418 keyEquivalent:@""];
419
420 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
421 [copySubmenu addItem:tmpCopyItem];
422
423 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
424 action:@selector(callNumber:)
425 keyEquivalent:@""];
426 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
427 [callSubmenu addItem:tmpCallItem];
428 }
429
430 NSMenuItem* copyItems = [[NSMenuItem alloc] init];
431 [copyItems setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
432 [copyItems setSubmenu:copySubmenu];
433
434 NSMenuItem* callItems = [[NSMenuItem alloc] init];
435 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
436 [callItems setSubmenu:callSubmenu];
437
438 [theMenu insertItem:copyItems atIndex:theMenu.itemArray.count];
439 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
440 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
441
442 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400443}
444
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400445@end