blob: 35dd7dada6f698465f250d6be582400c21f3ebf8 [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
Anthony Léonarddd85dee2018-02-19 14:34:35 -050059 QMetaObject::Connection modelSortedConnection_, filterChangedConnection_, newConversationConnection_, conversationRemovedConnection_, interactionStatusUpdatedConnection_;
60 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{
131 [smartView deselectAll:nil];
Olivier Soldanod4311552017-11-20 15:09:53 -0500132
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500133 if (!model_->owner.contactModel->hasPendingRequests()) {
134 if (currentFilterType == lrc::api::profile::Type::PENDING) {
135 [self selectConversationList];
136 }
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500137 if (selectorIsPresent) {
138 [listTypeSelector removeFromSuperview];
139 selectorIsPresent = false;
140 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500141 } else {
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500142 if (!selectorIsPresent) {
143 // First we restore the selector with selection on "Conversations"
144 [self.view addSubview:listTypeSelector];
145 [listTypeSelector setSelected:YES forSegment:CONVERSATION_SEG];
146
147 // Then constraints are recreated (as these are lost when calling removeFromSuperview)
148 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[searchField]-8-[listTypeSelector]"
149 options:0
150 metrics:nil
151 views:NSDictionaryOfVariableBindings(searchField, listTypeSelector)]];
152 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[listTypeSelector]-8-[tabbar]"
153 options:0
154 metrics:nil
155 views:NSDictionaryOfVariableBindings(listTypeSelector, tabbar)]];
156 [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[listTypeSelector]-20-|"
157 options:0
158 metrics:nil
159 views:NSDictionaryOfVariableBindings(listTypeSelector)]];
160 selectorIsPresent = true;
161 }
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500162 }
163
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500164 [smartView reloadData];
165
Olivier Soldanod4311552017-11-20 15:09:53 -0500166 if (!selectedUid_.empty() && model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500167 auto it = getConversationFromUid(selectedUid_, *model_);
Olivier Soldanod4311552017-11-20 15:09:53 -0500168 if (it != model_->allFilteredConversations().end()) {
169 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
170 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
171 } else {
172 selectedUid_.clear();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400173 }
174 }
175
Olivier Soldanod4311552017-11-20 15:09:53 -0500176 [smartView scrollToBeginningOfDocument:nil];
177}
178
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500179- (BOOL)setConversationModel:(lrc::api::ConversationModel *)conversationModel
Olivier Soldanod4311552017-11-20 15:09:53 -0500180{
181 if (model_ != conversationModel) {
182 model_ = conversationModel;
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500183 selectedUid_.clear(); // Clear selected conversation as the selected account is being changed
Olivier Soldanod4311552017-11-20 15:09:53 -0500184 QObject::disconnect(modelSortedConnection_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500185 QObject::disconnect(filterChangedConnection_);
Anthony Léonard01634102017-12-27 16:37:16 -0500186 QObject::disconnect(newConversationConnection_);
187 QObject::disconnect(conversationRemovedConnection_);
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500188 QObject::disconnect(interactionStatusUpdatedConnection_);
Anthony Léonardcb73a2c2018-01-18 11:45:56 -0500189 [self reloadData];
Olivier Soldanod4311552017-11-20 15:09:53 -0500190 if (model_ != nil) {
191 modelSortedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::modelSorted,
192 [self] (){
193 [self reloadData];
194 });
195 filterChangedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::filterChanged,
196 [self] (){
197 [self reloadData];
198 });
Anthony Léonard01634102017-12-27 16:37:16 -0500199 newConversationConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::newConversation,
200 [self] (){
201 [self reloadData];
202 });
203 conversationRemovedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::conversationRemoved,
204 [self] (){
205 [self reloadData];
206 });
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500207 interactionStatusUpdatedConnection_ = QObject::connect(model_, &lrc::api::ConversationModel::interactionStatusUpdated,
208 [self] (const std::string& convUid) {
209 if (convUid != selectedUid_)
210 return;
211 auto it = getConversationFromUid(selectedUid_, *model_);
212 if (it != model_->allFilteredConversations().end()) {
213 // The following mechanism is here to debounce the interactionStatusUpdated so
214 // we do not redraw the conversation list for each message status changing
215 if (statusUpdateDebounceTimer != nil) {
216 [statusUpdateDebounceTimer invalidate];
217 }
218 statusUpdateDebounceTimer = [NSTimer timerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
219 [self reloadData];
220 }];
221 [[NSRunLoop mainRunLoop] addTimer:statusUpdateDebounceTimer forMode:NSRunLoopCommonModes];
222 }
223 });
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500224 model_->setFilter(""); // Reset the filter
225 }
226 [searchField setStringValue:@""];
227 return YES;
228 }
229 return NO;
230}
231
232-(void)selectConversation:(const lrc::api::conversation::Info&)conv model:(lrc::api::ConversationModel*)model;
233{
234 auto& uid = conv.uid;
235 if (selectedUid_ == uid)
236 return;
237
238 [self setConversationModel:model];
239
240 if (model_ != nil) {
Anthony Léonard6f819752018-01-05 09:53:40 -0500241 auto it = getConversationFromUid(selectedUid_, *model_);
Anthony Léonard9bebf1d2017-12-21 14:33:51 -0500242 if (it != model_->allFilteredConversations().end()) {
243 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:(it - model_->allFilteredConversations().begin())];
244 [smartView selectRowIndexes:indexSet byExtendingSelection:NO];
245 selectedUid_ = uid;
Olivier Soldanod4311552017-11-20 15:09:53 -0500246 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400247 }
248}
249
Anthony Léonard8585cc02017-12-28 14:03:45 -0500250-(void)deselect
251{
252 selectedUid_.clear();
253 [smartView deselectAll:nil];
254}
255
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500256- (IBAction) listTypeChanged:(id)sender
257{
258 NSInteger selectedItem = [sender selectedSegment];
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500259 switch (selectedItem) {
260 case CONVERSATION_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500261 if (currentFilterType != lrc::api::profile::Type::RING) {
262 model_->setFilter(lrc::api::profile::Type::RING);
263 [delegate listTypeChanged];
264 currentFilterType = lrc::api::profile::Type::RING;
265 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500266 break;
267 case REQUEST_SEG:
Anthony Léonardbee94cc2018-01-16 11:42:40 -0500268 if (currentFilterType != lrc::api::profile::Type::PENDING) {
269 model_->setFilter(lrc::api::profile::Type::PENDING);
270 [delegate listTypeChanged];
271 currentFilterType = lrc::api::profile::Type::PENDING;
272 }
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500273 break;
274 default:
275 NSLog(@"Invalid item selected in list selector: %d", selectedItem);
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500276 }
277}
278
279-(void) selectConversationList
280{
Anthony Léonardd00cd182018-01-17 09:21:27 -0500281 if (currentFilterType == lrc::api::profile::Type::RING)
282 return;
Anthony Léonarde6a01fa2018-01-12 10:29:58 -0500283 [listTypeSelector setSelectedSegment:CONVERSATION_SEG];
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500284
285 // Do not invert order of the next two lines or stack overflow
286 // may happen on -(void) reloadData call if filter is currently set to PENDING
287 currentFilterType = lrc::api::profile::Type::RING;
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500288 model_->setFilter(lrc::api::profile::Type::RING);
Anthony Léonard4bfd68b2018-01-15 16:23:50 -0500289 model_->setFilter("");
Anthony Léonard0a9904c2018-01-11 16:43:47 -0500290}
291
Anthony Léonardd00cd182018-01-17 09:21:27 -0500292-(void) selectPendingList
293{
294 if (currentFilterType == lrc::api::profile::Type::PENDING)
295 return;
296 [listTypeSelector setSelectedSegment:REQUEST_SEG];
297
298 currentFilterType = lrc::api::profile::Type::PENDING;
299 model_->setFilter(lrc::api::profile::Type::PENDING);
300 model_->setFilter("");
301}
302
Olivier Soldanod4311552017-11-20 15:09:53 -0500303#pragma mark - NSTableViewDelegate methods
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400304
Olivier Soldanod4311552017-11-20 15:09:53 -0500305- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400306{
307 return YES;
308}
309
Olivier Soldanod4311552017-11-20 15:09:53 -0500310- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400311{
312 return NO;
313}
314
Olivier Soldanod4311552017-11-20 15:09:53 -0500315- (void)tableViewSelectionDidChange:(NSNotification *)notification
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400316{
Olivier Soldanod4311552017-11-20 15:09:53 -0500317 NSInteger row = [notification.object selectedRow];
318
319 if (row == -1)
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400320 return;
Olivier Soldanod4311552017-11-20 15:09:53 -0500321
322 auto uid = model_->filteredConversation(row).uid;
323 if (selectedUid_ != uid) {
324 selectedUid_ = uid;
325 model_->selectConversation(uid);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400326 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400327}
328
Olivier Soldanod4311552017-11-20 15:09:53 -0500329- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400330{
Olivier Soldanod4311552017-11-20 15:09:53 -0500331 return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
332}
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400333
Olivier Soldanod4311552017-11-20 15:09:53 -0500334- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
335{
336 if (model_ == nil)
337 return nil;
338
339 auto conversation = model_->filteredConversation(row);
Alexandre Lisiona4bb0512016-04-19 18:04:31 -0400340 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400341
Olivier Soldanod4311552017-11-20 15:09:53 -0500342 result = [tableView makeViewWithIdentifier:@"MainCell" owner:tableView];
343// NSTextField* details = [result viewWithTag:DETAILS_TAG];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400344
Olivier Soldanod4311552017-11-20 15:09:53 -0500345 NSMutableArray* controls = [NSMutableArray arrayWithObject:[result viewWithTag:CALL_BUTTON_TAG]];
346 [((ContextualTableCellView*) result) setContextualsControls:controls];
347 [((ContextualTableCellView*) result) setShouldBlurParentView:YES];
Alexandre Lision21666f32015-09-22 17:04:36 -0400348
Olivier Soldanod4311552017-11-20 15:09:53 -0500349// if (auto call = RecentModel::instance().getActiveCall(qIdx)) {
350// [details setStringValue:call->roleData((int)Ring::Role::FormattedState).toString().toNSString()];
351// [((ContextualTableCellView*) result) setActiveState:YES];
352// } else {
353// [details setStringValue:qIdx.data((int)Ring::Role::FormattedLastUsed).toString().toNSString()];
354// [((ContextualTableCellView*) result) setActiveState:NO];
355// }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500356
Olivier Soldanod4311552017-11-20 15:09:53 -0500357 NSTextField* unreadCount = [result viewWithTag:TXT_BUTTON_TAG];
358 [unreadCount setHidden:(conversation.unreadMessages == 0)];
359 [unreadCount setIntValue:conversation.unreadMessages];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400360
361 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500362 NSString* displayNameString = bestNameForConversation(conversation, *model_);
363 NSString* displayIDString = bestIDForConversation(conversation, *model_);
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400364 if(displayNameString.length == 0 || [displayNameString isEqualToString:displayIDString]) {
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400365 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400366 [displayName setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400367 [displayRingID setHidden:YES];
Kateryna Kostiuk9d6657a2017-04-25 13:42:25 -0400368 }
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400369 else {
370 NSTextField* displayRingID = [result viewWithTag:RING_ID_LABEL];
371 [displayName setStringValue:displayNameString];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400372 [displayRingID setStringValue:displayIDString];
Anthony Léonard4adc3f02017-07-21 10:23:04 -0400373 [displayRingID setHidden:NO];
Kateryna Kostiuk6a536362017-05-02 14:26:12 -0400374 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400375 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400376
Olivier Soldanod4311552017-11-20 15:09:53 -0500377 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
378 [photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(conversation, model_->owner)))];
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400379
380 NSView* presenceView = [result viewWithTag:PRESENCE_TAG];
Olivier Soldanod4311552017-11-20 15:09:53 -0500381 if (model_->owner.contactModel->getContact(conversation.participants[0]).isPresent) {
Kateryna Kostiukd9039e92017-05-24 14:29:54 -0400382 [presenceView setHidden:NO];
383 } else {
384 [presenceView setHidden:YES];
385 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400386 return result;
387}
388
Olivier Soldanod4311552017-11-20 15:09:53 -0500389- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500390{
Olivier Soldanod4311552017-11-20 15:09:53 -0500391 return 60.0;
392}
393
394#pragma mark - NSTableDataSource methods
395
396- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
397{
398 if (tableView == smartView) {
399 if (model_ != nullptr)
400 return model_->allFilteredConversations().size();
401 }
402
403 return 0;
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500404}
405
Kateryna Kostiuk882cbac2017-07-05 17:29:00 -0400406- (void)startCallForRow:(id)sender {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400407 NSInteger row = [smartView rowForView:sender];
408 [smartView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
409 [self placeCall:nil];
410}
411
412- (IBAction)hangUpClickedAtRow:(id)sender {
413 NSInteger row = [smartView rowForView:sender];
Alexandre Lision57914fa2016-02-10 14:52:24 -0500414
Olivier Soldanod4311552017-11-20 15:09:53 -0500415 if (row == -1)
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500416 return;
Alexandre Lision45c18672016-11-02 15:24:45 -0400417
Olivier Soldanod4311552017-11-20 15:09:53 -0500418 auto conv = model_->filteredConversation(row);
419 auto& callId = conv.callId;
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500420
Olivier Soldanod4311552017-11-20 15:09:53 -0500421 if (callId.empty())
422 return;
423
424 auto* callModel = model_->owner.callModel.get();
425 callModel->hangUp(callId);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400426}
427
Alexandre Lision45c18672016-11-02 15:24:45 -0400428- (void) displayErrorModalWithTitle:(NSString*) title WithMessage:(NSString*) message
429{
430 NSAlert* alert = [NSAlert alertWithMessageText:title
431 defaultButton:@"Ok"
432 alternateButton:nil
433 otherButton:nil
434 informativeTextWithFormat:message];
435
436 [alert beginSheetModalForWindow:self.view.window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
437}
438
Olivier Soldanod4311552017-11-20 15:09:53 -0500439- (void) processSearchFieldInput
Alexandre Lision45c18672016-11-02 15:24:45 -0400440{
Olivier Soldanod4311552017-11-20 15:09:53 -0500441 model_->setFilter(std::string([[searchField stringValue] UTF8String]));
Alexandre Lision45c18672016-11-02 15:24:45 -0400442}
443
Olivier Soldanod4311552017-11-20 15:09:53 -0500444-(const lrc::api::account::Info&) chosenAccount
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400445{
Olivier Soldanod4311552017-11-20 15:09:53 -0500446 return model_->owner;
Kateryna Kostiukd2f7b5d2017-06-09 15:30:49 -0400447}
448
Alexandre Lisionf909fb12016-11-03 12:35:36 -0400449- (void) clearSearchField
450{
451 [searchField setStringValue:@""];
Olivier Soldanod4311552017-11-20 15:09:53 -0500452 [self processSearchFieldInput];
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400453}
454
Alexandre Lision72a669e2016-09-14 17:52:16 -0400455/**
456 Copy a NSString in the general Pasteboard
457
458 @param sender the NSObject containing the represented object to copy
459 */
460- (void) copyStringToPasteboard:(id) sender
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400461{
462 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
463 [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
464 [pasteBoard setString:[sender representedObject] forType:NSStringPboardType];
465}
466
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400467#pragma NSTextFieldDelegate
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400468
469- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
470{
471 if (commandSelector == @selector(insertNewline:)) {
472 if([[searchField stringValue] isNotEqualTo:@""]) {
Olivier Soldanod4311552017-11-20 15:09:53 -0500473 [self processSearchFieldInput];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400474 return YES;
475 }
476 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400477 return NO;
478}
479
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500480- (void)controlTextDidChange:(NSNotification *) notification
481{
Olivier Soldanod4311552017-11-20 15:09:53 -0500482 [self processSearchFieldInput];
Alexandre Lisionac4f5b62016-02-17 13:53:12 -0500483}
484
Alexandre Lision61db3552015-10-22 19:12:52 -0400485#pragma mark - NSPopOverDelegate
486
487- (void)popoverDidClose:(NSNotification *)notification
488{
489 if (addToContactPopover != nullptr) {
490 [addToContactPopover performClose:self];
491 addToContactPopover = NULL;
492 }
493}
494
Alexandre Lisionbf0385e2015-10-22 17:36:28 -0400495
Alexandre Lision61db3552015-10-22 19:12:52 -0400496#pragma mark - ContactLinkedDelegate
497
498- (void)contactLinked
499{
500 if (addToContactPopover != nullptr) {
501 [addToContactPopover performClose:self];
502 addToContactPopover = NULL;
503 }
504}
505
506#pragma mark - KeyboardShortcutDelegate
507
508- (void) onAddShortcut
509{
Olivier Soldanod4311552017-11-20 15:09:53 -0500510 if ([smartView selectedRow] == -1)
Alexandre Lision61db3552015-10-22 19:12:52 -0400511 return;
512
Olivier Soldanod4311552017-11-20 15:09:53 -0500513 auto uid = model_->filteredConversation([smartView selectedRow]).uid;
514 model_->makePermanent(uid);
Alexandre Lision61db3552015-10-22 19:12:52 -0400515}
516
517#pragma mark - ContextMenuDelegate
518
Olivier Soldanod4311552017-11-20 15:09:53 -0500519#if 0
520// TODO: Reimplement contextual menu with new models and behaviors
Alexandre Lision12946a72016-03-08 13:39:34 -0500521- (NSMenu*) contextualMenuForIndex:(NSTreeNode*) item
Alexandre Lision61db3552015-10-22 19:12:52 -0400522{
Alexandre Lision12946a72016-03-08 13:39:34 -0500523 auto qIdx = [treeController toQIdx:item];
524
525 if (!qIdx.isValid()) {
526 return nil;
527 }
528
Alexandre Lision61db3552015-10-22 19:12:52 -0400529 auto originIdx = RecentModel::instance().peopleProxy()->mapToSource(qIdx);
530 auto contactmethods = RecentModel::instance().getContactMethods(originIdx);
531 if (contactmethods.isEmpty())
532 return nil;
533
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400534 NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@""];
535
536 if (contactmethods.size() == 1
537 && !contactmethods.first()->contact()
538 || contactmethods.first()->contact()->isPlaceHolder()) {
539
Kateryna Kostiuk9dd759c2017-07-11 12:06:55 -0400540 NSMenuItem* addContactItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
541 action:@selector(addContactForRow:)
542 keyEquivalent:@""];
543 [addContactItem setRepresentedObject:item];
544 [theMenu addItem:addContactItem];
Alexandre Lision72a669e2016-09-14 17:52:16 -0400545 } else if (auto person = contactmethods.first()->contact()) {
546 NSMenuItem* copyNameItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy name", @"Contextual menu action")
547 action:@selector(copyStringToPasteboard:)
548 keyEquivalent:@""];
549
550 [copyNameItem setRepresentedObject:person->formattedName().toNSString()];
551 [theMenu addItem:copyNameItem];
Alexandre Lision61db3552015-10-22 19:12:52 -0400552 }
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400553
554 NSMenu* copySubmenu = [[NSMenu alloc] init];
555 NSMenu* callSubmenu = [[NSMenu alloc] init];
556
Alexandre Lision72a669e2016-09-14 17:52:16 -0400557 for (auto cm : contactmethods) {
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400558 NSMenuItem* tmpCopyItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
Alexandre Lision72a669e2016-09-14 17:52:16 -0400559 action:@selector(copyStringToPasteboard:)
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400560 keyEquivalent:@""];
561
562 [tmpCopyItem setRepresentedObject:cm->uri().toNSString()];
563 [copySubmenu addItem:tmpCopyItem];
564
565 NSMenuItem* tmpCallItem = [[NSMenuItem alloc] initWithTitle:cm->uri().toNSString()
566 action:@selector(callNumber:)
567 keyEquivalent:@""];
568 [tmpCallItem setRepresentedObject:cm->uri().toNSString()];
569 [callSubmenu addItem:tmpCallItem];
570 }
571
Alexandre Lision72a669e2016-09-14 17:52:16 -0400572 NSMenuItem* copyNumberItem = [[NSMenuItem alloc] init];
573 [copyNumberItem setTitle:NSLocalizedString(@"Copy number", @"Contextual menu action")];
574 [copyNumberItem setSubmenu:copySubmenu];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400575
576 NSMenuItem* callItems = [[NSMenuItem alloc] init];
577 [callItems setTitle:NSLocalizedString(@"Call number", @"Contextual menu action")];
578 [callItems setSubmenu:callSubmenu];
579
Alexandre Lision72a669e2016-09-14 17:52:16 -0400580 [theMenu insertItem:copyNumberItem atIndex:theMenu.itemArray.count];
Alexandre Lisiond7bf2882015-10-22 22:54:32 -0400581 [theMenu insertItem:[NSMenuItem separatorItem] atIndex:theMenu.itemArray.count];
582 [theMenu insertItem:callItems atIndex:theMenu.itemArray.count];
583
584 return theMenu;
Alexandre Lision61db3552015-10-22 19:12:52 -0400585}
Olivier Soldanod4311552017-11-20 15:09:53 -0500586#endif
Alexandre Lision61db3552015-10-22 19:12:52 -0400587
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400588@end