Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 1 | /* |
Alexandre Lision | 9fe374b | 2016-01-06 10:17:31 -0500 | [diff] [blame] | 2 | * Copyright (C) 2015-2016 Savoir-faire Linux Inc. |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 3 | * 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 Lision | d14bda3 | 2015-10-13 11:34:29 -0400 | [diff] [blame] | 32 | #import <itemdataroles.h> |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 33 | #import <person.h> |
| 34 | #import <contactmethod.h> |
| 35 | #import <globalinstances.h> |
| 36 | |
| 37 | #import "QNSTreeController.h" |
| 38 | #import "delegates/ImageManipulationDelegate.h" |
Alexandre Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 39 | #import "views/HoverTableRowView.h" |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 40 | #import "PersonLinkerVC.h" |
| 41 | #import "views/RingOutlineView.h" |
Alexandre Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 42 | #import "views/ContextualTableCellView.h" |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 43 | |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 44 | @interface SmartViewVC () <NSOutlineViewDelegate, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> { |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 45 | BOOL isShowingContacts; |
| 46 | QNSTreeController *treeController; |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 47 | NSPopover* addToContactPopover; |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 48 | |
| 49 | //UI elements |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 50 | __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 Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 55 | } |
| 56 | |
| 57 | @end |
| 58 | |
| 59 | @implementation SmartViewVC |
| 60 | |
| 61 | // Tags for views |
| 62 | NSInteger const IMAGE_TAG = 100; |
| 63 | NSInteger const DISPLAYNAME_TAG = 200; |
| 64 | NSInteger const DETAILS_TAG = 300; |
| 65 | NSInteger const CALL_BUTTON_TAG = 400; |
| 66 | NSInteger const TXT_BUTTON_TAG = 500; |
| 67 | |
| 68 | - (void)awakeFromNib |
| 69 | { |
| 70 | NSLog(@"INIT SmartView VC"); |
| 71 | |
| 72 | isShowingContacts = false; |
Alexandre Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 73 | treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()]; |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 74 | |
| 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 Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 82 | [smartView setAction:@selector(selectRow:)]; |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 83 | [smartView setDoubleAction:@selector(placeCall:)]; |
| 84 | |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 85 | [smartView setContextMenuDelegate:self]; |
| 86 | [smartView setShortcutsDelegate:self]; |
| 87 | |
Alexandre Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 88 | QObject::connect(RecentModel::instance().peopleProxy(), |
Alexandre Lision | ee09846 | 2015-10-22 17:22:50 -0400 | [diff] [blame] | 89 | &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 Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 98 | QObject::connect(RecentModel::instance().selectionModel(), |
| 99 | &QItemSelectionModel::currentChanged, |
| 100 | [=](const QModelIndex ¤t, 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 Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 116 | [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 Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 125 | -(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 Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 132 | - (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 Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 159 | if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){ |
Alexandre Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 160 | auto c = CallModel::instance().dialingCall(); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 161 | c->setPeerContactMethod(m); |
| 162 | c << Call::Action::ACCEPT; |
Alexandre Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 163 | CallModel::instance().selectCall(c); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 164 | } |
| 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 Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 211 | // ------------------------------------------------------------------------------- |
| 212 | // outlineViewSelectionDidChange:notification |
| 213 | // ------------------------------------------------------------------------------- |
| 214 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification |
| 215 | { |
| 216 | if ([treeController selectedNodes].count <= 0) { |
Alexandre Lision | 89edc6a | 2015-11-09 11:30:47 -0500 | [diff] [blame] | 217 | RecentModel::instance().selectionModel()->clearCurrentIndex(); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 218 | return; |
| 219 | } |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 220 | } |
| 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 Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 232 | [((ContextualTableCellView*) result) setContextualsControls:[NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]]]; |
| 233 | |
Alexandre Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 234 | if (auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) { |
Alexandre Lision | d14bda3 | 2015-10-13 11:34:29 -0400 | [diff] [blame] | 235 | [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()]; |
Alexandre Lision | 21666f3 | 2015-09-22 17:04:36 -0400 | [diff] [blame] | 236 | [((ContextualTableCellView*) result) setActiveState:YES]; |
| 237 | } else { |
Alexandre Lision | d14bda3 | 2015-10-13 11:34:29 -0400 | [diff] [blame] | 238 | [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()]; |
Alexandre Lision | 21666f3 | 2015-09-22 17:04:36 -0400 | [diff] [blame] | 239 | [((ContextualTableCellView*) result) setActiveState:NO]; |
| 240 | } |
| 241 | |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 242 | } 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 Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 248 | |
| 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 Lision | d5229f3 | 2015-11-16 11:17:41 -0500 | [diff] [blame] | 253 | QVariant photo = GlobalInstances::pixmapManipulator().contactPhoto(p, QSize(50,50)); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 254 | [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 Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 267 | CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE; |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 268 | } |
| 269 | |
| 270 | /* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView. |
Alexandre Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 271 | */ |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 272 | - (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item |
| 273 | { |
Alexandre Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 274 | return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil]; |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 275 | } |
Alexandre Lision | 4e280d6 | 2015-09-09 15:56:30 -0400 | [diff] [blame] | 276 | |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 277 | |
| 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 Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 300 | Call* c = CallModel::instance().dialingCall(); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 301 | // 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 Lision | bf0385e | 2015-10-22 17:36:28 -0400 | [diff] [blame] | 312 | |
| 313 | [searchField setStringValue:@""]; |
Alexandre Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 314 | RecentModel::instance().peopleProxy()-> |
Alexandre Lision | bf0385e | 2015-10-22 17:36:28 -0400 | [diff] [blame] | 315 | setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString)); |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 316 | } |
| 317 | |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 318 | - (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 Lision | d7bf288 | 2015-10-22 22:54:32 -0400 | [diff] [blame^] | 348 | - (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 Lision | bf0385e | 2015-10-22 17:36:28 -0400 | [diff] [blame] | 362 | #pragma NSTextFieldDelegate |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 363 | |
| 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 Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 376 | #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 Lision | bf0385e | 2015-10-22 17:36:28 -0400 | [diff] [blame] | 386 | - (void)controlTextDidChange:(NSNotification *) notification |
| 387 | { |
Alexandre Lision | d3aa3ad | 2015-10-23 14:28:41 -0400 | [diff] [blame] | 388 | RecentModel::instance().peopleProxy()-> |
Alexandre Lision | bf0385e | 2015-10-22 17:36:28 -0400 | [diff] [blame] | 389 | setFilterRegExp(QRegExp(QString::fromNSString([searchField stringValue]), Qt::CaseInsensitive, QRegExp::FixedString)); |
| 390 | } |
| 391 | |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 392 | #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 Lision | d7bf288 | 2015-10-22 22:54:32 -0400 | [diff] [blame^] | 428 | NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""]; |
| 429 | |
| 430 | if (contactmethods.size() == 1 |
| 431 | && !contactmethods.first()->contact() |
| 432 | || contactmethods.first()->contact()->isPlaceHolder()) { |
| 433 | |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 434 | [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action") |
| 435 | action:@selector(addToContact) |
| 436 | keyEquivalent:@"a" |
Alexandre Lision | d7bf288 | 2015-10-22 22:54:32 -0400 | [diff] [blame^] | 437 | atIndex:theMenu.itemArray.count]; |
Alexandre Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 438 | } |
Alexandre Lision | d7bf288 | 2015-10-22 22:54:32 -0400 | [diff] [blame^] | 439 | |
| 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 Lision | 61db355 | 2015-10-22 19:12:52 -0400 | [diff] [blame] | 471 | } |
| 472 | |
Alexandre Lision | 4dfcafc | 2015-08-20 12:43:23 -0400 | [diff] [blame] | 473 | @end |