blob: 776c81cdf566ccc45d3a7f8fa6922b8a7e743e2c [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;
Anthony Léonarddd85dee2018-02-19 14:34:35 -050060 NSTimer* statusUpdateDebounceTimer;
Olivier Soldanod4311552017-11-20 15:09:53 -050061
62 lrc::api::ConversationModel* model_;
63 std::string selectedUid_;
Anthony Léonard4bfd68b2018-01-15 16:23:50 -050064 lrc::api::profile::Type currentFilterType;
Anthony Léonardbee94cc2018-01-16 11:42:40 -050065
66 __unsafe_unretained IBOutlet RingWindowController *delegate;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040067}
68
69@end
70
71@implementation SmartViewVC
72
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -040073@synthesize tabbar;
74
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040075// Tags for views
Alexandre Lision57914fa2016-02-10 14:52:24 -050076NSInteger const IMAGE_TAG = 100;
77NSInteger const DISPLAYNAME_TAG = 200;
78NSInteger const DETAILS_TAG = 300;
79NSInteger const CALL_BUTTON_TAG = 400;
80NSInteger const TXT_BUTTON_TAG = 500;
81NSInteger const CANCEL_BUTTON_TAG = 600;
Kateryna Kostiuk6a536362017-05-02 14:26:12 -040082NSInteger const RING_ID_LABEL = 700;
Kateryna Kostiukd9039e92017-05-24 14:29:54 -040083NSInteger const PRESENCE_TAG = 800;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040084
Anthony Léonarde6a01fa2018-01-12 10:29:58 -050085// Segment indices for smartlist selector
86NSInteger const CONVERSATION_SEG = 0;
87NSInteger const REQUEST_SEG = 1;
88
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040089- (void)awakeFromNib
90{
91 NSLog(@"INIT SmartView VC");
Olivier Soldanod4311552017-11-20 15:09:53 -050092 //get selected account
93 //encapsulate conversationmodel in local version
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040094
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040095 [smartView setTarget:self];
96 [smartView setDoubleAction:@selector(placeCall:)];
97
Alexandre Lision61db3552015-10-22 19:12:52 -040098 [smartView setContextMenuDelegate:self];
99 [smartView setShortcutsDelegate:self];
100
Olivier Soldanod4311552017-11-20 15:09:53 -0500101 [smartView setDataSource: self];
Kateryna Kostiuk1f705ab2017-04-18 12:45:59 -0400102
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400103 [self.view setWantsLayer:YES];
104 [self.view setLayer:[CALayer layer]];
105 [self.view.layer setBackgroundColor:[NSColor whiteColor].CGColor];
106
107 [searchField setWantsLayer:YES];
108 [searchField setLayer:[CALayer layer]];
109 [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 -0500110
111 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500112 selectorIsPresent = true;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400113}
114
115- (void)placeCall:(id)sender
116{
Olivier Soldanod4311552017-11-20 15:09:53 -0500117 NSInteger row;
118 if (sender != nil && [sender clickedRow] != -1)
119 row = [sender clickedRow];
120 else if ([smartView selectedRow] != -1)
121 row = [smartView selectedRow];
122 else
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400123 return;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400124
Olivier Soldanod4311552017-11-20 15:09:53 -0500125 auto conv = model_->filteredConversation(row);
126 model_->placeCall(conv.uid);
127}
128
129-(void) reloadData
130{
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400131 NSLog(@"reload");
Olivier Soldanod4311552017-11-20 15:09:53 -0500132 [smartView deselectAll:nil];
Olivier Soldanod4311552017-11-20 15:09:53 -0500133
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500134 if (!model_->owner.contactModel->hasPendingRequests()) {
135 if (currentFilterType == lrc::api::profile::Type::PENDING) {
136 [self selectConversationList];
137 }
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500138 if (selectorIsPresent) {
139 [listTypeSelector removeFromSuperview];
140 selectorIsPresent = false;
141 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500142 } else {
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500143 if (!selectorIsPresent) {
144 // First we restore the selector with selection on "Conversations"
145 [self.view addSubview:listTypeSelector];
146 [listTypeSelector setSelected:YES forSegment:CONVERSATION_SEG];
147
148 // Then constraints are recreated (as these are lost when calling removeFromSuperview)
149 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[searchField]-8-[listTypeSelector]"
150 options:0
151 metrics:nil
152 views:NSDictionaryOfVariableBindings(searchField, listTypeSelector)]];
153 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[listTypeSelector]-8-[tabbar]"
154 options:0
155 metrics:nil
156 views:NSDictionaryOfVariableBindings(listTypeSelector, tabbar)]];
157 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[listTypeSelector]-20-|"
158 options:0
159 metrics:nil
160 views:NSDictionaryOfVariableBindings(listTypeSelector)]];
161 selectorIsPresent = true;
162 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500163 }
164
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500165 [smartView reloadData];
166
Olivier Soldanod4311552017-11-20 15:09:53 -0500167 if (!selectedUid_.empty() && model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500168 auto it = getConversationFromUid(selectedUid_, *model_);
Olivier Soldanod4311552017-11-20 15:09:53 -0500169 if (it != model_->allFilteredConversations().end()) {
170 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
171 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
172 } else {
173 selectedUid_.clear();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400174 }
175 }
176
Olivier Soldanod4311552017-11-20 15:09:53 -0500177 [smartView scrollToBeginningOfDocument:nil];
178}
179
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400180-(void) reloadConversationWithUid:(NSString *)uid
181{
182 if (model_ != nil) {
183 auto it = getConversationFromUid(std::string([uid UTF8String]), *model_);
184 if (it != model_->allFilteredConversations().end()) {
185 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
186 NSLog(@"reloadConversationWithUid: %@", uid);
187 [smartView reloadDataForRowIndexes:indexSet
188 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
189 }
190 }
191}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400192
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500193- (BOOL)setConversationModel:(lrc::api::ConversationModel *)conversationModel
Olivier Soldanod4311552017-11-20 15:09:53 -0500194{
195 if (model_ != conversationModel) {
196 model_ = conversationModel;
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500197 selectedUid_.clear(); // Clear selected conversation as the selected account is being changed
Olivier Soldanod4311552017-11-20 15:09:53 -0500198 QObject::disconnect(modelSortedConnection_);
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400199 QObject::disconnect(modelUpdatedConnection_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500200 QObject::disconnect(filterChangedConnection_);
Anthony Léonard01634102017-12-27 16:37:16 -0500201 QObject::disconnect(newConversationConnection_);
202 QObject::disconnect(conversationRemovedConnection_);
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500203 QObject::disconnect(interactionStatusUpdatedConnection_);
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400204 QObject::disconnect(conversationClearedConnection);
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500205 [self reloadData];
Olivier Soldanod4311552017-11-20 15:09:53 -0500206 if (model_ != nil) {
207 modelSortedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::modelSorted,
208 [self] (){
209 [self reloadData];
210 });
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400211 modelUpdatedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::conversationUpdated,
212 [self] (const std::string& uid){
213 [self reloadConversationWithUid: [NSString stringWithUTF8String:uid.c_str()]];
214 });
Olivier Soldanod4311552017-11-20 15:09:53 -0500215 filterChangedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::filterChanged,
216 [self] (){
217 [self reloadData];
218 });
Anthony Léonard01634102017-12-27 16:37:16 -0500219 newConversationConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::newConversation,
220 [self] (){
221 [self reloadData];
222 });
223 conversationRemovedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::conversationRemoved,
224 [self] (){
225 [self reloadData];
226 });
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400227 conversationClearedConnection = QObject::connect(model_, &lrc::api::ConversationModel::conversationCleared,
228 [self] (const std::string& id){
229 [self deselect];
230 [delegate listTypeChanged];
231 });
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500232 interactionStatusUpdatedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::interactionStatusUpdated,
233 [self] (const std::string& convUid) {
234 if (convUid != selectedUid_)
235 return;
236 auto it = getConversationFromUid(selectedUid_, *model_);
237 if (it != model_->allFilteredConversations().end()) {
238 // The following mechanism is here to debounce the interactionStatusUpdated so
239 // we do not redraw the conversation list for each message status changing
240 if (statusUpdateDebounceTimer != nil) {
241 [statusUpdateDebounceTimer invalidate];
242 }
243 statusUpdateDebounceTimer = [NSTimer timerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
244 [self reloadData];
245 }];
246 [[NSRunLoop mainRunLoop] addTimer:statusUpdateDebounceTimer forMode:NSRunLoopCommonModes];
247 }
248 });
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500249 model_->setFilter(""); // Reset the filter
250 }
251 [searchField setStringValue:@""];
252 return YES;
253 }
254 return NO;
255}
256
257-(void)selectConversation:(const lrc::api::conversation::Info&)conv model:(lrc::api::ConversationModel*)model;
258{
259 auto& uid = conv.uid;
260 if (selectedUid_ == uid)
261 return;
262
263 [self setConversationModel:model];
264
265 if (model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500266 auto it = getConversationFromUid(selectedUid_, *model_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500267 if (it != model_->allFilteredConversations().end()) {
268 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
269 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
270 selectedUid_ = uid;
Olivier Soldanod4311552017-11-20 15:09:53 -0500271 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400272 }
273}
274
Anthony Léonard8585cc02017-12-28 14:03:45 -0500275-(void)deselect
276{
277 selectedUid_.clear();
278 [smartView deselectAll:nil];
279}
280
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500281- (IBAction) listTypeChanged:(id)sender
282{
283 NSInteger selectedItem = [sender selectedSegment];
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500284 switch (selectedItem) {
285 case CONVERSATION_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500286 if (currentFilterType != lrc::api::profile::Type::RING) {
287 model_->setFilter(lrc::api::profile::Type::RING);
288 [delegate listTypeChanged];
289 currentFilterType = lrc::api::profile::Type::RING;
290 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500291 break;
292 case REQUEST_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500293 if (currentFilterType != lrc::api::profile::Type::PENDING) {
294 model_->setFilter(lrc::api::profile::Type::PENDING);
295 [delegate listTypeChanged];
296 currentFilterType = lrc::api::profile::Type::PENDING;
297 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500298 break;
299 default:
300 NSLog(@"Invalid item selected in list selector: %d", selectedItem);
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500301 }
302}
303
304-(void) selectConversationList
305{
Anthony Léonardd00cd182018-01-17 09:21:27 -0500306 if (currentFilterType == lrc::api::profile::Type::RING)
307 return;
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500308 [listTypeSelector setSelectedSegment:CONVERSATION_SEG];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500309
310 // Do not invert order of the next two lines or stack overflow
311 // may happen on -(void) reloadData call if filter is currently set to PENDING
312 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500313 model_->setFilter(lrc::api::profile::Type::RING);
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500314 model_->setFilter("");
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500315}
316
Anthony Léonardd00cd182018-01-17 09:21:27 -0500317-(void) selectPendingList
318{
319 if (currentFilterType == lrc::api::profile::Type::PENDING)
320 return;
321 [listTypeSelector setSelectedSegment:REQUEST_SEG];
322
323 currentFilterType = lrc::api::profile::Type::PENDING;
324 model_->setFilter(lrc::api::profile::Type::PENDING);
325 model_->setFilter("");
326}
327
Olivier Soldanod4311552017-11-20 15:09:53 -0500328#pragma mark - NSTableViewDelegate methods
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400329
Olivier Soldanod4311552017-11-20 15:09:53 -0500330- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400331{
332 return YES;
333}
334
Olivier Soldanod4311552017-11-20 15:09:53 -0500335- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400336{
337 return NO;
338}
339
Olivier Soldanod4311552017-11-20 15:09:53 -0500340- (void)tableViewSelectionDidChange:(NSNotification *)notification
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400341{
Olivier Soldanod4311552017-11-20 15:09:53 -0500342 NSInteger row = [notification.object selectedRow];
343
344 if (row == -1)
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400345 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500346
347 auto uid = model_->filteredConversation(row).uid;
348 if (selectedUid_ != uid) {
349 selectedUid_ = uid;
350 model_->selectConversation(uid);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400351 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400352}
353
Olivier Soldanod4311552017-11-20 15:09:53 -0500354- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400355{
Olivier Soldanod4311552017-11-20 15:09:53 -0500356 return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
357}
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400358
Olivier Soldanod4311552017-11-20 15:09:53 -0500359- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
360{
361 if (model_ == nil)
362 return nil;
363
364 auto conversation = model_->filteredConversation(row);
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400365 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400366
Olivier Soldanod4311552017-11-20 15:09:53 -0500367 result = [tableView makeViewWithIdentifier:@"MainCell" owner:tableView];
368// NSTextField* details = [result viewWithTag:DETAILS_TAG];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400369
Olivier Soldanod4311552017-11-20 15:09:53 -0500370 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
371 [((ContextualTableCellView*) result) setContextualsControls:controls];
372 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision21666f32015-09-22 17:04:36 -0400373
Andreas Traczyk3b98eb82018-03-29 17:54:42 -0400374 // if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
375 // [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
376 // [((ContextualTableCellView*) result) setActiveState:YES];
377 // } else {
378 // [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
379 // [((ContextualTableCellView*) result) setActiveState:NO];
380 // }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500381
Olivier Soldanod4311552017-11-20 15:09:53 -0500382 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
383 [unreadCount setHidden:(conversation.unreadMessages == 0)];
384 [unreadCount setIntValue:conversation.unreadMessages];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400385
386 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500387 NSString* displayNameString = bestNameForConversation(conversation, *model_);
388 NSString* displayIDString = bestIDForConversation(conversation, *model_);
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400389 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400390 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400391 [displayName setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400392 [displayRingID setHidden:YES];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400393 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400394 else {
395 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
396 [displayName setStringValue:displayNameString];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400397 [displayRingID setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400398 [displayRingID setHidden:NO];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400399 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400400 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400401
Olivier Soldanod4311552017-11-20 15:09:53 -0500402 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
403 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(conversation, model_->owner)))];
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400404
405 NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500406 if (model_->owner.contactModel->getContact(conversation.participants[0]).isPresent) {
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400407 [presenceView setHidden:NO];
408 } else {
409 [presenceView setHidden:YES];
410 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400411 return result;
412}
413
Olivier Soldanod4311552017-11-20 15:09:53 -0500414- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500415{
Olivier Soldanod4311552017-11-20 15:09:53 -0500416 return 60.0;
417}
418
419#pragma mark - NSTableDataSource methods
420
421- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
422{
423 if (tableView == smartView) {
424 if (model_ != nullptr)
425 return model_->allFilteredConversations().size();
426 }
427
428 return 0;
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500429}
430
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -0400431- (void)startCallForRow:(id)sender {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400432 NSInteger row = [smartView rowForView:sender];
433 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
434 [self placeCall:nil];
435}
436
437- (IBAction)hangUpClickedAtRow:(id)sender {
438 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500439
Olivier Soldanod4311552017-11-20 15:09:53 -0500440 if (row == -1)
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500441 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400442
Olivier Soldanod4311552017-11-20 15:09:53 -0500443 auto conv = model_->filteredConversation(row);
444 auto& callId = conv.callId;
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500445
Olivier Soldanod4311552017-11-20 15:09:53 -0500446 if (callId.empty())
447 return;
448
449 auto* callModel = model_->owner.callModel.get();
450 callModel->hangUp(callId);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400451}
452
Alexandre Lision45c18672016-11-02 15:24:45 -0400453- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
454{
455 NSAlert* alert = [NSAlert alertWithMessageText:title
456 defaultButton:@"Ok"
457 alternateButton:nil
458 otherButton:nil
459 informativeTextWithFormat:message];
460
461 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
462}
463
Olivier Soldanod4311552017-11-20 15:09:53 -0500464- (void) processSearchFieldInput
Alexandre Lision45c18672016-11-02 15:24:45 -0400465{
Olivier Soldanod4311552017-11-20 15:09:53 -0500466 model_->setFilter(std::string([[searchField stringValue] UTF8String]));
Alexandre Lision45c18672016-11-02 15:24:45 -0400467}
468
Olivier Soldanod4311552017-11-20 15:09:53 -0500469-(const lrc::api::account::Info&) chosenAccount
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400470{
Olivier Soldanod4311552017-11-20 15:09:53 -0500471 return model_->owner;
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400472}
473
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400474- (void) clearSearchField
475{
476 [searchField setStringValue:@""];
Olivier Soldanod4311552017-11-20 15:09:53 -0500477 [self processSearchFieldInput];
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400478}
479
Alexandre Lision72a669e2016-09-14 17:52:16 -0400480/**
481 Copy a NSString in the general Pasteboard
482
483 @param sender the NSObject containing the represented object to copy
484 */
485- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400486{
487 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
488 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
489 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
490}
491
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400492#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400493
494- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
495{
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400496 if (commandSelector != @selector(insertNewline:) || [[searchField stringValue] isEqual:@""]) {
497 return NO;
498 }
499 if (model_->allFilteredConversations().size() <= 0) {
500 return YES;
501 }
502 auto model = model_->filteredConversation(0);
503 auto uid = model.uid;
504 if (selectedUid_ == uid) {
505 return YES;
506 }
507 @try {
508 auto contact = model_->owner.contactModel->getContact(model.participants[0]);
509 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 -0400510 return YES;
511 }
Kateryna Kostiuk0febcfa2018-04-04 11:14:51 -0400512 selectedUid_ = uid;
513 model_->selectConversation(uid);
514 [self.view.window makeFirstResponder: smartView];
515 searchField.stringValue = @"";
516 return YES;
517 } @catch (NSException *exception) {
518 return YES;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400519 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400520}
521
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500522- (void)controlTextDidChange:(NSNotification *) notification
523{
Olivier Soldanod4311552017-11-20 15:09:53 -0500524 [self processSearchFieldInput];
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500525}
526
Alexandre Lision61db3552015-10-22 19:12:52 -0400527#pragma mark - NSPopOverDelegate
528
529- (void)popoverDidClose:(NSNotification *)notification
530{
531 if (addToContactPopover != nullptr) {
532 [addToContactPopover performClose:self];
533 addToContactPopover = NULL;
534 }
535}
536
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400537
Alexandre Lision61db3552015-10-22 19:12:52 -0400538#pragma mark - ContactLinkedDelegate
539
540- (void)contactLinked
541{
542 if (addToContactPopover != nullptr) {
543 [addToContactPopover performClose:self];
544 addToContactPopover = NULL;
545 }
546}
547
548#pragma mark - KeyboardShortcutDelegate
549
550- (void) onAddShortcut
551{
Olivier Soldanod4311552017-11-20 15:09:53 -0500552 if ([smartView selectedRow] == -1)
Alexandre Lision61db3552015-10-22 19:12:52 -0400553 return;
554
Olivier Soldanod4311552017-11-20 15:09:53 -0500555 auto uid = model_->filteredConversation([smartView selectedRow]).uid;
556 model_->makePermanent(uid);
Alexandre Lision61db3552015-10-22 19:12:52 -0400557}
558
559#pragma mark - ContextMenuDelegate
560
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400561- (NSMenu*) contextualMenuForRow:(int) index
Alexandre Lision61db3552015-10-22 19:12:52 -0400562{
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400563 if (model_ == nil)
Alexandre Lision61db3552015-10-22 19:12:52 -0400564 return nil;
565
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400566 auto conversation = model_->filteredConversation(NSInteger(index));
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400567
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400568 @try {
569 auto contact = model_->owner.contactModel->getContact(conversation.participants[0]);
570 if (contact.profileInfo.type == lrc::api::profile::Type::INVALID) {
571 return nil;
572 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400573
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400574 BOOL isSIP = false;
575 BOOL isRingContact = false;
576 /* for SIP contact show only call menu options
577 * if contact does not have uri that is not RING contact
578 * for trusted Ring contact show option block contact
579 * for untrasted contact show option add contact
580 */
Alexandre Lision72a669e2016-09-14 17:52:16 -0400581
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400582 if (contact.profileInfo.type == lrc::api::profile::Type::SIP) {
583 isSIP = true;
584 } else if (contact.profileInfo.uri.empty()) {
585 return nil;
586 }
587
588 else if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.isTrusted == true) {
589 isRingContact = true;
590 }
591 auto conversationUD = conversation.uid;
592 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
593 NSString* conversationUID = @(conversationUD.c_str());
594 NSMenuItem* separator = [NSMenuItem separatorItem];
595 NSMenuItem* videoCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place video call",
596 @"Contextual menu action")
597 action:@selector(videoCall:)
598 keyEquivalent:@""];
599 NSMenuItem* audioCallItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Place audio call",
600 @"Contextual menu action")
601 action:@selector(audioCall:)
602 keyEquivalent:@""];
603 [videoCallItem setRepresentedObject: conversationUID];
604 [audioCallItem setRepresentedObject: conversationUID];
605 [theMenu addItem:videoCallItem];
606 [theMenu addItem:audioCallItem];
607 if (isSIP == false) {
608 [theMenu addItem:separator];
609 NSMenuItem* clearConversationItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Clear conversation", @"Contextual menu action")
610 action:@selector(clearConversation:)
611 keyEquivalent:@""];
612 [clearConversationItem setRepresentedObject: conversationUID];
613 [theMenu addItem:clearConversationItem];
614 if(isRingContact) {
615 NSMenuItem* blockContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Block contact", @"Contextual menu action")
616 action:@selector(blockContact:)
617 keyEquivalent:@""];
618 [blockContactItem setRepresentedObject: conversationUID];
619 [theMenu addItem:blockContactItem];
620 } else {
621 NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
622 action:@selector(addContact:)
623 keyEquivalent:@"A"];
624 [addContactItem setRepresentedObject: conversationUID];
625 [theMenu addItem:addContactItem];
626 }
627 }
628 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400629 }
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400630 @catch (NSException *exception) {
631 return nil;
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400632 }
Alexandre Lision61db3552015-10-22 19:12:52 -0400633}
Kateryna Kostiukd51a3252018-04-04 09:20:37 -0400634
635- (void) addContact: (NSMenuItem* ) item {
636 auto menuObject = item.representedObject;
637 if(menuObject == nil) {
638 return;
639 }
640 NSString * convUId = (NSString*)menuObject;
641 std::string conversationID = std::string([convUId UTF8String]);
642 model_->makePermanent(conversationID);
643}
644
645- (void) blockContact: (NSMenuItem* ) item {
646 auto menuObject = item.representedObject;
647 if(menuObject == nil) {
648 return;
649 }
650 NSString * convUId = (NSString*)menuObject;
651 std::string conversationID = std::string([convUId UTF8String]);
652 model_->clearHistory(conversationID);
653 model_->removeConversation(conversationID, true);
654}
655
656- (void) audioCall: (NSMenuItem* ) item {
657 auto menuObject = item.representedObject;
658 if(menuObject == nil) {
659 return;
660 }
661 NSString * convUId = (NSString*)menuObject;
662 std::string conversationID = std::string([convUId UTF8String]);
663 model_->placeAudioOnlyCall(conversationID);
664
665}
666
667- (void) videoCall: (NSMenuItem* ) item {
668 auto menuObject = item.representedObject;
669 if(menuObject == nil) {
670 return;
671 }
672 NSString * convUId = (NSString*)menuObject;
673 std::string conversationID = std::string([convUId UTF8String]);
674 model_->placeCall(conversationID);
675}
676
677- (void) clearConversation:(NSMenuItem* ) item {
678 auto menuObject = item.representedObject;
679 if(menuObject == nil) {
680 return;
681 }
682 NSString * convUId = (NSString*)menuObject;
683 std::string conversationID = std::string([convUId UTF8String]);
684 model_->clearHistory(conversationID);
685}
Alexandre Lision61db3552015-10-22 19:12:52 -0400686
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400687@end