blob: 17b451b4eadea320b9ffd09bc1619d02d438c4ce [file] [log] [blame]
Andreas Traczyk252a94a2018-04-20 16:36:20 -04001
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04002/*
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05003 * Copyright (C) 2015-2018 Savoir-faire Linux Inc.
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04004 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Anthony Léonard2382b562017-12-13 15:51:28 -05005 * Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -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 <QItemSelectionModel>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040023#import <QPixmap>
24#import <QtMacExtras/qmacfunctions.h>
25
Anthony Léonard2382b562017-12-13 15:51:28 -050026// LRC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040027#import <globalinstances.h>
Anthony Léonard2382b562017-12-13 15:51:28 -050028#import <api/interaction.h>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040029
30#import "MessagesVC.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040031#import "views/IMTableCellView.h"
32#import "views/MessageBubbleView.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040033#import "views/NSImage+Extensions.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040034#import "INDSequentialTextSelectionManager.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050035#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050036#import "utils.h"
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040037#import "views/NSColor+RingTheme.h"
38
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040039
Anthony Léonard2382b562017-12-13 15:51:28 -050040@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040041
Anthony Léonard2382b562017-12-13 15:51:28 -050042 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040043
Anthony Léonard2382b562017-12-13 15:51:28 -050044 std::string convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050045 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050046 const lrc::api::conversation::Info* cachedConv_;
47
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050048 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050049
50 // Both are needed to invalidate cached conversation as pointer
51 // may not be referencing the same conversation anymore
52 QMetaObject::Connection modelSortedSignal_;
53 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050054 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040055}
56
57@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
58
59@end
60
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050061// Tags for view
62NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040063NSInteger const GENERIC_INT_TIME_TAG = 200;
64
65// views size
66CGFloat const GENERIC_CELL_HEIGHT = 60;
67CGFloat const TIME_BOX_HEIGHT = 34;
68CGFloat const MESSAGE_TEXT_PADDING = 10;
69CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
70CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050071
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040072@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040073
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040074//MessageBuble type
75typedef NS_ENUM(NSInteger, MessageSequencing) {
76 SINGLE_WITH_TIME = 0,
77 SINGLE_WITHOUT_TIME = 1,
78 FIRST_WITH_TIME = 2,
79 FIRST_WITHOUT_TIME = 3,
80 MIDDLE_IN_SEQUENCE = 5,
81 LAST_IN_SEQUENCE = 6,
82};
83
84- (void)awakeFromNib
85{
86 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
87 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
88 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
89 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
90 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
91 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
92}
Andreas Traczyk252a94a2018-04-20 16:36:20 -040093-(void) clearData {
94 cachedConv_ = nil;
95 convUid_ = "";
96 convModel_ = nil;
97
98 QObject::disconnect(modelSortedSignal_);
99 QObject::disconnect(filterChangedSignal_);
100 QObject::disconnect(interactionStatusUpdatedSignal_);
101 QObject::disconnect(newInteractionSignal_);
102}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400103
Anthony Léonard2382b562017-12-13 15:51:28 -0500104-(const lrc::api::conversation::Info*) getCurrentConversation
105{
106 if (convModel_ == nil || convUid_.empty())
107 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400108
Anthony Léonard2382b562017-12-13 15:51:28 -0500109 if (cachedConv_ != nil)
110 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400111
Anthony Léonard6f819752018-01-05 09:53:40 -0500112 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400113 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500114 cachedConv_ = &(*it);
115
116 return cachedConv_;
117}
118
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400119-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
120 auto* conv = [self getCurrentConversation];
121
122 if (conv == nil)
123 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400124 auto it = conv->interactions.find(uid);
125 if (it == conv->interactions.end()) {
126 return;
127 }
128 auto itIndex = distance(conv->interactions.begin(),it);
129 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:itIndex];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400130 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400131 if (itIndex > 0) {
132 auto previousIt = it;
133 previousIt--;
134 auto previousInteraction = previousIt->second;
135 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
136 NSRange range = NSMakeRange(itIndex - 1, 2);
137 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
138 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400139 }
140 if (update) {
141 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
142 }
143 [conversationView reloadDataForRowIndexes: indexSet
144 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400145 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
146 NSRange range = [conversationView rowsInRect:visibleRect];
147 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
148 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
149 if (([conversationView numberOfRows] > 0) &&
150 lastvisibleRow == ([conversationView numberOfRows] -1)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400151 [conversationView scrollToEndOfDocument:nil];
152 }
153}
154
155-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
156 auto* conv = [self getCurrentConversation];
157
158 if (conv == nil)
159 return;
160 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
161 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
162 //reload previous message to update bubbleview
163 if (it > 0) {
164 NSRange range = NSMakeRange(it - 1, it);
165 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
166 }
167 if (update) {
168 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
169 }
170 [conversationView reloadDataForRowIndexes: indexSet
171 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
172 if (update) {
173 [conversationView scrollToEndOfDocument:nil];
174 }
175}
176
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500177-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500178{
179 if (convUid_ == convUid && convModel_ == model)
180 return;
181
182 cachedConv_ = nil;
183 convUid_ = convUid;
184 convModel_ = model;
185
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500186 // Signal triggered when messages are received or their status updated
187 QObject::disconnect(newInteractionSignal_);
188 QObject::disconnect(interactionStatusUpdatedSignal_);
189 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400190 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
191 if (uid != convUid_)
192 return;
193 cachedConv_ = nil;
194 [conversationView noteNumberOfRowsChanged];
195 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
196 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500197 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
198 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
199 if (uid != convUid_)
200 return;
201 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400202 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
203 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
204 convModel_->refreshFilter();
205 }
206 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
207 //accept incoming transfer
208 if (interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER &&
209 (interaction.status == lrc::api::interaction::Status::TRANSFER_AWAITING_HOST ||
210 interaction.status == lrc::api::interaction::Status::TRANSFER_CREATED)) {
211 lrc::api::datatransfer::Info info = {};
212 convModel_->getTransferInfo(interactionId, info);
213 double convertData = static_cast<double>(info.totalSize);
214 NSString* pathUrl = @(info.displayName.c_str());
215
216 NSString* fileExtension = pathUrl.pathExtension;
217
218 CFStringRef utiType = UTTypeCreatePreferredIdentifierForTag(
219 kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
220
221 bool isImage = UTTypeConformsTo(utiType, kUTTypeImage);
222 if( convertData <= 10485760 && isImage) {
223 [self acceptFile:interactionId];
224 }
225 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500226 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500227
228 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
229 // after a reordering.
230 QObject::disconnect(modelSortedSignal_);
231 QObject::disconnect(filterChangedSignal_);
232 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
233 [self](){
234 cachedConv_ = nil;
235 });
236 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
237 [self](){
238 cachedConv_ = nil;
239 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500240 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400241 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400242}
243
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400244#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400245
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400246-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500247{
248 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
249 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400250 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500251
252 // TODO: Fix symbol in LRC
253 NSString* fixedString = [text stringByReplacingOccurrencesOfString:@"🕽" withString:@"📞"];
254 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400255 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500256
257 return result;
258}
259
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400260-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500261{
262 IMTableCellView* result;
263
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400264 auto type = interaction.type;
265 auto status = interaction.status;
266
267 NSString* fileName = @"incoming file";
268
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500269 // First, view is created
270 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
271 switch (status) {
272 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400273 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
274 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
275 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
276 [result.acceptButton setTarget:self];
277 [result.declineButton setAction:@selector(declineIncomingFile:)];
278 [result.declineButton setTarget:self];
279 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500280 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400281 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
282 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
283 [result.progressIndicator startAnimation:conversationView];
284 [result.declineButton setAction:@selector(declineIncomingFile:)];
285 [result.declineButton setTarget:self];
286 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500287 case lrc::api::interaction::Status::TRANSFER_FINISHED:
288 case lrc::api::interaction::Status::TRANSFER_CANCELED:
289 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400290 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
291 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500292 }
293 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400294 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500295 switch (status) {
296 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500297 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400298 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500299 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400300 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500301 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400302 [result.declineButton setAction:@selector(declineIncomingFile:)];
303 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500304 break;
305 case lrc::api::interaction::Status::TRANSFER_FINISHED:
306 case lrc::api::interaction::Status::TRANSFER_CANCELED:
307 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500308 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400309 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500310 }
311 }
312
313 // Then status label is updated if needed
314 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400315 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500316 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400317 [result.statusLabel setTextColor:[NSColor greenColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500318 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500319 break;
320 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400321 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500322 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500323 break;
324 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400325 [result.statusLabel setTextColor:[NSColor redColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500326 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500327 break;
328 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400329 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500330 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
331 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500332 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400333 result.transferedImage.image = nil;
334 [result.msgBackground setHidden:NO];
335 [result invalidateImageConstraints];
336 NSString* name = @(interaction.body.c_str());
337 if (name.length > 0) {
338 if (([name rangeOfString:@"/"].location != NSNotFound)) {
339 NSArray *listItems = [name componentsSeparatedByString:@"/"];
340 NSString* name1 = listItems.lastObject;
341 fileName = name1;
342 } else {
343 fileName = name;
344 }
345 }
346 result.transferedFileName.stringValue = fileName;
347 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
348 NSImage* image = [self getImageForFilePath:name];
349 if (([name rangeOfString:@"/"].location == NSNotFound)) {
350 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
351 }
352 if(image != nil) {
353 result.transferedImage.image = [image roundCorners:14];
354 [result updateImageConstraint:image.size.width andHeight:image.size.height];
355 }
356 }
357 [result setupForInteraction:interactionID];
358 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
359 NSString* timeString = [self timeForMessage: msgTime];
360 result.timeLabel.stringValue = timeString;
361 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
362 if (!isOutgoing) {
363 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
364 auto* conv = [self getCurrentConversation];
365 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
366 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500367 return result;
368}
369
Anthony Léonard2382b562017-12-13 15:51:28 -0500370#pragma mark - NSTableViewDelegate methods
371- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400372{
373 return YES;
374}
375
Anthony Léonard2382b562017-12-13 15:51:28 -0500376- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400377{
Anthony Léonard2382b562017-12-13 15:51:28 -0500378 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400379}
380
Anthony Léonard2382b562017-12-13 15:51:28 -0500381- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400382{
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400383
Anthony Léonard2382b562017-12-13 15:51:28 -0500384 auto* conv = [self getCurrentConversation];
385
386 if (conv == nil)
387 return nil;
388
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500389 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500390
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500391 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500392
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400393 IMTableCellView* result;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400394 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500395 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
396
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500397 switch (interaction.type) {
398 case lrc::api::interaction::Type::TEXT:
399 if (isOutgoing) {
400 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
401 } else {
402 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
403 }
404 break;
405 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
406 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400407 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500408 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500409 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400410 case lrc::api::interaction::Type::CALL: {
411 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
412 NSString* timeString = [self timeForMessage: msgTime];
413 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str()) andTime:timeString];
414 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500415 default: // If interaction is not of a known type
416 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400417 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400418 MessageSequencing sequence = [self computeSequencingFor:row];
419 BubbleType type = SINGLE;
420 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
421 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400422 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400423 if (sequence == MIDDLE_IN_SEQUENCE) {
424 type = MIDDLE;
425 }
426 if (sequence == LAST_IN_SEQUENCE) {
427 type = LAST;
428 }
429 result.msgBackground.type = type;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500430 [result setupForInteraction:it->first];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400431 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400432 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400433 [result.msgBackground setNeedsDisplay:YES];
434 [result setNeedsDisplay:YES];
435 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400436
437 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400438 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@",@(interaction.body.c_str())]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400439 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400440
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400441 CGSize messageSize = [self sizeFor: @(interaction.body.c_str()) maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400442
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400443 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400444 [[result.msgView textStorage] appendAttributedString:msgAttString];
445 [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500446
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400447 if (shouldDisplayTime) {
448 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
449 NSString* timeString = [self timeForMessage: msgTime];
450 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500451 }
452
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400453 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
454 && sequence != FIRST_WITH_TIME) ? YES : NO;
455 [result.photoView setHidden:!shouldDisplayAvatar];
456 if (!isOutgoing && shouldDisplayAvatar) {
457 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
458 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
459 }
460 [result.messageStatus setHidden:YES];
461 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
462 if (interaction.status == lrc::api::interaction::Status::SENDING) {
463 [result.messageStatus setHidden:NO];
464 [result.sendingMessageIndicator startAnimation:nil];
465 [result.messageFailed setHidden:YES];
466 } else if (interaction.status == lrc::api::interaction::Status::FAILED) {
467 [result.messageStatus setHidden:NO];
468 [result.sendingMessageIndicator setHidden:YES];
469 [result.messageFailed setHidden:NO];
470 }
471 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400472 return result;
473}
474
Anthony Léonard2382b562017-12-13 15:51:28 -0500475- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400476{
Anthony Léonard2382b562017-12-13 15:51:28 -0500477 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400478
Anthony Léonard2382b562017-12-13 15:51:28 -0500479 auto* conv = [self getCurrentConversation];
480
481 if (conv == nil)
482 return 0;
483
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500484 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500485
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500486 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500487
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400488 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500489
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400490 MessageSequencing sequence = [self computeSequencingFor:row];
491
492 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
493
494
495 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
496
497 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
498 NSString* name = @(interaction.body.c_str());
499 NSImage* image = [self getImageForFilePath:name];
500 if (([name rangeOfString:@"/"].location == NSNotFound)) {
501 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
502 }
503 if (image != nil) {
504 return image.size.height + TIME_BOX_HEIGHT;
505 }
506 }
507 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
508 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500509
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500510 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400511 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500512
Anthony Léonard2382b562017-12-13 15:51:28 -0500513 // TODO Implement interactions other than messages
514 if(interaction.type != lrc::api::interaction::Type::TEXT) {
515 return 0;
516 }
517
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400518 CGSize messageSize = [self sizeFor: @(interaction.body.c_str()) maxWidth:tableView.frame.size.width * 0.7];
519 CGFloat singleLignMessageHeight = 15;
520
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400521 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
522
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400523 if (shouldDisplayTime) {
524 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
525 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
526 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400527 if(shouldApplyPadding) {
528 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
529 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
530 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400531 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
532 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
533}
534
535#pragma mark - message view parameters
536
537-(NSString *) getDataTransferPath:(uint64_t)interactionId {
538 lrc::api::datatransfer::Info info = {};
539 convModel_->getTransferInfo(interactionId, info);
540 double convertData = static_cast<double>(info.totalSize);
541 return @(info.path.c_str());
542}
543
544-(NSImage*) getImageForFilePath: (NSString *) path {
545 if (path.length <= 0) {return nil;}
546 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
547 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
548 if(transferedImage != nil) {
549 return [transferedImage imageResizeInsideMax: MAX_TRANSFERED_IMAGE_SIZE];
550 }
551 return nil;
552}
553
554-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
555 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500556 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400557 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500558 attributes:[self messageAttributes]];
559
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400560 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
561 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400562 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400563 [[tv textStorage] setAttributedString:msgAttString];
564 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400565 return tv.frame.size;
566}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400567
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400568-(MessageSequencing) computeSequencingFor:(NSInteger) row {
569 auto* conv = [self getCurrentConversation];
570 if (conv == nil)
571 return SINGLE_WITHOUT_TIME;
572 auto it = conv->interactions.begin();
573 std::advance(it, row);
574 auto interaction = it->second;
575 if (interaction.type != lrc::api::interaction::Type::TEXT) {
576 return SINGLE_WITH_TIME;
577 }
578 if (row == 0) {
579 if (it == conv->interactions.end()) {
580 return SINGLE_WITH_TIME;
581 }
582 auto nextIt = it;
583 nextIt++;
584 auto nextInteraction = nextIt->second;
585 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
586 return SINGLE_WITH_TIME;
587 }
588 return FIRST_WITH_TIME;
589 }
590
591 if (row == conversationView.numberOfRows - 1) {
592 if(it == conv->interactions.begin()) {
593 return SINGLE_WITH_TIME;
594 }
595 auto previousIt = it;
596 previousIt--;
597 auto previousInteraction = previousIt->second;
598 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
599 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
600 if (!timeChanged && !authorChanged) {
601 return LAST_IN_SEQUENCE;
602 }
603 if (!timeChanged && authorChanged) {
604 return SINGLE_WITHOUT_TIME;
605 }
606 return SINGLE_WITH_TIME;
607 }
608 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
609 return SINGLE_WITH_TIME;
610 }
611 auto previousIt = it;
612 previousIt--;
613 auto previousInteraction = previousIt->second;
614 auto nextIt = it;
615 nextIt++;
616 auto nextInteraction = nextIt->second;
617
618 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
619 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
620 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400621 if (previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER ||
622 previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
623 if(!sequenceWillChange) {
624 return FIRST_WITH_TIME;
625 }
626 return SINGLE_WITH_TIME;
627 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400628 if (!sequenceWillChange) {
629 if (!timeChanged && !authorChanged) {
630 return MIDDLE_IN_SEQUENCE;
631 }
632 if (timeChanged) {
633 return FIRST_WITH_TIME;
634 }
635 return FIRST_WITHOUT_TIME;
636 } if (!timeChanged && !authorChanged) {
637 return LAST_IN_SEQUENCE;
638 } if (timeChanged) {
639 return SINGLE_WITH_TIME;
640 }
641 return SINGLE_WITHOUT_TIME;
642}
643
644-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
645 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
646}
647
648-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
649 bool timeChanged = NO;
650 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
651 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
652 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
653 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
654 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
655 timeChanged = YES;
656 }
657 return timeChanged;
658}
659
660-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
661 bool authorChanged = YES;
662 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
663 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
664 authorChanged = NO;
665 }
666 return authorChanged;
667}
668
669-(NSString *)timeForMessage:(NSDate*) msgTime {
670 NSDate *today = [NSDate date];
671 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
672 if ([[NSCalendar currentCalendar] compareDate:today
673 toDate:msgTime
674 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
675 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle];
676 }
677
678 if ([[NSCalendar currentCalendar] compareDate:today
679 toDate:msgTime
680 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
681 [[NSCalendar currentCalendar] compareDate:today
682 toDate:msgTime
683 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
684 [dateFormatter setDateFormat:@"MMM dd, HH:mm"];
685 return [dateFormatter stringFromDate:msgTime];
686 }
687
688 [dateFormatter setDateFormat:@"HH:mm"];
689 return [dateFormatter stringFromDate:msgTime];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400690}
691
Anthony Léonard2382b562017-12-13 15:51:28 -0500692#pragma mark - NSTableViewDataSource
693
694- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
695{
696 auto* conv = [self getCurrentConversation];
697
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500698 if (conv)
699 return conv->interactions.size();
700 else
701 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500702}
703
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400704#pragma mark - Text formatting
705
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400706- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400707{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400708 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400709 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400710 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400711 return attrs;
712}
713
714- (NSParagraphStyle*) paragraphStyle
715{
716 /*
717 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
718 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
719 to copy, we use the default one.
720
721 The default values supplied by the default NSParagraphStyle are:
722 Alignment NSNaturalTextAlignment
723 Tab stops 12 left-aligned tabs, spaced by 28.0 points
724 Line break mode NSLineBreakByWordWrapping
725 All others 0.0
726 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400727 NSMutableParagraphStyle* aMutableParagraphStyle =
728 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
729 [aMutableParagraphStyle setHeadIndent:1.0];
730 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
731 return aMutableParagraphStyle;
732}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400733
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400734- (void)acceptFile:(uint64_t)interactionID {
735 NSURL *downloadsURL = [[NSFileManager defaultManager]
736 URLForDirectory:NSDownloadsDirectory
737 inDomain:NSUserDomainMask appropriateForURL:nil
738 create:YES error:nil];
739 auto& inter = [self getCurrentConversation]->interactions.find(interactionID)->second;
740 if (convModel_ && !convUid_.empty()) {
741 NSURL *bUrl = [downloadsURL URLByAppendingPathComponent:@(inter.body.c_str())];
742 const char* fullPath = [bUrl fileSystemRepresentation];
743 convModel_->acceptTransfer(convUid_, interactionID, fullPath);
744 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400745}
746
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500747#pragma mark - Actions
748
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400749- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500750 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
751 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
752 if (convModel_ && !convUid_.empty()) {
753 NSSavePanel* filePicker = [NSSavePanel savePanel];
754 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
755
756 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
757 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
758 convModel_->acceptTransfer(convUid_, interId, fullPath);
759 }
760 }
761}
762
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400763- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500764 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
765 if (convModel_ && !convUid_.empty()) {
766 convModel_->cancelTransfer(convUid_, inter);
767 }
768}
769
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400770@end