SmartViewVC.mm 34 KB
Newer Older
1
/*
2
 *  Copyright (C) 2015-2019 Savoir-faire Linux Inc.
3
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 5
 *  Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>
 *  Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 *  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"

24 25 26
//std
#import <sstream>

27 28 29 30 31 32
//Qt
#import <QtMacExtras/qmacfunctions.h>
#import <QPixmap>

//LRC
#import <globalinstances.h>
33 34 35 36 37 38
#import <api/newaccountmodel.h>
#import <api/conversationmodel.h>
#import <api/account.h>
#import <api/contact.h>
#import <api/contactmodel.h>
#import <api/newcallmodel.h>
39 40

#import "delegates/ImageManipulationDelegate.h"
41
#import "views/HoverTableRowView.h"
42
#import "views/IconButton.h"
43
#import "views/RingTableView.h"
44
#import "views/ContextualTableCellView.h"
45
#import "utils.h"
46
#import "RingWindowController.h"
47

48
@interface SmartViewVC () <NSTableViewDelegate, NSTableViewDataSource, NSPopoverDelegate, ContextMenuDelegate, KeyboardShortcutDelegate> {
49

50
    NSPopover* addToContactPopover;
51 52

    //UI elements
53
    __unsafe_unretained IBOutlet RingTableView* smartView;
54
    __unsafe_unretained IBOutlet NSSearchField* searchField;
55
    __strong IBOutlet NSSegmentedControl *listTypeSelector;
56
    __strong IBOutlet NSLayoutConstraint *listTypeSelectorHeight;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
57
    __strong IBOutlet NSLayoutConstraint *listTypeSelectorBottom;
58
    bool selectorIsPresent;
59

60
    QMetaObject::Connection modelSortedConnection_, modelUpdatedConnection_, filterChangedConnection_, newConversationConnection_, conversationRemovedConnection_, newInteractionConnection_, interactionStatusUpdatedConnection_, conversationClearedConnection;
61

62
    lrc::api::ConversationModel* convModel_;
63
    std::string selectedUid_;
64
    lrc::api::profile::Type currentFilterType;
65 66

    __unsafe_unretained IBOutlet RingWindowController *delegate;
67 68 69 70 71 72
}

@end

@implementation SmartViewVC

73 74
@synthesize tabbar;

75
// Tags for views
76 77
NSInteger const IMAGE_TAG           = 100;
NSInteger const DISPLAYNAME_TAG     = 200;
78 79 80 81 82 83 84 85 86 87
NSInteger const NOTIFICATONS_TAG    = 300;
NSInteger const RING_ID_LABEL       = 400;
NSInteger const PRESENCE_TAG        = 500;
NSInteger const TOTALMSGS_TAG       = 600;
NSInteger const TOTALINVITES_TAG    = 700;
NSInteger const DATE_TAG            = 800;
NSInteger const SNIPPET_TAG         = 900;
NSInteger const ADD_BUTTON_TAG            = 1000;
NSInteger const REFUSE_BUTTON_TAG         = 1100;
NSInteger const BLOCK_BUTTON_TAG          = 1200;
88

89 90 91 92
// Segment indices for smartlist selector
NSInteger const CONVERSATION_SEG    = 0;
NSInteger const REQUEST_SEG         = 1;

93 94 95
- (void)awakeFromNib
{
    NSLog(@"INIT SmartView VC");
96 97
    //get selected account
    //encapsulate conversationmodel in local version
98 99 100 101

    [smartView setTarget:self];
    [smartView setDoubleAction:@selector(placeCall:)];

102 103 104
    [smartView setContextMenuDelegate:self];
    [smartView setShortcutsDelegate:self];

105
    [smartView setDataSource: self];
106
    currentFilterType = lrc::api::profile::Type::RING;
107
    selectorIsPresent = true;
108 109

    smartView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleNone;
110

111 112
}

113
- (void)placeCall:(id)sender
114
{
115 116 117 118 119 120
    NSInteger row;
    if (sender != nil && [sender clickedRow] != -1)
        row = [sender clickedRow];
    else if ([smartView selectedRow] != -1)
        row = [smartView selectedRow];
    else
121
        return;
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
    if (convModel_ == nil)
        return;

    auto conv = convModel_->filteredConversation(row);
    convModel_->placeCall(conv.uid);
}

-(void) reloadSelectorNotifications
{
    NSTextField* totalMsgsCount = [self.view viewWithTag:TOTALMSGS_TAG];
    NSTextField* totalInvites = [self.view viewWithTag:TOTALINVITES_TAG];

    if (!selectorIsPresent) {
        [totalMsgsCount setHidden:true];
        [totalInvites setHidden:true];
137
        return;
138
    }
139

140 141 142 143 144 145 146 147 148 149 150 151
    auto ringConversations = convModel_->getFilteredConversations(lrc::api::profile::Type::RING);
    int totalUnreadMessages = 0;
    std::for_each(ringConversations.begin(), ringConversations.end(),
        [&totalUnreadMessages, self] (const auto& conversation) {
            totalUnreadMessages += convModel_->getNumberOfUnreadMessagesFor(conversation.uid);
        });
    [totalMsgsCount setHidden:(totalUnreadMessages == 0)];
    [totalMsgsCount setIntValue:totalUnreadMessages];

    auto totalRequests = [self chosenAccount].contactModel->pendingRequestCount();
    [totalInvites setHidden:(totalRequests == 0)];
    [totalInvites setIntValue:totalRequests];
152 153
}

154
-(void) reloadData
155
{
156
    [smartView deselectAll:nil];
157
    if (convModel_ == nil)
158
        return;
159

160 161 162
    [self reloadSelectorNotifications];

    if (!convModel_->owner.contactModel->hasPendingRequests()) {
163 164 165
        if (currentFilterType == lrc::api::profile::Type::PENDING) {
            [self selectConversationList];
        }
166
        if (selectorIsPresent) {
167
            listTypeSelectorHeight.constant = 0.0;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
168
            listTypeSelectorBottom.priority = 250;
169
            [listTypeSelector setHidden:YES];
170 171
            selectorIsPresent = false;
        }
172
    } else {
173 174
        if (!selectorIsPresent) {
            [listTypeSelector setSelected:YES forSegment:CONVERSATION_SEG];
175
            listTypeSelectorHeight.constant = 18.0;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
176
            listTypeSelectorBottom.priority = 999;
177
            [listTypeSelector setHidden:NO];
178 179
            selectorIsPresent = true;
        }
180 181
    }

182
    [smartView reloadData];
183
    [smartView layoutSubtreeIfNeeded];
184

185 186 187 188
    if (!selectedUid_.empty() && convModel_ != nil) {
        auto it = getConversationFromUid(selectedUid_, *convModel_);
        if (it != convModel_->allFilteredConversations().end()) {
            NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - convModel_->allFilteredConversations().begin())];
189
            [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
190 191 192
        }
    }

193 194 195
    [smartView scrollToBeginningOfDocument:nil];
}

196 197
-(void) reloadConversationWithUid:(NSString *)uid
{
198 199 200 201 202 203 204 205 206
    if (convModel_ == nil) {
        return;
    }

    auto it = getConversationFromUid(std::string([uid UTF8String]), *convModel_);
    if (it != convModel_->allFilteredConversations().end()) {
        NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - convModel_->allFilteredConversations().begin())];
        [smartView reloadDataForRowIndexes:indexSet
                             columnIndexes:[NSIndexSet indexSetWithIndex:0]];
207 208
    }
}
209

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
210 211 212 213 214 215 216 217
-(void) reloadConversationWithURI:(NSString *)uri
{
    if (convModel_ == nil) {
        return;
    }
    [smartView reloadData];
}

218
- (BOOL)setConversationModel:(lrc::api::ConversationModel *)conversationModel
219
{
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
    if (convModel_ == conversationModel) {
        return false;
    }

    convModel_ = conversationModel;
    selectedUid_.clear(); // Clear selected conversation as the selected account is being changed
    QObject::disconnect(modelSortedConnection_);
    QObject::disconnect(modelUpdatedConnection_);
    QObject::disconnect(filterChangedConnection_);
    QObject::disconnect(newConversationConnection_);
    QObject::disconnect(conversationRemovedConnection_);
    QObject::disconnect(conversationClearedConnection);
    QObject::disconnect(interactionStatusUpdatedConnection_);
    QObject::disconnect(newInteractionConnection_);
    [self reloadData];
    if (convModel_ != nil) {
        modelSortedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
237 238 239
                                                        [self] (){
                                                            [self reloadData];
                                                        });
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
        modelUpdatedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationUpdated,
                                                        [self] (const std::string& convUid){
                                                            [self reloadConversationWithUid: [NSString stringWithUTF8String:convUid.c_str()]];
                                                        });
        filterChangedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
                                                        [self] (){
                                                            [self reloadData];
                                                        });
        newConversationConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newConversation,
                                                        [self] (const std::string& convUid) {
                                                            [self reloadData];
                                                            [self updateConversationForNewContact:[NSString stringWithUTF8String:convUid.c_str()]];
                                                        });
        conversationRemovedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationRemoved,
                                                        [self] (){
                                                            [delegate listTypeChanged];
                                                            [self reloadData];
                                                        });
        conversationClearedConnection = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationCleared,
                                                        [self] (const std::string& convUid){
                                                            [self deselect];
                                                            [delegate listTypeChanged];
                                                        });
        interactionStatusUpdatedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
                                                        [self] (const std::string& convUid) {
                                                            if (convUid != selectedUid_)
                                                                return;
                                                            [self reloadConversationWithUid: [NSString stringWithUTF8String:convUid.c_str()]];
                                                        });
        newInteractionConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
                                                        [self](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
                                                            if (convUid == selectedUid_) {
                                                                convModel_->clearUnreadInteractions(convUid);
                                                            }
                                                        });
        convModel_->setFilter(""); // Reset the filter
276
    }
277 278
    [searchField setStringValue:@""];
    return true;
279 280 281 282 283 284 285 286 287 288
}

-(void)selectConversation:(const lrc::api::conversation::Info&)conv model:(lrc::api::ConversationModel*)model;
{
    auto& uid = conv.uid;
    if (selectedUid_ == uid)
        return;

    [self setConversationModel:model];

289 290 291 292 293 294 295 296 297
    if (convModel_ == nil) {
        return;
    }

    auto it = getConversationFromUid(selectedUid_, *convModel_);
    if (it != convModel_->allFilteredConversations().end()) {
        NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - convModel_->allFilteredConversations().begin())];
        [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
        selectedUid_ = uid;
298 299 300
    }
}

301 302 303 304 305 306
-(void)deselect
{
    selectedUid_.clear();
    [smartView deselectAll:nil];
}

307
-(void) clearConversationModel {
308
    convModel_ = nil;
309 310
    [self deselect];
    [smartView reloadData];
311 312 313 314
    if (selectorIsPresent) {
        [listTypeSelector removeFromSuperview];
        selectorIsPresent = false;
    }
315 316
}

317 318
- (IBAction) listTypeChanged:(id)sender
{
319
    selectedUid_.clear();
320
    NSInteger selectedItem = [sender selectedSegment];
321 322
    switch (selectedItem) {
        case CONVERSATION_SEG:
323
            if (currentFilterType != lrc::api::profile::Type::RING) {
324
                convModel_->setFilter(lrc::api::profile::Type::RING);
325 326 327
                [delegate listTypeChanged];
                currentFilterType = lrc::api::profile::Type::RING;
            }
328 329
            break;
        case REQUEST_SEG:
330
            if (currentFilterType != lrc::api::profile::Type::PENDING) {
331
                convModel_->setFilter(lrc::api::profile::Type::PENDING);
332 333 334
                [delegate listTypeChanged];
                currentFilterType = lrc::api::profile::Type::PENDING;
            }
335 336 337
            break;
        default:
            NSLog(@"Invalid item selected in list selector: %d", selectedItem);
338 339 340 341 342
    }
}

-(void) selectConversationList
{
343 344
    if (currentFilterType == lrc::api::profile::Type::RING)
        return;
345
    [listTypeSelector setSelectedSegment:CONVERSATION_SEG];
346 347 348 349

    // Do not invert order of the next two lines or stack overflow
    // may happen on -(void) reloadData call if filter is currently set to PENDING
    currentFilterType = lrc::api::profile::Type::RING;
350 351
    convModel_->setFilter(lrc::api::profile::Type::RING);
    convModel_->setFilter("");
352 353
}

354 355 356 357 358 359 360
-(void) selectPendingList
{
    if (currentFilterType == lrc::api::profile::Type::PENDING)
        return;
    [listTypeSelector setSelectedSegment:REQUEST_SEG];

    currentFilterType = lrc::api::profile::Type::PENDING;
361 362
    convModel_->setFilter(lrc::api::profile::Type::PENDING);
    convModel_->setFilter("");
363 364
}

365
#pragma mark - NSTableViewDelegate methods
366

367
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
368 369 370 371
{
    return YES;
}

372
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
373 374 375 376
{
    return NO;
}

377
- (void)tableViewSelectionDidChange:(NSNotification *)notification
378
{
379
    NSInteger row = [notification.object selectedRow];
380
    NSInteger rows = [smartView numberOfRows];
381

382
    for (int i = 0; i< rows; i++) {
383 384
        HoverTableRowView* cellRowView = [smartView rowViewAtRow:i makeIfNecessary: NO];
        [cellRowView drawSelection: (i == row)];
385
    }
386

387
    if (row == -1)
388
        return;
389
    if (convModel_ == nil)
390
        return;
391

392
    auto uid = convModel_->filteredConversation(row).uid;
393 394
    if (selectedUid_ != uid) {
        selectedUid_ = uid;
395 396 397
        convModel_->selectConversation(uid);
        convModel_->clearUnreadInteractions(uid);
        [self reloadSelectorNotifications];
398 399 400
    }
}

401 402 403 404 405 406
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
{
    return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
}

- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
407
{
408
    if (convModel_ == nil)
409
        return nil;
410

411
    auto conversation = convModel_->filteredConversation(row);
412
    NSTableCellView* result;
413

414
    result = [tableView makeViewWithIdentifier:@"MainCell" owner:tableView];
415

416
    NSTextField* unreadCount = [result viewWithTag:NOTIFICATONS_TAG];
417 418
    [unreadCount setHidden:(conversation.unreadMessages == 0)];
    [unreadCount setIntValue:conversation.unreadMessages];
419
    NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
420
    NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
421 422 423 424 425 426 427
    NSTextField* lastInteractionDate = [result viewWithTag:DATE_TAG];
    NSTextField* interactionSnippet = [result viewWithTag:SNIPPET_TAG];
    [displayName setStringValue:@""];
    [displayRingID setStringValue:@""];
    [lastInteractionDate setStringValue:@""];
    [interactionSnippet setStringValue:@""];
    NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
428 429
    NSString* displayNameString = bestNameForConversation(conversation, *convModel_);
    NSString* displayIDString = bestIDForConversation(conversation, *convModel_);
430 431
    if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
        [displayName setStringValue:displayIDString];
432
        [displayRingID setHidden:YES];
433 434 435 436
    }
    else {
        [displayName setStringValue:displayNameString];
        [displayRingID setStringValue:displayIDString];
437
        [displayRingID setHidden:NO];
438
    }
439
    auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
    NSImage* image = QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(conversation, convModel_->owner)));
    if(image) {
        [NSLayoutConstraint deactivateConstraints:[photoView constraints]];
        NSArray* constraints = [NSLayoutConstraint
                                constraintsWithVisualFormat:@"H:[photoView(54)]"
                                options:0
                                metrics:nil                                                                          views:NSDictionaryOfVariableBindings(photoView)];
        [NSLayoutConstraint activateConstraints:constraints];
    } else {
        [NSLayoutConstraint deactivateConstraints:[photoView constraints]];
        NSArray* constraints = [NSLayoutConstraint
                                constraintsWithVisualFormat:@"H:[photoView(0)]"
                                options:0
                                metrics:nil                                                                          views:NSDictionaryOfVariableBindings(photoView)];
        [NSLayoutConstraint activateConstraints:constraints];
    }
    [photoView setImage: image];
457 458

    NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
459 460 461 462 463 464 465 466 467 468
    [presenceView setHidden:YES];
    if (!conversation.participants.empty()){
        try {
            auto contact = convModel_->owner.contactModel->getContact(conversation.participants[0]);
            if (contact.isPresent) {
                [presenceView setHidden:NO];
            }
        } catch (std::out_of_range& e) {
            NSLog(@"viewForTableColumn: getContact - out of range");
        }
469
    }
470

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    NSButton* addContactButton = [result viewWithTag:ADD_BUTTON_TAG];
    NSButton* refuseContactButton = [result viewWithTag:REFUSE_BUTTON_TAG];
    NSButton* blockContactButton = [result viewWithTag:BLOCK_BUTTON_TAG];
    [addContactButton setHidden:YES];
    [refuseContactButton setHidden:YES];
    [blockContactButton setHidden:YES];

    if (profileType(conversation, *convModel_) == lrc::api::profile::Type::PENDING) {
        [lastInteractionDate setHidden:true];
        [interactionSnippet setHidden:true];
        [addContactButton setHidden:NO];
        [refuseContactButton setHidden:NO];
        [blockContactButton setHidden:NO];
        [addContactButton setAction:@selector(acceptInvitation:)];
        [addContactButton setTarget:self];
        [refuseContactButton setAction:@selector(refuseInvitation:)];
        [refuseContactButton setTarget:self];
        [blockContactButton setAction:@selector(blockPendingContact:)];
        [blockContactButton setTarget:self];
        return result;
    }

    [lastInteractionDate setHidden:false];

    [interactionSnippet setHidden:false];

    auto lastUid = conversation.lastMessageUid;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511
    auto callId = conversation.confId.empty() ? conversation.callId : conversation.confId;
    NSString *callInfo = @"";
    if (!callId.empty()) {
        if ([self chosenAccount].callModel.get()->hasCall(callId)) {
        auto call = [self chosenAccount].callModel.get()->getCall(callId);
            callInfo = (call.status == lrc::api::call::Status::IN_PROGRESS) ? @"Talking" :  @(to_string(call.status).c_str());
        }
    }
    
    if (callInfo.length > 0) {
        [lastInteractionDate setStringValue: callInfo];
        [interactionSnippet setHidden:true];
        return result;
    }
512 513 514 515 516 517 518 519
    if (conversation.interactions.find(lastUid) != conversation.interactions.end()) {
        // last interaction snippet
        std::string lastInteractionSnippet = conversation.interactions[lastUid].body;
        std::stringstream ss(lastInteractionSnippet);
        std::getline(ss, lastInteractionSnippet);
        NSString* lastInteractionSnippetFixedString = [[NSString stringWithUTF8String:lastInteractionSnippet.c_str()]
                                                       stringByReplacingOccurrencesOfString:@"🕽" withString:@""];
        lastInteractionSnippetFixedString = [lastInteractionSnippetFixedString stringByReplacingOccurrencesOfString:@"📞" withString:@""];
520
        if (conversation.interactions[lastUid].type == lrc::api::interaction::Type::DATA_TRANSFER) {
521
            lastInteractionSnippetFixedString = [lastInteractionSnippetFixedString lastPathComponent];
522 523 524 525
        }
        [interactionSnippet setStringValue:lastInteractionSnippetFixedString];

        // last interaction date/time
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
        NSString *timeString = @"";
        NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:conversation.interactions[lastUid].timestamp];
        NSDate *today = [NSDate date];
        NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
        [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
        if ([[NSCalendar currentCalendar] compareDate:today
                                               toDate:msgTime
                                    toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
            timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterNoStyle];
        } else if ([[NSCalendar currentCalendar] compareDate:today
                                                      toDate:msgTime
                                           toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
                   [[NSCalendar currentCalendar] compareDate:today
                                                      toDate:msgTime
                                           toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
            timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterNoStyle];
542
        } else {
543
            timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
544
        }
545
        [lastInteractionDate setStringValue:timeString];
546 547
    }

548 549 550
    return result;
}

551
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
552
{
553 554 555 556 557 558 559
    return 60.0;
}

#pragma mark - NSTableDataSource methods

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
560 561
    if (tableView == smartView && convModel_ != nullptr) {
        return convModel_->allFilteredConversations().size();
562 563 564
    }

    return 0;
565 566
}

567
- (void)startCallForRow:(id)sender {
568 569 570 571 572 573 574
    NSInteger row = [smartView rowForView:sender];
    [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
    [self placeCall:nil];
}

- (IBAction)hangUpClickedAtRow:(id)sender {
    NSInteger row = [smartView rowForView:sender];
575

576 577
    if (row == -1)
        return;
578
    if (convModel_ == nil)
579
        return;
580

581
    auto conv = convModel_->filteredConversation(row);
582
    auto& callId = conv.callId;
583

584
    if (callId.empty())
585 586
        return;

587
    auto* callModel = convModel_->owner.callModel.get();
588
    callModel->hangUp(callId);
589
}
590

591 592 593 594 595 596 597 598 599 600 601
- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
{
    NSAlert* alert = [NSAlert alertWithMessageText:title
                                     defaultButton:@"Ok"
                                   alternateButton:nil
                                       otherButton:nil
                         informativeTextWithFormat:message];

    [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
}

602
- (void) processSearchFieldInput
603
{
604 605 606 607 608
    if (convModel_ == nil) {
        return;
    }

    convModel_->setFilter(std::string([[searchField stringValue] UTF8String]));
609 610
}

611
-(const lrc::api::account::Info&) chosenAccount
612
{
613
    return convModel_->owner;
614 615
}

616 617 618
- (void) clearSearchField
{
    [searchField setStringValue:@""];
619
    [self processSearchFieldInput];
620 621
}

622
-(void)updateConversationForNewContact:(NSString *)uId {
623
    if (convModel_ == nil) {
624 625 626 627
        return;
    }
    [self clearSearchField];
    auto uid = std::string([uId UTF8String]);
628 629
    auto it = getConversationFromUid(uid, *convModel_);
    if (it != convModel_->allFilteredConversations().end()) {
630
        @try {
631
            auto contact = convModel_->owner.contactModel->getContact(it->participants[0]);
632 633
            if (!contact.profileInfo.uri.empty() && contact.profileInfo.uri.compare(selectedUid_) == 0) {
                selectedUid_ = uid;
634
                convModel_->selectConversation(uid);
635 636 637 638 639 640 641
            }
        } @catch (NSException *exception) {
            return;
        }
    }
}

642 643 644 645 646 647
/**
 Copy a NSString in the general Pasteboard

 @param sender the NSObject containing the represented object to copy
 */
