SmartViewVC.mm 16.5 KB
Newer Older
1
/*
2
 *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#import "SmartViewVC.h"

//Qt
#import <QtMacExtras/qmacfunctions.h>
#import <QPixmap>
#import <QIdentityProxyModel>
#import <QItemSelectionModel>

//LRC
#import <recentmodel.h>
#import <callmodel.h>
#import <call.h>
32
#import <itemdataroles.h>
33 34 35
#import <person.h>
#import <contactmethod.h>
#import <globalinstances.h>
36
#import <phonedirectorymodel.h>
37 38 39

#import "QNSTreeController.h"
#import "delegates/ImageManipulationDelegate.h"
40
#import "views/HoverTableRowView.h"
41 42
#import "PersonLinkerVC.h"
#import "views/RingOutlineView.h"
43
#import "views/ContextualTableCellView.h"
44

45
@interface SmartViewVC () <NSOutlineViewDelegate, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> {
46

47
    QNSTreeController *treeController;
48
    NSPopover* addToContactPopover;
49 50

    //UI elements
51 52 53 54 55
    __unsafe_unretained IBOutlet RingOutlineView* smartView;
    __unsafe_unretained IBOutlet NSSearchField* searchField;
    __unsafe_unretained IBOutlet NSButton* showContactsButton;
    __unsafe_unretained IBOutlet NSButton* showHistoryButton;
    __unsafe_unretained IBOutlet NSTabView* tabbar;
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
}

@end

@implementation SmartViewVC

// Tags for views
NSInteger const IMAGE_TAG       =   100;
NSInteger const DISPLAYNAME_TAG =   200;
NSInteger const DETAILS_TAG     =   300;
NSInteger const CALL_BUTTON_TAG =   400;
NSInteger const TXT_BUTTON_TAG  =   500;

- (void)awakeFromNib
{
    NSLog(@"INIT SmartView VC");

73
    RecentModel::instance().peopleProxy()->setFilterRole((int)Ring::Role::Name);
74
    treeController = [[QNSTreeController alloc] initWithQModel:RecentModel::instance().peopleProxy()];
75 76 77 78 79 80 81
    [treeController setAvoidsEmptySelection:NO];
    [treeController setChildrenKeyPath:@"children"];

    [smartView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
    [smartView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
    [smartView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
    [smartView setTarget:self];
82
    [smartView setAction:@selector(selectRow:)];
83 84
    [smartView setDoubleAction:@selector(placeCall:)];

85 86 87
    [smartView setContextMenuDelegate:self];
    [smartView setShortcutsDelegate:self];

88
    QObject::connect(RecentModel::instance().peopleProxy(),
89 90 91 92 93 94 95 96 97
                     &QAbstractItemModel::dataChanged,
                     [self](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
                         for(int row = topLeft.row() ; row <= bottomRight.row() ; ++row)
                         {
                             [smartView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
                                                  columnIndexes:[NSIndexSet indexSetWithIndex:0]];
                         }
                     });

98 99 100
    QObject::connect(RecentModel::instance().selectionModel(),
                     &QItemSelectionModel::currentChanged,
                     [=](const QModelIndex &current, const QModelIndex &previous) {
101 102
                         if(!current.isValid()) {
                             [smartView deselectAll:nil];
103
                             return;
104
                         }
105 106 107 108 109

                         auto proxyIdx = RecentModel::instance().peopleProxy()->mapFromSource(current);
                         if (proxyIdx.isValid()) {
                             [treeController setSelectionQModelIndex:proxyIdx];

110 111
                             [showContactsButton setHighlighted:NO];
                             [showHistoryButton setHighlighted:NO];
112 113 114 115 116
                             [tabbar selectTabViewItemAtIndex:0];
                             [smartView scrollRowToVisible:proxyIdx.row()];
                         }
                     });

117 118 119 120 121 122 123 124 125
    [self.view setWantsLayer:YES];
    [self.view setLayer:[CALayer layer]];
    [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];

    [searchField setWantsLayer:YES];
    [searchField setLayer:[CALayer layer]];
    [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
}

126 127
-(void) selectRow:(id)sender
{
128 129 130 131
    if ([treeController selectedNodes].count == 0) {
        RecentModel::instance().selectionModel()->clearCurrentIndex();
        return;
    }
132 133 134 135 136
    auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
    auto proxyIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
    RecentModel::instance().selectionModel()->setCurrentIndex(proxyIdx, QItemSelectionModel::ClearAndSelect);
}

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
- (void)placeCall:(id)sender
{
    QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
    ContactMethod* m = nil;

    // Double click on an ongoing call
    if (qIdx.parent().isValid()) {
        return;
    }

    if([[treeController selectedNodes] count] > 0) {
        QVariant var = qIdx.data((int)Call::Role::ContactMethod);
        m = qvariant_cast<ContactMethod*>(var);
        if (!m) {
            // test if it is a person
            QVariant var = qIdx.data((int)Person::Role::Object);
            if (var.isValid()) {
                Person *c = var.value<Person*>();
                if (c->phoneNumbers().size() > 0) {
                    m = c->phoneNumbers().first();
                }
            }
        }
    }

    // Before calling check if we properly extracted a contact method and that
    // there is NOT already an ongoing call for this index (e.g: no children for this node)
164
    if(m && !RecentModel::instance().peopleProxy()->index(0, 0, qIdx).isValid()){
165
        auto c = CallModel::instance().dialingCall();
166 167
        c->setPeerContactMethod(m);
        c << Call::Action::ACCEPT;
168
        CallModel::instance().selectCall(c);
169 170 171
    }
}

172 173 174 175 176 177
- (IBAction)showHistory:(NSButton*)sender
{
    [showContactsButton setHighlighted:NO];
    [showHistoryButton setHighlighted:![sender isHighlighted]];

    if (![sender isHighlighted]) {
178 179 180 181 182 183
        [tabbar selectTabViewItemAtIndex:0];
    } else {
        [tabbar selectTabViewItemAtIndex:1];
    }
}

184 185 186 187 188 189
- (IBAction)showContacts:(NSButton*)sender
{
    [showContactsButton setHighlighted:![sender isHighlighted]];
    [showHistoryButton setHighlighted:NO];

    if (![sender isHighlighted]) {
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
        [tabbar selectTabViewItemAtIndex:0];
    } else {
        [tabbar selectTabViewItemAtIndex:2];
    }
}

#pragma mark - NSOutlineViewDelegate methods

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
{
    return YES;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    return NO;
}

- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    if ([treeController selectedNodes].count <= 0) {
211
        RecentModel::instance().selectionModel()->clearCurrentIndex();
212 213 214 215 216 217 218 219 220 221 222 223
        return;
    }
}

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
    NSTableCellView *result;
    if (!qIdx.parent().isValid()) {
        result = [outlineView makeViewWithIdentifier:@"MainCell" owner:outlineView];
        NSTextField* details = [result viewWithTag:DETAILS_TAG];

224 225
        NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
        [((ContextualTableCellView*) result) setContextualsControls:controls];
226

227
        if (auto call = RecentModel::instance().getActiveCall(RecentModel::instance().peopleProxy()->mapToSource(qIdx))) {
228
            [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
229 230
            [((ContextualTableCellView*) result) setActiveState:YES];
        } else {
231
            [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
232 233 234
            [((ContextualTableCellView*) result) setActiveState:NO];
        }

235 236 237 238 239 240
        NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
        int unread = qIdx.data((int)Ring::Role::UnreadTextMessageCount).toInt();
        [unreadCount setHidden:(unread == 0)];
        [unreadCount.layer setCornerRadius:5.0f];
        [unreadCount setStringValue:qIdx.data((int)Ring::Role::UnreadTextMessageCount).toString().toNSString()];

241 242 243 244 245 246 247 248
    } else {
        result = [outlineView makeViewWithIdentifier:@"CallCell" owner:outlineView];
        NSTextField* details = [result viewWithTag:DETAILS_TAG];

        [details setStringValue:qIdx.data((int)Call::Role::HumanStateName).toString().toNSString()];
    }

    NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
249
    [displayName setStringValue:qIdx.data((int)Ring::Role::Name).toString().toNSString()];
250 251
    NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
    Person* p = qvariant_cast<Person*>(qIdx.data((int)Person::Role::Object));
252
    QVariant photo = GlobalInstances::pixmapManipulator().contactPhoto(p, QSize(50,50));
253 254 255 256
    [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(photo))];
    return result;
}

257 258 259 260 261 262 263 264 265 266
- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
{
    [outlineView scrollRowToVisible:0];
}

- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
{
    return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
}

267 268 269 270 271 272 273 274 275
- (IBAction)callClickedAtRow:(id)sender {
    NSInteger row = [smartView rowForView:sender];
    [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
    [self placeCall:nil];
}

- (IBAction)hangUpClickedAtRow:(id)sender {
    NSInteger row = [smartView rowForView:sender];
    [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
276
    CallModel::instance().getCall(CallModel::instance().selectionModel()->currentIndex()) << Call::Action::REFUSE;
277 278 279 280 281 282 283 284
}

- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
    QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
    return (((NSTreeNode*)item).indexPath.length == 1) ? 60.0 : 45.0;
}

285
- (void) startConversationFromSearchField
286
{
287 288 289 290
    auto cm = PhoneDirectoryModel::instance().getNumber(QString::fromNSString([searchField stringValue]));
    time_t currentTime;
    ::time(&currentTime);
    cm->setLastUsed(currentTime);
291
}
292

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
- (void) addToContact
{
    if ([treeController selectedNodes].count == 0)
        return;

    auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
    auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
    auto contactmethod = RecentModel::instance().getContactMethods(originIdx);
    if (contactmethod.isEmpty())
        return;

    if (addToContactPopover != nullptr) {
        [addToContactPopover performClose:self];
        addToContactPopover = NULL;
    } else if (contactmethod.first()) {
        auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
        [editorVC setMethodToLink:contactmethod.first()];
        [editorVC setContactLinkedDelegate:self];
        addToContactPopover = [[NSPopover alloc] init];
        [addToContactPopover setContentSize:editorVC.view.frame.size];
        [addToContactPopover setContentViewController:editorVC];
        [addToContactPopover setAnimates:YES];
        [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
        [addToContactPopover setDelegate:self];

        [addToContactPopover showRelativeToRect:[smartView frameOfCellAtColumn:0 row:[smartView selectedRow]]
                                         ofView:smartView preferredEdge:NSMaxXEdge];
    }
}

323 324 325 326 327 328 329 330 331 332 333 334 335 336
- (void) copyNumberToPasteboard:(id) sender
{
    NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
    [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
    [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
}

- (void) callNumber:(id) sender
{
    Call* c = CallModel::instance().dialingCall();
    c->setDialNumber(QString::fromNSString([sender representedObject]));
    c << Call::Action::ACCEPT;
}

337
#pragma NSTextFieldDelegate
338 339 340 341 342

- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
    if (commandSelector == @selector(insertNewline:)) {
        if([[searchField stringValue] isNotEqualTo:@""]) {
343
            [self startConversationFromSearchField];
344 345 346 347 348 349 350
            return YES;
        }
    }

    return NO;
}

351 352 353 354 355 356 357 358 359 360
#pragma mark - NSPopOverDelegate

- (void)popoverDidClose:(NSNotification *)notification
{
    if (addToContactPopover != nullptr) {
        [addToContactPopover performClose:self];
        addToContactPopover = NULL;
    }
}

361 362
- (void)controlTextDidChange:(NSNotification *) notification
{
363
    RecentModel::instance().peopleProxy()->setFilterWildcard(QString::fromNSString([searchField stringValue]));
364 365
}

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
#pragma mark - ContactLinkedDelegate

- (void)contactLinked
{
    if (addToContactPopover != nullptr) {
        [addToContactPopover performClose:self];
        addToContactPopover = NULL;
    }
}

#pragma mark - KeyboardShortcutDelegate

- (void) onAddShortcut
{
    auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
    auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
    auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
    if (contactmethods.isEmpty())
        return;

    auto contactmethod = contactmethods.first();
    if (contactmethod && (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder())) {
        [self addToContact];
    }
}

#pragma mark - ContextMenuDelegate

- (NSMenu*) contextualMenuForIndex:(NSIndexPath*) path
{
    auto qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
    auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
    auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
    if (contactmethods.isEmpty())
        return nil;

402 403 404 405 406 407
    NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];

    if (contactmethods.size() == 1
        && !contactmethods.first()->contact()
        || contactmethods.first()->contact()->isPlaceHolder()) {

408 409 410
        [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
                              action:@selector(addToContact)
                       keyEquivalent:@"a"
411
                             atIndex:theMenu.itemArray.count];
412
    }
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

    NSMenu* copySubmenu = [[NSMenu alloc] init];
    NSMenu* callSubmenu = [[NSMenu alloc] init];

    for(auto cm : contactmethods) {
        NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
                                                             action:@selector(copyNumberToPasteboard:)
                                                      keyEquivalent:@""];

        [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
        [copySubmenu addItem:tmpCopyItem];

        NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
                                                             action:@selector(callNumber:)
                                                      keyEquivalent:@""];
        [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
        [callSubmenu addItem:tmpCallItem];
    }

    NSMenuItem* copyItems = [[NSMenuItem alloc] init];
    [copyItems setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
    [copyItems setSubmenu:copySubmenu];

    NSMenuItem* callItems = [[NSMenuItem alloc] init];
    [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
    [callItems setSubmenu:callSubmenu];

    [theMenu insertItem:copyItems atIndex:theMenu.itemArray.count];
    [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
    [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];

    return theMenu;
445 446
}

447
@end