blob: 32560c97a769c124f50f6777baaa3c6b4d060d35 [file] [log] [blame]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04001/*
Anthony Léonardcb73a2c2018-01-18 11:45:56 -05002 * Copyright (C) 2015-2018 Savoir-faire Linux Inc.
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
Olivier Soldanod4311552017-11-20 15:09:53 -05004 * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>
5 * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22#import "SmartViewVC.h"
23
24//Qt
25#import <QtMacExtras/qmacfunctions.h>
26#import <QPixmap>
27#import <QIdentityProxyModel>
28#import <QItemSelectionModel>
29
30//LRC
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040031#import <globalinstances.h>
Olivier Soldanod4311552017-11-20 15:09:53 -050032#import <api/newaccountmodel.h>
33#import <api/conversationmodel.h>
34#import <api/account.h>
35#import <api/contact.h>
36#import <api/contactmodel.h>
37#import <api/newcallmodel.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040038
39#import "QNSTreeController.h"
40#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040041#import "views/HoverTableRowView.h"
Alexandre Lision61db3552015-10-22 19:12:52 -040042#import "PersonLinkerVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050043#import "views/IconButton.h"
Olivier Soldanod4311552017-11-20 15:09:53 -050044#import "views/RingTableView.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040045#import "views/ContextualTableCellView.h"
Olivier Soldanod4311552017-11-20 15:09:53 -050046#import "utils.h"
Anthony Léonardbee94cc2018-01-16 11:42:40 -050047#import "RingWindowController.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040048
Olivier Soldanod4311552017-11-20 15:09:53 -050049@interface SmartViewVC () <NSTableViewDelegate, NSTableViewDataSource, NSPopoverDelegate, ContextMenuDelegate, ContactLinkedDelegate, KeyboardShortcutDelegate> {
Alexandre Lision0f66bd32016-01-18 11:30:45 -050050
Alexandre Lision61db3552015-10-22 19:12:52 -040051 NSPopover* addToContactPopover;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040052
53 //UI elements
Olivier Soldanod4311552017-11-20 15:09:53 -050054 __unsafe_unretained IBOutlet RingTableView* smartView;
Alexandre Lision61db3552015-10-22 19:12:52 -040055 __unsafe_unretained IBOutlet NSSearchField* searchField;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -050056 __strong IBOutlet NSSegmentedControl *listTypeSelector;
57 bool selectorIsPresent;
Alexandre Lision45c18672016-11-02 15:24:45 -040058
Andreas Traczyk3b98eb82018-03-29 17:54:42 -040059 QMetaObject::Connection modelSortedConnection_, modelUpdatedConnection_, filterChangedConnection_, newConversationConnection_, conversationRemovedConnection_, interactionStatusUpdatedConnection_, conversationClearedConnection;
Olivier Soldanod4311552017-11-20 15:09:53 -050060
61 lrc::api::ConversationModel* model_;
62 std::string selectedUid_;
Anthony Léonard4bfd68b2018-01-15 16:23:50 -050063 lrc::api::profile::Type currentFilterType;
Anthony Léonardbee94cc2018-01-16 11:42:40 -050064
65 __unsafe_unretained IBOutlet RingWindowController *delegate;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040066}
67
68@end
69
70@implementation SmartViewVC
71
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -040072@synthesize tabbar;
73
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040074// Tags for views
Alexandre Lision57914fa2016-02-10 14:52:24 -050075NSInteger const IMAGE_TAG = 100;
76NSInteger const DISPLAYNAME_TAG = 200;
77NSInteger const DETAILS_TAG = 300;
78NSInteger const CALL_BUTTON_TAG = 400;
79NSInteger const TXT_BUTTON_TAG = 500;
80NSInteger const CANCEL_BUTTON_TAG = 600;
Kateryna Kostiuk6a536362017-05-02 14:26:12 -040081NSInteger const RING_ID_LABEL = 700;
Kateryna Kostiukd9039e92017-05-24 14:29:54 -040082NSInteger const PRESENCE_TAG = 800;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040083
Anthony Léonarde6a01fa2018-01-12 10:29:58 -050084// Segment indices for smartlist selector
85NSInteger const CONVERSATION_SEG = 0;
86NSInteger const REQUEST_SEG = 1;
87
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040088- (void)awakeFromNib
89{
90 NSLog(@"INIT SmartView VC");
Olivier Soldanod4311552017-11-20 15:09:53 -050091 //get selected account
92 //encapsulate conversationmodel in local version
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040093
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040094 [smartView setTarget:self];
95 [smartView setDoubleAction:@selector(placeCall:)];
96
Alexandre Lision61db3552015-10-22 19:12:52 -040097 [smartView setContextMenuDelegate:self];
98 [smartView setShortcutsDelegate:self];
99
Olivier Soldanod4311552017-11-20 15:09:53 -0500100 [smartView setDataSource: self];
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -0400101
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400102 [self.view setWantsLayer:YES];
103 [self.view setLayer:[CALayer layer]];
104 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
105
106 [searchField setWantsLayer:YES];
107 [searchField setLayer:[CALayer layer]];
108 [searchField.layer setBackgroundColor:[NSColor colorWithCalibratedRed:0.949 green:0.949 blue:0.949 alpha:0.9].CGColor];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500109
110 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500111 selectorIsPresent = true;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400112}
113
114- (void)placeCall:(id)sender
115{
Olivier Soldanod4311552017-11-20 15:09:53 -0500116 NSInteger row;
117 if (sender != nil && [sender clickedRow] != -1)
118 row = [sender clickedRow];
119 else if ([smartView selectedRow] != -1)
120 row = [smartView selectedRow];
121 else
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400122 return;
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400123 if (model_ == nil)
124 return;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400125
Olivier Soldanod4311552017-11-20 15:09:53 -0500126 auto conv = model_->filteredConversation(row);
127 model_->placeCall(conv.uid);
128}
129
130-(void) reloadData
131{
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400132 NSLog(@"reload");
Olivier Soldanod4311552017-11-20 15:09:53 -0500133 [smartView deselectAll:nil];
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400134 if (model_ == nil)
135 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500136
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500137 if (!model_->owner.contactModel->hasPendingRequests()) {
138 if (currentFilterType == lrc::api::profile::Type::PENDING) {
139 [self selectConversationList];
140 }
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500141 if (selectorIsPresent) {
142 [listTypeSelector removeFromSuperview];
143 selectorIsPresent = false;
144 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500145 } else {
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500146 if (!selectorIsPresent) {
147 // First we restore the selector with selection on "Conversations"
148 [self.view addSubview:listTypeSelector];
149 [listTypeSelector setSelected:YES forSegment:CONVERSATION_SEG];
150
151 // Then constraints are recreated (as these are lost when calling removeFromSuperview)
152 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[searchField]-8-[listTypeSelector]"
153 options:0
154 metrics:nil
155 views:NSDictionaryOfVariableBindings(searchField, listTypeSelector)]];
156 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[listTypeSelector]-8-[tabbar]"
157 options:0
158 metrics:nil
159 views:NSDictionaryOfVariableBindings(listTypeSelector, tabbar)]];
160 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[listTypeSelector]-20-|"
161 options:0
162 metrics:nil
163 views:NSDictionaryOfVariableBindings(listTypeSelector)]];
164 selectorIsPresent = true;
165 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500166 }
167
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500168 [smartView reloadData];
169
Olivier Soldanod4311552017-11-20 15:09:53 -0500170 if (!selectedUid_.empty() && model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500171 auto it = getConversationFromUid(selectedUid_, *model_);
Olivier Soldanod4311552017-11-20 15:09:53 -0500172 if (it != model_->allFilteredConversations().end()) {
173 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
174 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400175 }
176 }
177
Olivier Soldanod4311552017-11-20 15:09:53 -0500178 [smartView scrollToBeginningOfDocument:nil];
179}
180
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400181-(void) reloadConversationWithUid:(NSString *)uid
182{
183 if (model_ != nil) {
184 auto it = getConversationFromUid(std::string([uid UTF8String]), *model_);
185 if (it != model_->allFilteredConversations().end()) {
186 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
187 NSLog(@"reloadConversationWithUid: %@", uid);
188 [smartView reloadDataForRowIndexes:indexSet
189 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
190 }
191 }
192}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400193
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500194- (BOOL)setConversationModel:(lrc::api::ConversationModel *)conversationModel
Olivier Soldanod4311552017-11-20 15:09:53 -0500195{
196 if (model_ != conversationModel) {
197 model_ = conversationModel;
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500198 selectedUid_.clear(); // Clear selected conversation as the selected account is being changed
Olivier Soldanod4311552017-11-20 15:09:53 -0500199 QObject::disconnect(modelSortedConnection_);
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400200 QObject::disconnect(modelUpdatedConnection_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500201 QObject::disconnect(filterChangedConnection_);
Anthony Léonard01634102017-12-27 16:37:16 -0500202 QObject::disconnect(newConversationConnection_);
203 QObject::disconnect(conversationRemovedConnection_);
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500204 QObject::disconnect(interactionStatusUpdatedConnection_);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400205 QObject::disconnect(conversationClearedConnection);
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500206 [self reloadData];
Olivier Soldanod4311552017-11-20 15:09:53 -0500207 if (model_ != nil) {
208 modelSortedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::modelSorted,
209 [self] (){
210 [self reloadData];
211 });
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400212 modelUpdatedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::conversationUpdated,
213 [self] (const std::string& uid){
214 [self reloadConversationWithUid: [NSString stringWithUTF8String:uid.c_str()]];
215 });
Olivier Soldanod4311552017-11-20 15:09:53 -0500216 filterChangedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::filterChanged,
217 [self] (){
218 [self reloadData];
219 });
Anthony Léonard01634102017-12-27 16:37:16 -0500220 newConversationConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::newConversation,
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400221 [self] (const std::string& uid) {
Anthony Léonard01634102017-12-27 16:37:16 -0500222 [self reloadData];
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400223 [self updateConversationForNewContact:[NSString stringWithUTF8String:uid.c_str()]];
Anthony Léonard01634102017-12-27 16:37:16 -0500224 });
225 conversationRemovedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::conversationRemoved,
226 [self] (){
227 [self reloadData];
228 });
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400229 conversationClearedConnection = QObject::connect(model_, &lrc::api::ConversationModel::conversationCleared,
230 [self] (const std::string& id){
231 [self deselect];
232 [delegate listTypeChanged];
233 });
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500234 interactionStatusUpdatedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::interactionStatusUpdated,
235 [self] (const std::string& convUid) {
236 if (convUid != selectedUid_)
237 return;
Andreas Traczyk46d53af2018-04-13 16:00:17 -0400238 [self reloadConversationWithUid: [NSString stringWithUTF8String:convUid.c_str()]];
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500239 });
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500240 model_->setFilter(""); // Reset the filter
241 }
242 [searchField setStringValue:@""];
243 return YES;
244 }
245 return NO;
246}
247
248-(void)selectConversation:(const lrc::api::conversation::Info&)conv model:(lrc::api::ConversationModel*)model;
249{
250 auto& uid = conv.uid;
251 if (selectedUid_ == uid)
252 return;
253
254 [self setConversationModel:model];
255
256 if (model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500257 auto it = getConversationFromUid(selectedUid_, *model_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500258 if (it != model_->allFilteredConversations().end()) {
259 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
260 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
261 selectedUid_ = uid;
Olivier Soldanod4311552017-11-20 15:09:53 -0500262 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400263 }
264}
265
Anthony Léonard8585cc02017-12-28 14:03:45 -0500266-(void)deselect
267{
268 selectedUid_.clear();
269 [smartView deselectAll:nil];
270}
271
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400272-(void) clearConversationModel {
273 model_ = nil;
274 [self deselect];
275 [smartView reloadData];
276}
277
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500278- (IBAction) listTypeChanged:(id)sender
279{
280 NSInteger selectedItem = [sender selectedSegment];
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500281 switch (selectedItem) {
282 case CONVERSATION_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500283 if (currentFilterType != lrc::api::profile::Type::RING) {
284 model_->setFilter(lrc::api::profile::Type::RING);
285 [delegate listTypeChanged];
286 currentFilterType = lrc::api::profile::Type::RING;
287 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500288 break;
289 case REQUEST_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500290 if (currentFilterType != lrc::api::profile::Type::PENDING) {
291 model_->setFilter(lrc::api::profile::Type::PENDING);
292 [delegate listTypeChanged];
293 currentFilterType = lrc::api::profile::Type::PENDING;
294 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500295 break;
296 default:
297 NSLog(@"Invalid item selected in list selector: %d", selectedItem);
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500298 }
299}
300
301-(void) selectConversationList
302{
Anthony Léonardd00cd182018-01-17 09:21:27 -0500303 if (currentFilterType == lrc::api::profile::Type::RING)
304 return;
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500305 [listTypeSelector setSelectedSegment:CONVERSATION_SEG];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500306
307 // Do not invert order of the next two lines or stack overflow
308 // may happen on -(void) reloadData call if filter is currently set to PENDING
309 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500310 model_->setFilter(lrc::api::profile::Type::RING);
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500311 model_->setFilter("");
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500312}
313
Anthony Léonardd00cd182018-01-17 09:21:27 -0500314-(void) selectPendingList
315{
316 if (currentFilterType == lrc::api::profile::Type::PENDING)
317 return;
318 [listTypeSelector setSelectedSegment:REQUEST_SEG];
319
320 currentFilterType = lrc::api::profile::Type::PENDING;
321 model_->setFilter(lrc::api::profile::Type::PENDING);
322 model_->setFilter("");
323}
324
Olivier Soldanod4311552017-11-20 15:09:53 -0500325#pragma mark - NSTableViewDelegate methods
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400326
Olivier Soldanod4311552017-11-20 15:09:53 -0500327- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400328{
329 return YES;
330}
331
Olivier Soldanod4311552017-11-20 15:09:53 -0500332- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400333{
334 return NO;
335}
336
Olivier Soldanod4311552017-11-20 15:09:53 -0500337- (void)tableViewSelectionDidChange:(NSNotification *)notification
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400338{
Olivier Soldanod4311552017-11-20 15:09:53 -0500339 NSInteger row = [notification.object selectedRow];
340
341 if (row == -1)
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400342 return;
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400343 if (model_ == nil)
344 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500345
346 auto uid = model_->filteredConversation(row).uid;
347 if (selectedUid_ != uid) {
348 selectedUid_ = uid;
349 model_->selectConversation(uid);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400350 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400351}
352
Olivier Soldanod4311552017-11-20 15:09:53 -0500353- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400354{
Olivier Soldanod4311552017-11-20 15:09:53 -0500355 return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
356}
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400357
Olivier Soldanod4311552017-11-20 15:09:53 -0500358- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
359{
360 if (model_ == nil)
361 return nil;
362
363 auto conversation = model_->filteredConversation(row);
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400364 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400365
Olivier Soldanod4311552017-11-20 15:09:53 -0500366 result = [tableView makeViewWithIdentifier:@"MainCell" owner:tableView];
367// NSTextField* details = [result viewWithTag:DETAILS_TAG];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400368
Olivier Soldanod4311552017-11-20 15:09:53 -0500369 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
370 [((ContextualTableCellView*) result) setContextualsControls:controls];
371 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision21666f32015-09-22 17:04:36 -0400372
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400373 // if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
374 // [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
375 // [((ContextualTableCellView*) result) setActiveState:YES];
376 // } else {
377 // [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
378 // [((ContextualTableCellView*) result) setActiveState:NO];
379 // }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500380
Olivier Soldanod4311552017-11-20 15:09:53 -0500381 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
382 [unreadCount setHidden:(conversation.unreadMessages == 0)];
383 [unreadCount setIntValue:conversation.unreadMessages];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400384
385 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500386 NSString* displayNameString = bestNameForConversation(conversation, *model_);
387 NSString* displayIDString = bestIDForConversation(conversation, *model_);
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400388 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400389 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400390 [displayName setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400391 [displayRingID setHidden:YES];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400392 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400393 else {
394 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
395 [displayName setStringValue:displayNameString];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400396 [displayRingID setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400397 [displayRingID setHidden:NO];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400398 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400399 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400400
Olivier Soldanod4311552017-11-20 15:09:53 -0500401 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
402 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(conversation, model_->owner)))];
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400403
404 NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500405 if (model_->owner.contactModel->getContact(conversation.participants[0]).isPresent) {
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400406 [presenceView setHidden:NO];
407 } else {
408 [presenceView setHidden:YES];
409 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400410 return result;
411}
412
Olivier Soldanod4311552017-11-20 15:09:53 -0500413- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500414{
Olivier Soldanod4311552017-11-20 15:09:53 -0500415 return 60.0;
416}
417
418#pragma mark - NSTableDataSource methods
419
420- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
421{
422 if (tableView == smartView) {
423 if (model_ != nullptr)
424 return model_->allFilteredConversations().size();
425 }
426
427 return 0;
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500428}
429
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -0400430- (void)startCallForRow:(id)sender {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400431 NSInteger row = [smartView rowForView:sender];
432 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
433 [self placeCall:nil];
434}
435
436- (IBAction)hangUpClickedAtRow:(id)sender {
437 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500438
Olivier Soldanod4311552017-11-20 15:09:53 -0500439 if (row == -1)
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500440 return;
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400441 if (model_ == nil)
442 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400443
Olivier Soldanod4311552017-11-20 15:09:53 -0500444 auto conv = model_->filteredConversation(row);
445 auto& callId = conv.callId;
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500446
Olivier Soldanod4311552017-11-20 15:09:53 -0500447 if (callId.empty())
448 return;
449
450 auto* callModel = model_->owner.callModel.get();
451 callModel->hangUp(callId);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400452}
453
Alexandre Lision45c18672016-11-02 15:24:45 -0400454- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
455{
456 NSAlert* alert = [NSAlert alertWithMessageText:title
457 defaultButton:@"Ok"
458 alternateButton:nil
459 otherButton:nil
460 informativeTextWithFormat:message];
461
462 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
463}
464
Olivier Soldanod4311552017-11-20 15:09:53 -0500465- (void) processSearchFieldInput
Alexandre Lision45c18672016-11-02 15:24:45 -0400466{
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400467 if (model_ == nil)
468 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500469 model_->setFilter(std::string([[searchField stringValue] UTF8String]));
Alexandre Lision45c18672016-11-02 15:24:45 -0400470}
471
Olivier Soldanod4311552017-11-20 15:09:53 -0500472-(const lrc::api::account::Info&) chosenAccount
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400473{
Olivier Soldanod4311552017-11-20 15:09:53 -0500474 return model_->owner;
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400475}
476
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400477- (void) clearSearchField
478{
479 [searchField setStringValue:@""];
Olivier Soldanod4311552017-11-20 15:09:53 -0500480 [self processSearchFieldInput];
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400481}
482
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400483-(void)updateConversationForNewContact:(NSString *)uId {
484 if (model_ == nil) {
485 return;
486 }
487 [self clearSearchField];
488 auto uid = std::string([uId UTF8String]);
489 auto it = getConversationFromUid(uid, *model_);
490 if (it != model_->allFilteredConversations().end()) {
491 @try {
492 auto contact = model_->owner.contactModel->getContact(it->participants[0]);
493 if (!contact.profileInfo.uri.empty() && contact.profileInfo.uri.compare(selectedUid_) == 0) {
494 selectedUid_ = uid;
495 model_->selectConversation(uid);
496 }
497 } @catch (NSException *exception) {
498 return;
499 }
500 }
501}
502
Alexandre Lision72a669e2016-09-14 17:52:16 -0400503/**
504 Copy a NSString in the general Pasteboard
505
506 @param sender the NSObject containing the represented object to copy
507 */
508- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400509{
510 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
511 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
512 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
513}
514
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400515#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400516
517- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
518{
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400519 if (commandSelector != @selector(insertNewline:) || [[searchField stringValue] isEqual:@""]) {
520 return NO;
521 }
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400522 if (model_ == nil) {
523 [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")];
524 return NO;
525 }
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400526 if (model_->allFilteredConversations().size() <= 0) {
527 return YES;
528 }
529 auto model = model_->filteredConversation(0);
530 auto uid = model.uid;
531 if (selectedUid_ == uid) {
532 return YES;
533 }
534 @try {
535 auto contact = model_->owner.contactModel->getContact(model.participants[0]);
536 if ((contact.profileInfo.uri.empty() && contact.profileInfo.type != lrc::api::profile::Type::SIP) || contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400537 return YES;
538 }
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400539 selectedUid_ = uid;
540 model_->selectConversation(uid);
541 [self.view.window makeFirstResponder: smartView];
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400542 return YES;
543 } @catch (NSException *exception) {
544 return YES;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400545 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400546}
547
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500548- (void)controlTextDidChange:(NSNotification *) notification
549{
Olivier Soldanod4311552017-11-20 15:09:53 -0500550 [self processSearchFieldInput];
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500551}
552
Alexandre Lision61db3552015-10-22 19:12:52 -0400553#pragma mark - NSPopOverDelegate
554
555- (void)popoverDidClose:(NSNotification *)notification
556{
557 if (addToContactPopover != nullptr) {
558 [addToContactPopover performClose:self];
559 addToContactPopover = NULL;
560 }
561}
562
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400563
Alexandre Lision61db3552015-10-22 19:12:52 -0400564#pragma mark - ContactLinkedDelegate
565
566- (void)contactLinked
567{
568 if (addToContactPopover != nullptr) {
569 [addToContactPopover performClose:self];
570 addToContactPopover = NULL;
571 }
572}
573
574#pragma mark - KeyboardShortcutDelegate
575
576- (void) onAddShortcut
577{
Olivier Soldanod4311552017-11-20 15:09:53 -0500578 if ([smartView selectedRow] == -1)
Alexandre Lision61db3552015-10-22 19:12:52 -0400579 return;
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400580 if (model_ == nil)
581 return ;
Alexandre Lision61db3552015-10-22 19:12:52 -0400582
Olivier Soldanod4311552017-11-20 15:09:53 -0500583 auto uid = model_->filteredConversation([smartView selectedRow]).uid;
584 model_->makePermanent(uid);
Alexandre Lision61db3552015-10-22 19:12:52 -0400585}
586
587#pragma mark - ContextMenuDelegate
588
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400589- (NSMenu*) contextualMenuForRow:(int) index
Alexandre Lision61db3552015-10-22 19:12:52 -0400590{
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400591 if (model_ == nil)
Alexandre Lision61db3552015-10-22 19:12:52 -0400592 return nil;
593
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400594 auto conversation = model_->filteredConversation(NSInteger(index));
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400595
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400596 @try {
597 auto contact = model_->owner.contactModel->getContact(conversation.participants[0]);
598 if (contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
599 return nil;
600 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400601
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400602 BOOL isSIP = false;
603 BOOL isRingContact = false;
604 /* for SIP contact show only call menu options
605 * if contact does not have uri that is not RING contact
606 * for trusted Ring contact show option block contact
607 * for untrasted contact show option add contact
608 */
Alexandre Lision72a669e2016-09-14 17:52:16 -0400609
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400610 if (contact.profileInfo.type == lrc::api::profile::Type::SIP) {
611 isSIP = true;
612 } else if (contact.profileInfo.uri.empty()) {
613 return nil;
614 }
615
616 else if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.isTrusted == true) {
617 isRingContact = true;
618 }
619 auto conversationUD = conversation.uid;
620 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
621 NSString* conversationUID = @(conversationUD.c_str());
622 NSMenuItem* separator = [NSMenuItem separatorItem];
623 NSMenuItem* videoCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place video call",
624 @"Contextual menu action")
625 action:@selector(videoCall:)
626 keyEquivalent:@""];
627 NSMenuItem* audioCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place audio call",
628 @"Contextual menu action")
629 action:@selector(audioCall:)
630 keyEquivalent:@""];
631 [videoCallItem setRepresentedObject: conversationUID];
632 [audioCallItem setRepresentedObject: conversationUID];
633 [theMenu addItem:videoCallItem];
634 [theMenu addItem:audioCallItem];
635 if (isSIP == false) {
636 [theMenu addItem:separator];
637 NSMenuItem* clearConversationItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Clear conversation", @"Contextual menu action")
638 action:@selector(clearConversation:)
639 keyEquivalent:@""];
640 [clearConversationItem setRepresentedObject: conversationUID];
641 [theMenu addItem:clearConversationItem];
642 if(isRingContact) {
643 NSMenuItem* blockContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Block contact", @"Contextual menu action")
644 action:@selector(blockContact:)
645 keyEquivalent:@""];
646 [blockContactItem setRepresentedObject: conversationUID];
647 [theMenu addItem:blockContactItem];
648 } else {
649 NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
650 action:@selector(addContact:)
651 keyEquivalent:@"A"];
652 [addContactItem setRepresentedObject: conversationUID];
653 [theMenu addItem:addContactItem];
654 }
655 }
656 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400657 }
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400658 @catch (NSException *exception) {
659 return nil;
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400660 }
Alexandre Lision61db3552015-10-22 19:12:52 -0400661}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400662
663- (void) addContact: (NSMenuItem* ) item {
664 auto menuObject = item.representedObject;
665 if(menuObject == nil) {
666 return;
667 }
668 NSString * convUId = (NSString*)menuObject;
669 std::string conversationID = std::string([convUId UTF8String]);
670 model_->makePermanent(conversationID);
671}
672
673- (void) blockContact: (NSMenuItem* ) item {
674 auto menuObject = item.representedObject;
675 if(menuObject == nil) {
676 return;
677 }
678 NSString * convUId = (NSString*)menuObject;
679 std::string conversationID = std::string([convUId UTF8String]);
680 model_->clearHistory(conversationID);
681 model_->removeConversation(conversationID, true);
682}
683
684- (void) audioCall: (NSMenuItem* ) item {
685 auto menuObject = item.representedObject;
686 if(menuObject == nil) {
687 return;
688 }
689 NSString * convUId = (NSString*)menuObject;
690 std::string conversationID = std::string([convUId UTF8String]);
691 model_->placeAudioOnlyCall(conversationID);
692
693}
694
695- (void) videoCall: (NSMenuItem* ) item {
696 auto menuObject = item.representedObject;
697 if(menuObject == nil) {
698 return;
699 }
700 NSString * convUId = (NSString*)menuObject;
701 std::string conversationID = std::string([convUId UTF8String]);
702 model_->placeCall(conversationID);
703}
704
705- (void) clearConversation:(NSMenuItem* ) item {
706 auto menuObject = item.representedObject;
707 if(menuObject == nil) {
708 return;
709 }
710 NSString * convUId = (NSString*)menuObject;
711 std::string conversationID = std::string([convUId UTF8String]);
712 model_->clearHistory(conversationID);
713}
Alexandre Lision61db3552015-10-22 19:12:52 -0400714
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400715@end