- (void) copyStringToPasteboard:(id) sender
648 649 650 651 652 653
{
    NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
    [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
    [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
}

654
#pragma NSTextFieldDelegate
655 656 657

- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
658 659 660
    if (commandSelector != @selector(insertNewline:) || [[searchField stringValue] isEqual:@""]) {
        return NO;
    }
661
    if (convModel_ == nil) {
662 663 664
        [self displayErrorModalWithTitle:NSLocalizedString(@"No account available", @"Displayed as RingID when no accounts are available for selection") WithMessage:NSLocalizedString(@"Navigate to preferences to create a new account", @"Allert message when no accounts are available")];
        return NO;
    }
665
    if (convModel_->allFilteredConversations().size() <= 0) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
666 667
        return YES;
    }
668
    auto model = convModel_->filteredConversation(0);
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
669 670 671 672 673
    auto uid = model.uid;
    if (selectedUid_ == uid) {
        return YES;
    }
    @try {
674
        auto contact = convModel_->owner.contactModel->getContact(model.participants[0]);
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
675
        if ((contact.profileInfo.uri.empty() && contact.profileInfo.type != lrc::api::profile::Type::SIP) || contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
676 677
            return YES;
        }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
678
        selectedUid_ = uid;
679
        convModel_->selectConversation(uid);
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
680 681 682 683
        [self.view.window makeFirstResponder: smartView];
        return YES;
    } @catch (NSException *exception) {
        return YES;
684 685 686
    }
}

