blob: 6b2518fbccba50fd66c168c0fd8a4cd6235396ab [file] [log] [blame]
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04001/*
Sébastien Blin029ffa82019-01-02 17:43:48 -05002 * Copyright (C) 2015-2019 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
Andreas Traczyk252a94a2018-04-20 16:36:20 -040024//std
25#import <sstream>
26
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040027//Qt
28#import <QtMacExtras/qmacfunctions.h>
29#import <QPixmap>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040030
31//LRC
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040032#import <globalinstances.h>
Olivier Soldanod4311552017-11-20 15:09:53 -050033#import <api/newaccountmodel.h>
34#import <api/conversationmodel.h>
35#import <api/account.h>
36#import <api/contact.h>
37#import <api/contactmodel.h>
38#import <api/newcallmodel.h>
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040039
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040040#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040041#import "views/HoverTableRowView.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050042#import "views/IconButton.h"
Olivier Soldanod4311552017-11-20 15:09:53 -050043#import "views/RingTableView.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040044#import "views/ContextualTableCellView.h"
Olivier Soldanod4311552017-11-20 15:09:53 -050045#import "utils.h"
Anthony Léonardbee94cc2018-01-16 11:42:40 -050046#import "RingWindowController.h"
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040047
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050048@interface SmartViewVC () <NSTableViewDelegate, NSTableViewDataSource, NSPopoverDelegate, ContextMenuDelegate, KeyboardShortcutDelegate> {
Alexandre Lision0f66bd32016-01-18 11:30:45 -050049
Alexandre Lision61db3552015-10-22 19:12:52 -040050 NSPopover* addToContactPopover;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040051
52 //UI elements
Olivier Soldanod4311552017-11-20 15:09:53 -050053 __unsafe_unretained IBOutlet RingTableView* smartView;
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -040054 __unsafe_unretained IBOutlet RingTableView* searchResultsView;
Alexandre Lision61db3552015-10-22 19:12:52 -040055 __unsafe_unretained IBOutlet NSSearchField* searchField;
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -040056 __unsafe_unretained IBOutlet NSTextField* searchStatus;
57 __unsafe_unretained IBOutlet NSBox* contactsHeader;
58 __unsafe_unretained IBOutlet NSBox* searchResultHeader;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -050059 __strong IBOutlet NSSegmentedControl *listTypeSelector;
Andreas Traczyk252a94a2018-04-20 16:36:20 -040060 __strong IBOutlet NSLayoutConstraint *listTypeSelectorHeight;
Kateryna Kostiukef66f972018-11-02 17:10:37 -040061 __strong IBOutlet NSLayoutConstraint *listTypeSelectorBottom;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -050062 bool selectorIsPresent;
Alexandre Lision45c18672016-11-02 15:24:45 -040063
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -040064 QMetaObject::Connection modelSortedConnection_, modelUpdatedConnection_, filterChangedConnection_, newConversationConnection_, conversationRemovedConnection_, newInteractionConnection_, interactionStatusUpdatedConnection_, conversationClearedConnection, searchStatusChangedConnection_,
65 searchResultUpdated_;
Olivier Soldanod4311552017-11-20 15:09:53 -050066
Andreas Traczyk252a94a2018-04-20 16:36:20 -040067 lrc::api::ConversationModel* convModel_;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040068 QString selectedUid_;
Anthony Léonard4bfd68b2018-01-15 16:23:50 -050069 lrc::api::profile::Type currentFilterType;
Anthony Léonardbee94cc2018-01-16 11:42:40 -050070
71 __unsafe_unretained IBOutlet RingWindowController *delegate;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040072}
73
74@end
75
76@implementation SmartViewVC
77
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -040078@synthesize tabbar;
79
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040080// Tags for views
Alexandre Lision57914fa2016-02-10 14:52:24 -050081NSInteger const IMAGE_TAG = 100;
82NSInteger const DISPLAYNAME_TAG = 200;
Andreas Traczyk252a94a2018-04-20 16:36:20 -040083NSInteger const NOTIFICATONS_TAG = 300;
Andreas Traczyk252a94a2018-04-20 16:36:20 -040084NSInteger const PRESENCE_TAG = 500;
85NSInteger const TOTALMSGS_TAG = 600;
86NSInteger const TOTALINVITES_TAG = 700;
87NSInteger const DATE_TAG = 800;
88NSInteger const SNIPPET_TAG = 900;
89NSInteger const ADD_BUTTON_TAG = 1000;
90NSInteger const REFUSE_BUTTON_TAG = 1100;
91NSInteger const BLOCK_BUTTON_TAG = 1200;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040092
Anthony Léonarde6a01fa2018-01-12 10:29:58 -050093// Segment indices for smartlist selector
94NSInteger const CONVERSATION_SEG = 0;
95NSInteger const REQUEST_SEG = 1;
96
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040097- (void)awakeFromNib
98{
99 NSLog(@"INIT SmartView VC");
Olivier Soldanod4311552017-11-20 15:09:53 -0500100 //get selected account
101 //encapsulate conversationmodel in local version
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400102
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400103 [smartView setTarget:self];
104 [smartView setDoubleAction:@selector(placeCall:)];
105
Alexandre Lision61db3552015-10-22 19:12:52 -0400106 [smartView setContextMenuDelegate:self];
107 [smartView setShortcutsDelegate:self];
108
Olivier Soldanod4311552017-11-20 15:09:53 -0500109 [smartView setDataSource: self];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500110 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500111 selectorIsPresent = true;
Kateryna Kostiukd508f752020-11-13 16:26:00 -0500112 NSFont *searchBarFont = [NSFont systemFontOfSize: 12.0 weight: NSFontWeightLight];
113 NSColor *color = [NSColor secondaryLabelColor];
114 NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
115 NSDictionary *searchBarAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
116 searchBarFont, NSFontAttributeName,
117 style, NSParagraphStyleAttributeName,
118 color, NSForegroundColorAttributeName,
119 nil];
120 NSAttributedString* attributedName = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search for new or existing contact", @"search bar placeholder") attributes: searchBarAttrs];
121 searchField.placeholderAttributedString = attributedName;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400122 smartView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleNone;
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400123
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400124 [searchResultsView setContextMenuDelegate:self];
125 [searchResultsView setShortcutsDelegate:self];
126 [searchResultsView setDoubleAction:@selector(placeCall:)];
127 searchResultsView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleNone;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400128}
129
130- (void)placeCall:(id)sender
131{
Olivier Soldanod4311552017-11-20 15:09:53 -0500132 NSInteger row;
133 if (sender != nil && [sender clickedRow] != -1)
134 row = [sender clickedRow];
135 else if ([smartView selectedRow] != -1)
136 row = [smartView selectedRow];
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400137 else if ([searchResultsView selectedRow] != -1)
138 row = [searchResultsView selectedRow];
Olivier Soldanod4311552017-11-20 15:09:53 -0500139 else
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400140 return;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400141 if (convModel_ == nil)
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400142 return;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400143
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400144 auto convOpt = sender == searchResultsView ? convModel_->searchResultForRow(row) : convModel_->filteredConversation(row);
145 if (!convOpt.has_value())
146 return;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400147 convModel_->placeCall(convOpt->get().uid);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400148}
149
150-(void) reloadSelectorNotifications
151{
152 NSTextField* totalMsgsCount = [self.view viewWithTag:TOTALMSGS_TAG];
153 NSTextField* totalInvites = [self.view viewWithTag:TOTALINVITES_TAG];
154
155 if (!selectorIsPresent) {
156 [totalMsgsCount setHidden:true];
157 [totalInvites setHidden:true];
158 return;
159 }
160
161 auto ringConversations = convModel_->getFilteredConversations(lrc::api::profile::Type::RING);
162 int totalUnreadMessages = 0;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400163 std::for_each(ringConversations.get().begin(), ringConversations.get().end(),
164 [&totalUnreadMessages, self] (const auto& conversation) {
165 totalUnreadMessages += convModel_->getNumberOfUnreadMessagesFor(conversation.get().uid);
166 });
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400167 [totalMsgsCount setHidden:(totalUnreadMessages == 0)];
168 [totalMsgsCount setIntValue:totalUnreadMessages];
169
170 auto totalRequests = [self chosenAccount].contactModel->pendingRequestCount();
171 [totalInvites setHidden:(totalRequests == 0)];
172 [totalInvites setIntValue:totalRequests];
Olivier Soldanod4311552017-11-20 15:09:53 -0500173}
174
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400175-(void) reloadSearchResults
176{
177 [searchResultHeader setHidden: convModel_->getAllSearchResults().size() == 0];
178 [searchResultsView reloadData];
179 [searchResultsView layoutSubtreeIfNeeded];
180}
181
182
Olivier Soldanod4311552017-11-20 15:09:53 -0500183-(void) reloadData
184{
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400185 [contactsHeader setHidden: convModel_->allFilteredConversations().get().empty() || searchField.stringValue.length == 0];
Olivier Soldanod4311552017-11-20 15:09:53 -0500186 [smartView deselectAll:nil];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400187 if (convModel_ == nil)
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400188 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500189
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400190 [self reloadSelectorNotifications];
191
192 if (!convModel_->owner.contactModel->hasPendingRequests()) {
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500193 if (currentFilterType == lrc::api::profile::Type::PENDING) {
194 [self selectConversationList];
195 }
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500196 if (selectorIsPresent) {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400197 listTypeSelectorHeight.constant = 0.0;
Kateryna Kostiukef66f972018-11-02 17:10:37 -0400198 listTypeSelectorBottom.priority = 250;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400199 [listTypeSelector setHidden:YES];
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500200 selectorIsPresent = false;
201 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500202 } else {
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500203 if (!selectorIsPresent) {
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500204 [listTypeSelector setSelected:YES forSegment:CONVERSATION_SEG];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400205 listTypeSelectorHeight.constant = 18.0;
Kateryna Kostiukef66f972018-11-02 17:10:37 -0400206 listTypeSelectorBottom.priority = 999;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400207 [listTypeSelector setHidden:NO];
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500208 selectorIsPresent = true;
209 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500210 }
211
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500212 [smartView reloadData];
Kateryna Kostiukc8035c62018-09-19 16:13:14 -0400213 [smartView layoutSubtreeIfNeeded];
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500214
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400215 if (!selectedUid_.isEmpty() && convModel_ != nil) {
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400216 auto index = getFilteredConversationIndexFromUid(selectedUid_, *convModel_);
217 if (index >= 0) {
218 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(index)];
Olivier Soldanod4311552017-11-20 15:09:53 -0500219 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400220 }
221 }
222
Olivier Soldanod4311552017-11-20 15:09:53 -0500223 [smartView scrollToBeginningOfDocument:nil];
224}
225
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400226-(void) reloadConversationWithUid:(NSString *)uid
227{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400228 if (convModel_ == nil) {
229 return;
230 }
231
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400232 auto index = getFilteredConversationIndexFromUid(QString::fromNSString(uid), *convModel_);
233 if (index >= 0) {
234 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(index)];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400235 [smartView reloadDataForRowIndexes:indexSet
236 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400237 return;
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400238 }
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400239 index = getSearchResultIndexFromUid(QString::fromNSString(uid), *convModel_);
240 if (index < 0) {
241 return;
242 }
243 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(index)];
244 [searchResultsView reloadDataForRowIndexes:indexSet
245 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400246}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400247
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400248-(void) reloadConversationWithURI:(NSString *)uri
249{
250 if (convModel_ == nil) {
251 return;
252 }
253 [smartView reloadData];
254}
255
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500256- (BOOL)setConversationModel:(lrc::api::ConversationModel *)conversationModel
Olivier Soldanod4311552017-11-20 15:09:53 -0500257{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400258 if (convModel_ == conversationModel) {
259 return false;
260 }
kkostiuk8c525872021-02-07 17:43:30 -0500261 [searchStatus setHidden: true];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400262
263 convModel_ = conversationModel;
264 selectedUid_.clear(); // Clear selected conversation as the selected account is being changed
265 QObject::disconnect(modelSortedConnection_);
266 QObject::disconnect(modelUpdatedConnection_);
267 QObject::disconnect(filterChangedConnection_);
268 QObject::disconnect(newConversationConnection_);
269 QObject::disconnect(conversationRemovedConnection_);
270 QObject::disconnect(conversationClearedConnection);
271 QObject::disconnect(interactionStatusUpdatedConnection_);
272 QObject::disconnect(newInteractionConnection_);
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400273 QObject::disconnect(searchStatusChangedConnection_);
274 QObject::disconnect(searchResultUpdated_);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400275 [self reloadData];
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400276 [self reloadSearchResults];
277
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400278 if (convModel_ != nil) {
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400279 modelSortedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelChanged,
Olivier Soldanod4311552017-11-20 15:09:53 -0500280 [self] (){
281 [self reloadData];
282 });
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400283 searchStatusChangedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::searchStatusChanged,
284 [self](const QString &status) {
285 [searchStatus setHidden:status.isEmpty()];
286 auto statusString = status.toNSString();
287 [searchStatus setStringValue: statusString];
288 });
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400289 modelUpdatedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400290 [self] (const QString& convUid){
291 [self reloadConversationWithUid: convUid.toNSString()];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400292 });
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400293 searchResultUpdated_ = QObject::connect(convModel_, &lrc::api::ConversationModel::searchResultUpdated,
294 [self] (){
295 [self reloadSearchResults];
296 });
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400297 filterChangedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
298 [self] (){
299 [self reloadData];
300 });
301 newConversationConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newConversation,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400302 [self] (const QString& convUid) {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400303 [self reloadData];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400304 [self updateConversationForNewContact:convUid.toNSString()];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400305 });
306 conversationRemovedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationRemoved,
307 [self] (){
308 [delegate listTypeChanged];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400309 });
310 conversationClearedConnection = QObject::connect(convModel_, &lrc::api::ConversationModel::conversationCleared,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400311 [self] (const QString& convUid){
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400312 [self deselect];
313 [delegate listTypeChanged];
314 });
315 interactionStatusUpdatedConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400316 [self] (const QString& convUid) {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400317 if (convUid != selectedUid_)
318 return;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400319 [self reloadConversationWithUid: convUid.toNSString()];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400320 });
321 newInteractionConnection_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400322 [self](const QString& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400323 if (convUid == selectedUid_) {
324 convModel_->clearUnreadInteractions(convUid);
325 }
326 });
327 convModel_->setFilter(""); // Reset the filter
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500328 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400329 [searchField setStringValue:@""];
330 return true;
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500331}
332
Kateryna Kostiuk66150982020-09-18 15:14:36 -0400333-(QString)getSelectedUID {
334 return selectedUid_;
335}
336
337
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500338-(void)selectConversation:(const lrc::api::conversation::Info&)conv model:(lrc::api::ConversationModel*)model;
339{
340 auto& uid = conv.uid;
341 if (selectedUid_ == uid)
342 return;
343
344 [self setConversationModel:model];
345
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400346 if (convModel_ == nil) {
347 return;
348 }
349
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400350 auto index = getFilteredConversationIndexFromUid(uid, *convModel_);
351 if (index >= 0) {
352 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:index];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400353 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
354 selectedUid_ = uid;
Kateryna Kostiuk3541ae22020-08-17 12:26:14 -0400355 } else {
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400356 index = getSearchResultIndexFromUid(uid, *convModel_);
357 if (index < 0) {
Kateryna Kostiuk3541ae22020-08-17 12:26:14 -0400358 return;
359 }
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400360 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(index)];
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400361 [searchResultsView selectRowIndexes:indexSet byExtendingSelection:NO];
362 selectedUid_ = uid;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400363 }
364}
365
Anthony Léonard8585cc02017-12-28 14:03:45 -0500366-(void)deselect
367{
368 selectedUid_.clear();
369 [smartView deselectAll:nil];
370}
371
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400372-(void) clearConversationModel {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400373 convModel_ = nil;
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400374 [self deselect];
375 [smartView reloadData];
Andreas Traczyk4fdb2d92018-04-19 15:57:46 -0400376 if (selectorIsPresent) {
377 [listTypeSelector removeFromSuperview];
378 selectorIsPresent = false;
379 }
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400380}
381
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500382- (IBAction) listTypeChanged:(id)sender
383{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400384 selectedUid_.clear();
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500385 NSInteger selectedItem = [sender selectedSegment];
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500386 switch (selectedItem) {
387 case CONVERSATION_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500388 if (currentFilterType != lrc::api::profile::Type::RING) {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400389 convModel_->setFilter(lrc::api::profile::Type::RING);
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500390 [delegate listTypeChanged];
391 currentFilterType = lrc::api::profile::Type::RING;
392 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500393 break;
394 case REQUEST_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500395 if (currentFilterType != lrc::api::profile::Type::PENDING) {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400396 convModel_->setFilter(lrc::api::profile::Type::PENDING);
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500397 [delegate listTypeChanged];
398 currentFilterType = lrc::api::profile::Type::PENDING;
399 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500400 break;
401 default:
402 NSLog(@"Invalid item selected in list selector: %d", selectedItem);
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500403 }
404}
405
406-(void) selectConversationList
407{
Anthony Léonardd00cd182018-01-17 09:21:27 -0500408 if (currentFilterType == lrc::api::profile::Type::RING)
409 return;
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500410 [listTypeSelector setSelectedSegment:CONVERSATION_SEG];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500411
412 // Do not invert order of the next two lines or stack overflow
413 // may happen on -(void) reloadData call if filter is currently set to PENDING
414 currentFilterType = lrc::api::profile::Type::RING;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400415 convModel_->setFilter(lrc::api::profile::Type::RING);
416 convModel_->setFilter("");
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500417}
418
Anthony Léonardd00cd182018-01-17 09:21:27 -0500419-(void) selectPendingList
420{
421 if (currentFilterType == lrc::api::profile::Type::PENDING)
422 return;
423 [listTypeSelector setSelectedSegment:REQUEST_SEG];
424
425 currentFilterType = lrc::api::profile::Type::PENDING;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400426 convModel_->setFilter(lrc::api::profile::Type::PENDING);
427 convModel_->setFilter("");
Anthony Léonardd00cd182018-01-17 09:21:27 -0500428}
429
Olivier Soldanod4311552017-11-20 15:09:53 -0500430#pragma mark - NSTableViewDelegate methods
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400431
Olivier Soldanod4311552017-11-20 15:09:53 -0500432- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400433{
434 return YES;
435}
436
Olivier Soldanod4311552017-11-20 15:09:53 -0500437- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400438{
439 return NO;
440}
441
Olivier Soldanod4311552017-11-20 15:09:53 -0500442- (void)tableViewSelectionDidChange:(NSNotification *)notification
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400443{
Olivier Soldanod4311552017-11-20 15:09:53 -0500444 NSInteger row = [notification.object selectedRow];
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400445 NSInteger rows = notification.object == smartView ? [smartView numberOfRows] : [searchResultsView numberOfRows];
Olivier Soldanod4311552017-11-20 15:09:53 -0500446
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400447 if (notification.object == smartView) {
448
449 for (int i = 0; i< rows; i++) {
450 HoverTableRowView* cellRowView = [smartView rowViewAtRow:i makeIfNecessary: NO];
451 [cellRowView drawSelection: (i == row)];
452 }
453
454 } else {
455 for (int i = 0; i< rows; i++) {
456 HoverTableRowView* cellRowView = [searchResultsView rowViewAtRow:i makeIfNecessary: NO];
457 [cellRowView drawSelection: (i == row)];
458 }
Kateryna Kostiuk82c0d532018-05-09 16:49:06 -0400459 }
Olivier Soldanod4311552017-11-20 15:09:53 -0500460 if (row == -1)
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400461 return;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400462 if (convModel_ == nil)
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400463 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500464
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400465 auto convOpt = notification.object == smartView ? convModel_->filteredConversation(row) : convModel_->searchResultForRow(row);
466 if (!convOpt.has_value())
467 return;
kkostiukf81c6372021-01-11 18:51:28 -0500468 lrc::api::conversation::Info& conversation = *convOpt;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400469 if (selectedUid_ != conversation.uid && !conversation.uid.isEmpty()) {
470 selectedUid_ = conversation.uid;
471 convModel_->selectConversation(selectedUid_);
472 convModel_->clearUnreadInteractions(selectedUid_);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400473 [self reloadSelectorNotifications];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400474 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400475}
476
Olivier Soldanod4311552017-11-20 15:09:53 -0500477- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400478{
Olivier Soldanod4311552017-11-20 15:09:53 -0500479 return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
480}
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400481
Olivier Soldanod4311552017-11-20 15:09:53 -0500482- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
483{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400484 if (convModel_ == nil)
Olivier Soldanod4311552017-11-20 15:09:53 -0500485 return nil;
486
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400487 bool isSearching = tableView == searchResultsView;
488
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400489 auto convOpt = isSearching ? convModel_->searchResultForRow(row) : convModel_->filteredConversation(row);
490 if (!convOpt.has_value())
491 return nil;
kkostiukf81c6372021-01-11 18:51:28 -0500492 lrc::api::conversation::Info& conversation = *convOpt;
kkostiuk3b4298f2021-02-07 21:45:43 -0500493 if (conversation.participants.size() == 0) {
494 return nil;
495 }
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400496 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400497
Olivier Soldanod4311552017-11-20 15:09:53 -0500498 result = [tableView makeViewWithIdentifier:@"MainCell" owner:tableView];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400499
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400500 NSTextField* unreadCount = [result viewWithTag:NOTIFICATONS_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500501 [unreadCount setHidden:(conversation.unreadMessages == 0)];
502 [unreadCount setIntValue:conversation.unreadMessages];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400503 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Kateryna Kostiuk0bf03752018-05-09 13:57:15 -0400504 NSTextField* lastInteractionDate = [result viewWithTag:DATE_TAG];
505 NSTextField* interactionSnippet = [result viewWithTag:SNIPPET_TAG];
506 [displayName setStringValue:@""];
Kateryna Kostiuk0bf03752018-05-09 13:57:15 -0400507 [lastInteractionDate setStringValue:@""];
508 [interactionSnippet setStringValue:@""];
509 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400510 NSString* displayNameString = bestNameForConversation(conversation, *convModel_);
511 NSString* displayIDString = bestIDForConversation(conversation, *convModel_);
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400512 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
513 [displayName setStringValue:displayIDString];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400514 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400515 else {
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400516 [displayName setStringValue:displayNameString];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400517 }
Kateryna Kostiukdc720842020-09-10 16:44:02 -0400518 @autoreleasepool {
519 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
520 NSImage* image = QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(conversation, convModel_->owner)));
521 if(image) {
522 [NSLayoutConstraint deactivateConstraints:[photoView constraints]];
523 NSArray* constraints = [NSLayoutConstraint
524 constraintsWithVisualFormat:@"H:[photoView(54)]"
525 options:0
526 metrics:nil views:NSDictionaryOfVariableBindings(photoView)];
527 [NSLayoutConstraint activateConstraints:constraints];
528 } else {
529 [NSLayoutConstraint deactivateConstraints:[photoView constraints]];
530 NSArray* constraints = [NSLayoutConstraint
531 constraintsWithVisualFormat:@"H:[photoView(0)]"
532 options:0
533 metrics:nil views:NSDictionaryOfVariableBindings(photoView)];
534 [NSLayoutConstraint activateConstraints:constraints];
535 }
536 [photoView setImage: image];
Kateryna Kostiuk0bf03752018-05-09 13:57:15 -0400537 }
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400538
539 NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
Andreas Traczyk4e39aa52018-05-11 17:29:26 -0400540 [presenceView setHidden:YES];
kkostiuk3b4298f2021-02-07 21:45:43 -0500541 try {
542 auto contact = convModel_->owner.contactModel->getContact(conversation.participants.front());
543 if (contact.isPresent) {
544 [presenceView setHidden:NO];
Andreas Traczyk4e39aa52018-05-11 17:29:26 -0400545 }
kkostiuk3b4298f2021-02-07 21:45:43 -0500546 } catch (std::out_of_range& e) {
547 NSLog(@"contact out of range");
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400548 }
Andreas Traczyk4e39aa52018-05-11 17:29:26 -0400549
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400550 NSButton* addContactButton = [result viewWithTag:ADD_BUTTON_TAG];
551 NSButton* refuseContactButton = [result viewWithTag:REFUSE_BUTTON_TAG];
552 NSButton* blockContactButton = [result viewWithTag:BLOCK_BUTTON_TAG];
553 [addContactButton setHidden:YES];
554 [refuseContactButton setHidden:YES];
555 [blockContactButton setHidden:YES];
556
557 if (profileType(conversation, *convModel_) == lrc::api::profile::Type::PENDING) {
558 [lastInteractionDate setHidden:true];
559 [interactionSnippet setHidden:true];
560 [addContactButton setHidden:NO];
561 [refuseContactButton setHidden:NO];
562 [blockContactButton setHidden:NO];
563 [addContactButton setAction:@selector(acceptInvitation:)];
564 [addContactButton setTarget:self];
565 [refuseContactButton setAction:@selector(refuseInvitation:)];
566 [refuseContactButton setTarget:self];
567 [blockContactButton setAction:@selector(blockPendingContact:)];
568 [blockContactButton setTarget:self];
569 return result;
570 }
571
572 [lastInteractionDate setHidden:false];
573
574 [interactionSnippet setHidden:false];
575
576 auto lastUid = conversation.lastMessageUid;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400577 auto callId = conversation.confId.isEmpty() ? conversation.callId : conversation.confId;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400578 NSString *callInfo = @"";
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400579 if (!callId.isEmpty()) {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400580 if ([self chosenAccount].callModel.get()->hasCall(callId)) {
581 auto call = [self chosenAccount].callModel.get()->getCall(callId);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400582 callInfo = (call.status == lrc::api::call::Status::IN_PROGRESS) ? @"Talking" : to_string(call.status).toNSString();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400583 }
584 }
585
586 if (callInfo.length > 0) {
587 [lastInteractionDate setStringValue: callInfo];
588 [interactionSnippet setHidden:true];
589 return result;
590 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400591 if (conversation.interactions.find(lastUid) != conversation.interactions.end()) {
592 // last interaction snippet
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400593 auto lastInteractionSnippet = conversation.interactions[lastUid].body.trimmed().replace("\r","").replace("\n","");
594 NSString* lastInteractionSnippetFixedString = [lastInteractionSnippet.toNSString()
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400595 stringByReplacingOccurrencesOfString:@"🕽" withString:@""];
596 lastInteractionSnippetFixedString = [lastInteractionSnippetFixedString stringByReplacingOccurrencesOfString:@"📞" withString:@""];
Kateryna Kostiuk209a6302019-08-14 16:46:21 -0400597 if (conversation.interactions[lastUid].type == lrc::api::interaction::Type::DATA_TRANSFER) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400598 lastInteractionSnippetFixedString = [lastInteractionSnippetFixedString lastPathComponent];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400599 }
600 [interactionSnippet setStringValue:lastInteractionSnippetFixedString];
601
602 // last interaction date/time
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500603 NSString *timeString = @"";
604 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:conversation.interactions[lastUid].timestamp];
605 NSDate *today = [NSDate date];
606 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
607 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
608 if ([[NSCalendar currentCalendar] compareDate:today
609 toDate:msgTime
610 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
611 timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterNoStyle];
612 } else if ([[NSCalendar currentCalendar] compareDate:today
613 toDate:msgTime
614 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
615 [[NSCalendar currentCalendar] compareDate:today
616 toDate:msgTime
617 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
618 timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterNoStyle];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400619 } else {
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500620 timeString = [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterShortStyle];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400621 }
Kateryna Kostiuk66406432019-11-09 17:20:34 -0500622 [lastInteractionDate setStringValue:timeString];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400623 }
624
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400625 return result;
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400626
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400627}
628
Olivier Soldanod4311552017-11-20 15:09:53 -0500629- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500630{
Olivier Soldanod4311552017-11-20 15:09:53 -0500631 return 60.0;
632}
633
634#pragma mark - NSTableDataSource methods
635
636- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
637{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400638 if (tableView == smartView && convModel_ != nullptr) {
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400639 bool hideSmartView = convModel_->getAllSearchResults().size() > 0 && convModel_->allFilteredConversations().size() == 0;
640 [[[smartView superview] superview] setHidden: hideSmartView];
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400641 return convModel_->allFilteredConversations().size();
Olivier Soldanod4311552017-11-20 15:09:53 -0500642 }
643
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400644 if (tableView == searchResultsView && convModel_ != nullptr) {
645 [[[searchResultsView superview] superview] setHidden: convModel_->getAllSearchResults().size() == 0];
646 return convModel_->getAllSearchResults().size();
647 }
Olivier Soldanod4311552017-11-20 15:09:53 -0500648 return 0;
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500649}
650
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -0400651- (void)startCallForRow:(id)sender {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400652 NSInteger row = [smartView rowForView:sender];
653 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
654 [self placeCall:nil];
655}
656
Alexandre Lision45c18672016-11-02 15:24:45 -0400657- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
658{
659 NSAlert* alert = [NSAlert alertWithMessageText:title
660 defaultButton:@"Ok"
661 alternateButton:nil
662 otherButton:nil
663 informativeTextWithFormat:message];
664
665 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
666}
667
Olivier Soldanod4311552017-11-20 15:09:53 -0500668- (void) processSearchFieldInput
Alexandre Lision45c18672016-11-02 15:24:45 -0400669{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400670 if (convModel_ == nil) {
671 return;
672 }
673
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400674 convModel_->setFilter(QString::fromNSString([searchField stringValue]));
Alexandre Lision45c18672016-11-02 15:24:45 -0400675}
676
Olivier Soldanod4311552017-11-20 15:09:53 -0500677-(const lrc::api::account::Info&) chosenAccount
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400678{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400679 return convModel_->owner;
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400680}
681
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400682- (void) clearSearchField
683{
684 [searchField setStringValue:@""];
Olivier Soldanod4311552017-11-20 15:09:53 -0500685 [self processSearchFieldInput];
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400686}
687
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400688-(void)updateConversationForNewContact:(NSString *)uId {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400689 if (convModel_ == nil) {
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400690 return;
691 }
692 [self clearSearchField];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400693 auto uid = QString::fromNSString(uId);
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400694 auto convOpt = getConversationFromUid(uid, *convModel_);
695 if (!convOpt.has_value())
696 return;
kkostiukf81c6372021-01-11 18:51:28 -0500697 lrc::api::conversation::Info& conversation = *convOpt;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400698 @try {
699 auto contact = convModel_->owner.contactModel->getContact(conversation.participants[0]);
700 if (!contact.profileInfo.uri.isEmpty() && contact.profileInfo.uri.compare(selectedUid_) == 0) {
701 selectedUid_ = uid;
702 convModel_->selectConversation(uid);
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400703 }
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400704 } @catch (NSException *exception) {
705 return;
Kateryna Kostiuk22ce9362018-04-17 13:12:50 -0400706 }
707}
708
Alexandre Lision72a669e2016-09-14 17:52:16 -0400709/**
710 Copy a NSString in the general Pasteboard
711
712 @param sender the NSObject containing the represented object to copy
713 */
714- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400715{
716 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
717 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
718 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
719}
720
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400721#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400722
723- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
724{
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400725 if (commandSelector != @selector(insertNewline:) || [[searchField stringValue] isEqual:@""]) {
726 return NO;
727 }
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400728 if (convModel_ == nil) {
Kateryna Kostiukab499f42018-04-16 12:27:33 -0400729 [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")];
730 return NO;
731 }
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400732 if (convModel_->getAllSearchResults().size() <= 0 && convModel_->allFilteredConversations().size() <= 0) {
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400733 return YES;
734 }
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400735 bool hasSearchResult = convModel_->getAllSearchResults().size() > 0;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400736 auto convOpt = hasSearchResult ? convModel_->searchResultForRow(0) : convModel_->filteredConversation(0);
737 if (!convOpt.has_value())
738 return NO;
kkostiukf81c6372021-01-11 18:51:28 -0500739 lrc::api::conversation::Info& conversation = *convOpt;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400740 auto uid = conversation.uid;
741
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400742 if (selectedUid_ == uid) {
743 return YES;
744 }
745 @try {
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400746 auto contact = convModel_->owner.contactModel->getContact(conversation.participants[0]);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400747 if ((contact.profileInfo.uri.isEmpty() && contact.profileInfo.type != lrc::api::profile::Type::SIP) || contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400748 return YES;
749 }
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400750 selectedUid_ = uid;
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400751 convModel_->selectConversation(uid);
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400752 [self.view.window makeFirstResponder: smartView];
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400753 return YES;
754 } @catch (NSException *exception) {
755 return YES;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400756 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400757}
758
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500759- (void)controlTextDidChange:(NSNotification *) notification
760{
Olivier Soldanod4311552017-11-20 15:09:53 -0500761 [self processSearchFieldInput];
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500762}
763
Alexandre Lision61db3552015-10-22 19:12:52 -0400764#pragma mark - NSPopOverDelegate
765
766- (void)popoverDidClose:(NSNotification *)notification
767{
768 if (addToContactPopover != nullptr) {
769 [addToContactPopover performClose:self];
770 addToContactPopover = NULL;
771 }
772}
773
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400774
Alexandre Lision61db3552015-10-22 19:12:52 -0400775#pragma mark - ContactLinkedDelegate
776
777- (void)contactLinked
778{
779 if (addToContactPopover != nullptr) {
780 [addToContactPopover performClose:self];
781 addToContactPopover = NULL;
782 }
783}
784
785#pragma mark - KeyboardShortcutDelegate
786
787- (void) onAddShortcut
788{
Olivier Soldanod4311552017-11-20 15:09:53 -0500789 if ([smartView selectedRow] == -1)
Alexandre Lision61db3552015-10-22 19:12:52 -0400790 return;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400791 auto convID = [self conversationUIDFrom: [smartView selectedRow]];
792 if (convID.isEmpty())
793 return;
794 convModel_->makePermanent(convID);
Alexandre Lision61db3552015-10-22 19:12:52 -0400795}
796
797#pragma mark - ContextMenuDelegate
798
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400799- (NSMenu*) contextualMenuForRow:(int) index table:(NSTableView*) table
Alexandre Lision61db3552015-10-22 19:12:52 -0400800{
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400801 if (convModel_ == nil)
Alexandre Lision61db3552015-10-22 19:12:52 -0400802 return nil;
803
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400804 auto convOpt = table == smartView ? convModel_->filteredConversation(NSInteger(index)) :
Kateryna Kostiuk146cb9e2020-08-11 14:29:26 -0400805 convModel_->searchResultForRow(NSInteger(index));
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400806 if (!convOpt.has_value())
807 return nil;
kkostiukf81c6372021-01-11 18:51:28 -0500808 lrc::api::conversation::Info& conversation = *convOpt;
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400809
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400810 @try {
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400811 auto contact = convModel_->owner.contactModel->getContact(conversation.participants[0]);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400812 if (contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
813 return nil;
814 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400815
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400816 BOOL isSIP = false;
817 BOOL isRingContact = false;
818 /* for SIP contact show only call menu options
819 * if contact does not have uri that is not RING contact
820 * for trusted Ring contact show option block contact
821 * for untrasted contact show option add contact
822 */
Alexandre Lision72a669e2016-09-14 17:52:16 -0400823
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400824 if (contact.profileInfo.type == lrc::api::profile::Type::SIP) {
825 isSIP = true;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400826 } else if (contact.profileInfo.uri.isEmpty()) {
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400827 return nil;
828 }
829
830 else if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.isTrusted == true) {
831 isRingContact = true;
832 }
833 auto conversationUD = conversation.uid;
834 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400835 NSString* conversationUID = conversationUD.toNSString();
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400836 NSMenuItem* separator = [NSMenuItem separatorItem];
837 NSMenuItem* videoCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place video call",
838 @"Contextual menu action")
839 action:@selector(videoCall:)
840 keyEquivalent:@""];
841 NSMenuItem* audioCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place audio call",
842 @"Contextual menu action")
843 action:@selector(audioCall:)
844 keyEquivalent:@""];
845 [videoCallItem setRepresentedObject: conversationUID];
846 [audioCallItem setRepresentedObject: conversationUID];
847 [theMenu addItem:videoCallItem];
848 [theMenu addItem:audioCallItem];
Kateryna Kostiukc808b902020-08-11 21:40:59 -0400849 [theMenu addItem:separator];
850 NSMenuItem* clearConversationItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Clear conversation", @"Contextual menu action")
851 action:@selector(clearConversation:)
852 keyEquivalent:@""];
853 [clearConversationItem setRepresentedObject: conversationUID];
854 [theMenu addItem:clearConversationItem];
855 NSMenuItem* removeContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Remove conversation", @"Contextual menu action")
856 action:@selector(removeContact:)
857 keyEquivalent:@""];
858 [removeContactItem setRepresentedObject: conversationUID];
859 [theMenu addItem:removeContactItem];
860 if(isRingContact) {
861 NSMenuItem* blockContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Block contact", @"Contextual menu action")
862 action:@selector(blockContact:)
863 keyEquivalent:@""];
864 [blockContactItem setRepresentedObject: conversationUID];
865 [theMenu addItem:blockContactItem];
866 } else if (isSIP == false) {
867 NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
868 action:@selector(addContact:)
869 keyEquivalent:@"A"];
870 [addContactItem setRepresentedObject: conversationUID];
871 [theMenu addItem:addContactItem];
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400872 }
873 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400874 }
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400875 @catch (NSException *exception) {
876 return nil;
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400877 }
Alexandre Lision61db3552015-10-22 19:12:52 -0400878}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400879
880- (void) addContact: (NSMenuItem* ) item {
881 auto menuObject = item.representedObject;
882 if(menuObject == nil) {
883 return;
884 }
885 NSString * convUId = (NSString*)menuObject;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400886 QString conversationID = QString::fromNSString(convUId);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400887 convModel_->makePermanent(conversationID);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400888}
889
890- (void) blockContact: (NSMenuItem* ) item {
891 auto menuObject = item.representedObject;
892 if(menuObject == nil) {
893 return;
894 }
895 NSString * convUId = (NSString*)menuObject;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400896 QString conversationID = QString::fromNSString(convUId);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400897 convModel_->removeConversation(conversationID, true);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400898}
899
Kateryna Kostiukc808b902020-08-11 21:40:59 -0400900- (void) removeContact: (NSMenuItem* ) item {
901 auto menuObject = item.representedObject;
902 if(menuObject == nil) {
903 return;
904 }
905 NSString * convUId = (NSString*)menuObject;
906 QString conversationID = QString::fromNSString(convUId);
907 convModel_->removeConversation(conversationID, false);
908}
909
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400910- (void) audioCall: (NSMenuItem* ) item {
911 auto menuObject = item.representedObject;
912 if(menuObject == nil) {
913 return;
914 }
915 NSString * convUId = (NSString*)menuObject;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400916 QString conversationID = QString::fromNSString(convUId);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400917 convModel_->placeAudioOnlyCall(conversationID);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400918
919}
920
921- (void) videoCall: (NSMenuItem* ) item {
922 auto menuObject = item.representedObject;
923 if(menuObject == nil) {
924 return;
925 }
926 NSString * convUId = (NSString*)menuObject;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400927 QString conversationID = QString::fromNSString(convUId);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400928 convModel_->placeCall(conversationID);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400929}
930
931- (void) clearConversation:(NSMenuItem* ) item {
932 auto menuObject = item.representedObject;
933 if(menuObject == nil) {
934 return;
935 }
936 NSString * convUId = (NSString*)menuObject;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400937 QString conversationID = QString::fromNSString(convUId);
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400938 convModel_->clearHistory(conversationID);
939}
940
941- (void)acceptInvitation:(id)sender {
942 NSInteger row = [smartView rowForView:sender];
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400943 auto convID = [self conversationUIDFrom: row];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400944 if (convID.isEmpty())
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400945 return;
946 convModel_->makePermanent(convID);
947}
948
949- (void)refuseInvitation:(id)sender {
950 NSInteger row = [smartView rowForView:sender];
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400951 auto convID = [self conversationUIDFrom: row];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400952 if (convID.isEmpty())
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400953 return;
954 convModel_->removeConversation(convID);
955}
956
957- (void)blockPendingContact:(id)sender {
958 NSInteger row = [smartView rowForView:sender];
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400959 auto convID = [self conversationUIDFrom: row];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400960 if (convID.isEmpty())
Andreas Traczyk252a94a2018-04-20 16:36:20 -0400961 return;
962 convModel_->removeConversation(convID, true);
963 [self deselect];
964 [delegate listTypeChanged];
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400965}
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400966// return convUid for given row if exists. Otherwise return an empty string
967- (QString)conversationUIDFrom:(int)row {
968 if (row == -1)
969 return "";
970 if (convModel_ == nil)
971 return "";
972 auto convRef = convModel_->filteredConversation(row);
973 if (!convRef.has_value())
974 return "";
kkostiukf81c6372021-01-11 18:51:28 -0500975 lrc::api::conversation::Info& conversation = *convRef;
Kateryna Kostiuka7b909c2020-10-19 11:46:26 -0400976 auto& convID = conversation.Info::uid;
977 return convID;
978}
Alexandre Lision61db3552015-10-22 19:12:52 -0400979
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400980@end