blob: b2be8bbe1d27167c0cd7b10c295c3512424a2221 [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>
36
37#import "QNSTreeController.h"
38#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040039#import "views/HoverTableRowView.h"
Alexandre Lision61db3552015-10-22 19:12:52 -040040#import "PersonLinkerVC.h"
41#import "views/RingOutlineView.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040042#import "views/ContextualTableCellView.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040043
Alexandre Lision61db3552015-10-22 19:12:52 -040044@interface SmartViewVC () <NSOutlineViewDelegate, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040045 BOOL isShowingContacts;
46 QNSTreeController *treeController;
Alexandre Lision61db3552015-10-22 19:12:52 -040047 NSPopover* addToContactPopover;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040048
49 //UI elements
Alexandre Lision61db3552015-10-22 19:12:52 -040050 __unsafe_unretained IBOutlet RingOutlineView* smartView;
51 __unsafe_unretained IBOutlet NSSearchField* searchField;
52 __unsafe_unretained IBOutlet NSButton* showContactsButton;
53 __unsafe_unretained IBOutlet NSButton* showHistoryButton;
54 __unsafe_unretained IBOutlet NSTabView* tabbar;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040055}
56
57@end
58
59@implementation SmartViewVC
60
61// Tags for views
62NSInteger const IMAGE_TAG = 100;
63NSInteger const DISPLAYNAME_TAG = 200;
64NSInteger const DETAILS_TAG = 300;
65NSInteger const CALL_BUTTON_TAG = 400;
66NSInteger const TXT_BUTTON_TAG = 500;
67
68- (void)awakeFromNib
69{
70 NSLog(@"INIT SmartView VC");
71
72 isShowingContacts = false;
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040073 treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040074
75 [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
108 [showContactsButton setState:NO];
109 isShowingContacts = NO;
110 [showHistoryButton setState:NO];
111 [tabbar selectTabViewItemAtIndex:0];
112 [smartView scrollRowToVisible:proxyIdx.row()];
113 }
114 });
115
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400116 [self.view setWantsLayer:YES];
117 [self.view setLayer:[CALayer layer]];
118 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
119
120 [searchField setWantsLayer:YES];
121 [searchField setLayer:[CALayer layer]];
122 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
123}
124
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500125-(void) selectRow:(id)sender
126{
127 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
128 auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
129 RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
130}
131
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400132- (void)placeCall:(id)sender
133{
134 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
135 ContactMethod* m = nil;
136
137 // Double click on an ongoing call
138 if (qIdx.parent().isValid()) {
139 return;
140 }
141
142 if([[treeController selectedNodes] count] > 0) {
143 QVariant var = qIdx.data((int)Call::Role::ContactMethod);
144 m = qvariant_cast<ContactMethod*>(var);
145 if (!m) {
146 // test if it is a person
147 QVariant var = qIdx.data((int)Person::Role::Object);
148 if (var.isValid()) {
149 Person *c = var.value<Person*>();
150 if (c->phoneNumbers().size() > 0) {
151 m = c->phoneNumbers().first();
152 }
153 }
154 }
155 }
156
157 // Before calling check if we properly extracted a contact method and that
158 // there is NOT already an ongoing call for this index (e.g: no children for this node)
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400159 if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500160 auto c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400161 c->setPeerContactMethod(m);
162 c << Call::Action::ACCEPT;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500163 CallModel::instance().selectCall(c);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400164 }
165}
166
167- (IBAction)showHistory:(NSButton*)sender {
168 if (isShowingContacts) {
169 [showContactsButton setState:NO];
170 isShowingContacts = NO;
171 [tabbar selectTabViewItemAtIndex:1];
172 } else if ([sender state] == NSOffState) {
173 [tabbar selectTabViewItemAtIndex:0];
174 } else {
175 [tabbar selectTabViewItemAtIndex:1];
176 }
177}
178
179- (IBAction)showContacts:(NSButton*)sender {
180 if (isShowingContacts) {
181 [showContactsButton setState:NO];
182 [tabbar selectTabViewItemAtIndex:0];
183 } else {
184 [showHistoryButton setState:![sender state]];
185 [tabbar selectTabViewItemAtIndex:2];
186 }
187
188 isShowingContacts = [sender state];
189}
190
191#pragma mark - NSOutlineViewDelegate methods
192
193// -------------------------------------------------------------------------------
194// shouldSelectItem:item
195// -------------------------------------------------------------------------------
196- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
197{
198 return YES;
199}
200
201// -------------------------------------------------------------------------------
202// shouldEditTableColumn:tableColumn:item
203//
204// Decide to allow the edit of the given outline view "item".
205// -------------------------------------------------------------------------------
206- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
207{
208 return NO;
209}
210
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400211// -------------------------------------------------------------------------------
212// outlineViewSelectionDidChange:notification
213// -------------------------------------------------------------------------------
214- (void)outlineViewSelectionDidChange:(NSNotification *)notification
215{
216 if ([treeController selectedNodes].count <= 0) {
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500217 RecentModel::instance().selectionModel()->clearCurrentIndex();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400218 return;
219 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400220}
221
222/* View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
223 */
224- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
225{
226 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
227 NSTableCellView *result;
228 if (!qIdx.parent().isValid()) {
229 result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
230 NSTextField* details = [result viewWithTag:DETAILS_TAG];
231
Alexandre Lision4e280d62015-09-09 15:56:30 -0400232 [((ContextualTableCellView*) result) setContextualsControls:[NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]]];
233
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400234 if (auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400235 [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400236 [((ContextualTableCellView*) result) setActiveState:YES];
237 } else {
Alexandre Lisiond14bda32015-10-13 11:34:29 -0400238 [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
Alexandre Lision21666f32015-09-22 17:04:36 -0400239 [((ContextualTableCellView*) result) setActiveState:NO];
240 }
241
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400242 } else {
243 result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
244 NSTextField* details = [result viewWithTag:DETAILS_TAG];
245
246 [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
247 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400248
249 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
250 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
251 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
252 Person* p = qvariant_cast<Person*>(qIdx.data((int)Person::Role::Object));
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500253 QVariant photo = GlobalInstances::pixmapManipulator().contactPhoto(p, QSize(50,50));
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400254 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
255 return result;
256}
257
258- (IBAction)callClickedAtRow:(id)sender {
259 NSInteger row = [smartView rowForView:sender];
260 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
261 [self placeCall:nil];
262}
263
264- (IBAction)hangUpClickedAtRow:(id)sender {
265 NSInteger row = [smartView rowForView:sender];
266 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400267 CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400268}
269
270/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
Alexandre Lision4e280d62015-09-09 15:56:30 -0400271*/
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400272- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
273{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400274 return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400275}
Alexandre Lision4e280d62015-09-09 15:56:30 -0400276
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400277
278/* View Based OutlineView: This delegate method can be used to know when a new 'rowView' has been added to the table. At this point, you can choose to add in extra views, or modify any properties on 'rowView'.
279 */
280- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
281{
282
283}
284
285/* View Based OutlineView: This delegate method can be used to know when 'rowView' has been removed from the table. The removed 'rowView' may be reused by the table so any additionally inserted views should be removed at this point. A 'row' parameter is included. 'row' will be '-1' for rows that are being deleted from the table and no longer have a valid row, otherwise it will be the valid row that is being removed due to it being moved off screen.
286 */
287- (void)outlineView:(NSOutlineView *)outlineView didRemoveRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
288{
289
290}
291
292- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
293{
294 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
295 return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 45.0;
296}
297
298- (void) placeCallFromSearchField
299{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400300 Call* c = CallModel::instance().dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400301 // check for a valid ring hash
302 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
303 BOOL valid = [[[searchField stringValue] stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
304
305 if(valid && searchField.stringValue.length == 40) {
306 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",[searchField stringValue]]));
307 } else {
308 c->setDialNumber(QString::fromNSString([searchField stringValue]));
309 }
310
311 c << Call::Action::ACCEPT;
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400312
313 [searchField setStringValue:@""];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400314 RecentModel::instance().peopleProxy()->
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400315 setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString));
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400316}
317
Alexandre Lision61db3552015-10-22 19:12:52 -0400318- (void) addToContact
319{
320 if ([treeController selectedNodes].count == 0)
321 return;
322
323 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
324 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
325 auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
326 if (contactmethod.isEmpty())
327 return;
328
329 if (addToContactPopover != nullptr) {
330 [addToContactPopover performClose:self];
331 addToContactPopover = NULL;
332 } else if (contactmethod.first()) {
333 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
334 [editorVC setMethodToLink:contactmethod.first()];
335 [editorVC setContactLinkedDelegate:self];
336 addToContactPopover = [[NSPopover alloc] init];
337 [addToContactPopover setContentSize:editorVC.view.frame.size];
338 [addToContactPopover setContentViewController:editorVC];
339 [addToContactPopover setAnimates:YES];
340 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
341 [addToContactPopover setDelegate:self];
342
343 [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
344 ofView:smartView preferredEdge:NSMaxXEdge];
345 }
346}
347
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400348- (void) copyNumberToPasteboard:(id) sender
349{
350 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
351 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
352 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
353}
354
355- (void) callNumber:(id) sender
356{
357 Call* c = CallModel::instance().dialingCall();
358 c->setDialNumber(QString::fromNSString([sender representedObject]));
359 c << Call::Action::ACCEPT;
360}
361
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400362#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400363
364- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
365{
366 if (commandSelector == @selector(insertNewline:)) {
367 if([[searchField stringValue] isNotEqualTo:@""]) {
368 [self placeCallFromSearchField];
369 return YES;
370 }
371 }
372
373 return NO;
374}
375
Alexandre Lision61db3552015-10-22 19:12:52 -0400376#pragma mark - NSPopOverDelegate
377
378- (void)popoverDidClose:(NSNotification *)notification
379{
380 if (addToContactPopover != nullptr) {
381 [addToContactPopover performClose:self];
382 addToContactPopover = NULL;
383 }
384}
385
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400386- (void)controlTextDidChange:(NSNotification *) notification
387{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400388 RecentModel::instance().peopleProxy()->
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400389 setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString));
390}
391
Alexandre Lision61db3552015-10-22 19:12:52 -0400392#pragma mark - ContactLinkedDelegate
393
394- (void)contactLinked
395{
396 if (addToContactPopover != nullptr) {
397 [addToContactPopover performClose:self];
398 addToContactPopover = NULL;
399 }
400}
401
402#pragma mark - KeyboardShortcutDelegate
403
404- (void) onAddShortcut
405{
406 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
407 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
408 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
409 if (contactmethods.isEmpty())
410 return;
411
412 auto contactmethod = contactmethods.first();
413 if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
414 [self addToContact];
415 }
416}
417
418#pragma mark - ContextMenuDelegate
419
420- (NSMenu*) contextualMenuForIndex:(NSIndexPath*) path
421{
422 auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
423 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
424 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
425 if (contactmethods.isEmpty())
426 return nil;
427
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400428 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
429
430 if (contactmethods.size() == 1
431 && !contactmethods.first()->contact()
432 || contactmethods.first()->contact()->isPlaceHolder()) {
433
Alexandre Lision61db3552015-10-22 19:12:52 -0400434 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
435 action:@selector(addToContact)
436 keyEquivalent:@"a"
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400437 atIndex:theMenu.itemArray.count];
Alexandre Lision61db3552015-10-22 19:12:52 -0400438 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400439
440 NSMenu* copySubmenu = [[NSMenu alloc] init];
441 NSMenu* callSubmenu = [[NSMenu alloc] init];
442
443 for(auto cm : contactmethods) {
444 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
445 action:@selector(copyNumberToPasteboard:)
446 keyEquivalent:@""];
447
448 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
449 [copySubmenu addItem:tmpCopyItem];
450
451 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
452 action:@selector(callNumber:)
453 keyEquivalent:@""];
454 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
455 [callSubmenu addItem:tmpCallItem];
456 }
457
458 NSMenuItem* copyItems = [[NSMenuItem alloc] init];
459 [copyItems setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
460 [copyItems setSubmenu:copySubmenu];
461
462 NSMenuItem* callItems = [[NSMenuItem alloc] init];
463 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
464 [callItems setSubmenu:callSubmenu];
465
466 [theMenu insertItem:copyItems atIndex:theMenu.itemArray.count];
467 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
468 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
469
470 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400471}
472
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400473@end