687 688
- (void)controlTextDidChange:(NSNotification *) notification
{
689
    [self processSearchFieldInput];
690 691
}

692 693 694 695 696 697 698 699 700 701
#pragma mark - NSPopOverDelegate

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

702

703 704 705 706 707 708 709 710 711 712 713 714 715 716
#pragma mark - ContactLinkedDelegate

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

#pragma mark - KeyboardShortcutDelegate

- (void) onAddShortcut
{
717
    if ([smartView selectedRow] == -1)
718
        return;
719
    if (convModel_ == nil)
720
        return ;
721

722 723
    auto uid = convModel_->filteredConversation([smartView selectedRow]).uid;
    convModel_->makePermanent(uid);
724 725 726 727
}

#pragma mark - ContextMenuDelegate

728
- (NSMenu*) contextualMenuForRow:(int) index
729
{
730
    if (convModel_ == nil)
731 732
        return nil;

733
    auto conversation = convModel_->filteredConversation(NSInteger(index));
734

735
    @try {
736
        auto contact = convModel_->owner.contactModel->getContact(conversation.participants[0]);
737 738 739
        if (contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
            return nil;
        }
740

741 742 743 744 745 746 747 748 749 750 751 752 753
        BOOL isSIP = false;
        BOOL isRingContact = false;
        /* for SIP contact show only call menu options
         * if contact does not have uri that is not RING contact
         * for trusted Ring contact show option block contact
         * for untrasted contact show option add contact
         */

        if (contact.profileInfo.type == lrc::api::profile::Type::SIP) {
            isSIP = true;
        } else if (contact.profileInfo.uri.empty()) {
            return nil;
        }
754

755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
        else if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.isTrusted == true) {
            isRingContact = true;
        }
        auto conversationUD = conversation.uid;
        NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
        NSString* conversationUID = @(conversationUD.c_str());
        NSMenuItem* separator = [NSMenuItem separatorItem];
        NSMenuItem* videoCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place video call",
                                                                                        @"Contextual menu action")
                                                               action:@selector(videoCall:)
                                                        keyEquivalent:@""];
        NSMenuItem* audioCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place audio call",
                                                                                        @"Contextual menu action")
                                                               action:@selector(audioCall:)
                                                        keyEquivalent:@""];
        [videoCallItem setRepresentedObject: conversationUID];
        [audioCallItem setRepresentedObject: conversationUID];
        [theMenu addItem:videoCallItem];
        [theMenu addItem:audioCallItem];
        if (isSIP == false) {
            [theMenu addItem:separator];
            NSMenuItem* clearConversationItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Clear conversation", @"Contextual menu action")
                                                                           action:@selector(clearConversation:)
                                                                    keyEquivalent:@""];
            [clearConversationItem setRepresentedObject: conversationUID];
            [theMenu addItem:clearConversationItem];
            if(isRingContact) {
                NSMenuItem* blockContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Block contact", @"Contextual menu action")
                                                                          action:@selector(blockContact:)
                                                                   keyEquivalent:@""];
                [blockContactItem setRepresentedObject: conversationUID];
                [theMenu addItem:blockContactItem];
            } else {
                NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
                                                                        action:@selector(addContact:)
                                                                 keyEquivalent:@"A"];
                [addContactItem setRepresentedObject: conversationUID];
                [theMenu addItem:addContactItem];
            }
        }
        return theMenu;
796
    }
797 798 799 800
    @catch (NSException *exception) {
        return nil;
    }
}
801

802 803 804 805 806 807 808
- (void) addContact: (NSMenuItem* ) item  {
    auto menuObject = item.representedObject;
    if(menuObject == nil) {
        return;
    }
    NSString * convUId = (NSString*)menuObject;
    std::string conversationID = std::string([convUId UTF8String]);
809
    convModel_->makePermanent(conversationID);
810
}
811

812 813 814 815
- (void) blockContact: (NSMenuItem* ) item  {
    auto menuObject = item.representedObject;
    if(menuObject == nil) {
        return;
816
    }
817 818
    NSString * convUId = (NSString*)menuObject;
    std::string conversationID = std::string([convUId UTF8String]);
819 820
    //convModel_->clearHistory(conversationID);
    convModel_->removeConversation(conversationID, true);
821
}
822

823 824 825 826 827 828 829
- (void) audioCall: (NSMenuItem* ) item  {
    auto menuObject = item.representedObject;
    if(menuObject == nil) {
        return;
    }
    NSString * convUId = (NSString*)menuObject;
    std::string conversationID = std::string([convUId UTF8String]);
830
    convModel_->placeAudioOnlyCall(conversationID);
831

832
}
833

834 835 836 837 838 839 840
- (void) videoCall: (NSMenuItem* ) item  {
    auto menuObject = item.representedObject;
    if(menuObject == nil) {
        return;
    }
    NSString * convUId = (NSString*)menuObject;
    std::string conversationID = std::string([convUId UTF8String]);
841
    convModel_->placeCall(conversationID);
842
}
843

844 845 846 847 848 849 850
- (void) clearConversation:(NSMenuItem* ) item  {
    auto menuObject = item.representedObject;
    if(menuObject == nil) {
        return;
    }
    NSString * convUId = (NSString*)menuObject;
    std::string conversationID = std::string([convUId UTF8String]);
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
    convModel_->clearHistory(conversationID);
}

- (void)acceptInvitation:(id)sender {
    NSInteger row = [smartView rowForView:sender];

    if (row == -1)
        return;
    if (convModel_ == nil)
        return;

    auto conv = convModel_->filteredConversation(row);
    auto& convID = conv.Info::uid;

    if (convID.empty())
        return;
    convModel_->makePermanent(convID);
}

- (void)refuseInvitation:(id)sender {
    NSInteger row = [smartView rowForView:sender];

    if (row == -1)
        return;
    if (convModel_ == nil)
        return;

    auto conv = convModel_->filteredConversation(row);
    auto& convID = conv.Info::uid;

    if (convID.empty())
        return;
    convModel_->removeConversation(convID);
}

- (void)blockPendingContact:(id)sender {
    NSInteger row = [smartView rowForView:sender];

    if (row == -1)
        return;
    if (convModel_ == nil)
        return;

    auto conv = convModel_->filteredConversation(row);
    auto& convID = conv.Info::uid;

    if (convID.empty())
        return;
    convModel_->removeConversation(convID, true);
    [self deselect];
    [delegate listTypeChanged];
902 903
}

904
@end