blob: 6c7dc6c853df991a71fbe4b795fb0fed1ca7697a [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"
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040038#import "views/IconButton.h"
39#import <QuickLook/QuickLook.h>
40#import <Quartz/Quartz.h>
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040041
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040042
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040043@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource, QLPreviewPanelDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040044
Anthony Léonard2382b562017-12-13 15:51:28 -050045 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040046 __unsafe_unretained IBOutlet NSView* containerView;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040047
Anthony Léonard2382b562017-12-13 15:51:28 -050048 std::string convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050049 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050050 const lrc::api::conversation::Info* cachedConv_;
51
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050052 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050053
54 // Both are needed to invalidate cached conversation as pointer
55 // may not be referencing the same conversation anymore
56 QMetaObject::Connection modelSortedSignal_;
57 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050058 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040059 NSString* previewImage;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040060}
61
62@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
63
64@end
65
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050066// Tags for view
67NSInteger const GENERIC_INT_TEXT_TAG = 100;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040068NSInteger const GENERIC_INT_TIME_TAG = 200;
69
70// views size
71CGFloat const GENERIC_CELL_HEIGHT = 60;
72CGFloat const TIME_BOX_HEIGHT = 34;
73CGFloat const MESSAGE_TEXT_PADDING = 10;
74CGFloat const MAX_TRANSFERED_IMAGE_SIZE = 250;
75CGFloat const BUBBLE_HEIGHT_FOR_TRANSFERED_FILE = 87;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050076
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040077@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040078
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -040079
Kateryna Kostiukae660fd2018-04-24 14:10:41 -040080//MessageBuble type
81typedef NS_ENUM(NSInteger, MessageSequencing) {
82 SINGLE_WITH_TIME = 0,
83 SINGLE_WITHOUT_TIME = 1,
84 FIRST_WITH_TIME = 2,
85 FIRST_WITHOUT_TIME = 3,
86 MIDDLE_IN_SEQUENCE = 5,
87 LAST_IN_SEQUENCE = 6,
88};
89
90- (void)awakeFromNib
91{
92 NSNib *cellNib = [[NSNib alloc] initWithNibNamed:@"MessageCells" bundle:nil];
93 [conversationView registerNib:cellNib forIdentifier:@"LeftIncomingFileView"];
94 [conversationView registerNib:cellNib forIdentifier:@"LeftOngoingFileView"];
95 [conversationView registerNib:cellNib forIdentifier:@"LeftFinishedFileView"];
96 [conversationView registerNib:cellNib forIdentifier:@"RightOngoingFileView"];
97 [conversationView registerNib:cellNib forIdentifier:@"RightFinishedFileView"];
98}
Andreas Traczyk252a94a2018-04-20 16:36:20 -040099-(void) clearData {
100 cachedConv_ = nil;
101 convUid_ = "";
102 convModel_ = nil;
103
104 QObject::disconnect(modelSortedSignal_);
105 QObject::disconnect(filterChangedSignal_);
106 QObject::disconnect(interactionStatusUpdatedSignal_);
107 QObject::disconnect(newInteractionSignal_);
108}
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400109
Anthony Léonard2382b562017-12-13 15:51:28 -0500110-(const lrc::api::conversation::Info*) getCurrentConversation
111{
112 if (convModel_ == nil || convUid_.empty())
113 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400114
Anthony Léonard2382b562017-12-13 15:51:28 -0500115 if (cachedConv_ != nil)
116 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400117
Anthony Léonard6f819752018-01-05 09:53:40 -0500118 auto it = getConversationFromUid(convUid_, *convModel_);
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400119 if (it != convModel_->allFilteredConversations().end())
Anthony Léonard2382b562017-12-13 15:51:28 -0500120 cachedConv_ = &(*it);
121
122 return cachedConv_;
123}
124
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400125-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update {
126 auto* conv = [self getCurrentConversation];
127
128 if (conv == nil)
129 return;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400130 auto it = conv->interactions.find(uid);
131 if (it == conv->interactions.end()) {
132 return;
133 }
134 auto itIndex = distance(conv->interactions.begin(),it);
135 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:itIndex];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400136 //reload previous message to update bubbleview
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400137 if (itIndex > 0) {
138 auto previousIt = it;
139 previousIt--;
140 auto previousInteraction = previousIt->second;
141 if (previousInteraction.type == lrc::api::interaction::Type::TEXT) {
142 NSRange range = NSMakeRange(itIndex - 1, 2);
143 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
144 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400145 }
146 if (update) {
147 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
148 }
149 [conversationView reloadDataForRowIndexes: indexSet
150 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400151 CGRect visibleRect = [conversationView enclosingScrollView].contentView.visibleRect;
152 NSRange range = [conversationView rowsInRect:visibleRect];
153 NSIndexSet* visibleIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
154 NSUInteger lastvisibleRow = [visibleIndexes lastIndex];
155 if (([conversationView numberOfRows] > 0) &&
156 lastvisibleRow == ([conversationView numberOfRows] -1)) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400157 [conversationView scrollToEndOfDocument:nil];
158 }
159}
160
161-(void) reloadConversationForMessage:(uint64_t) uid shouldUpdateHeight:(bool)update updateConversation:(bool) updateConversation {
162 auto* conv = [self getCurrentConversation];
163
164 if (conv == nil)
165 return;
166 auto it = distance(conv->interactions.begin(),conv->interactions.find(uid));
167 NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:it];
168 //reload previous message to update bubbleview
169 if (it > 0) {
170 NSRange range = NSMakeRange(it - 1, it);
171 indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
172 }
173 if (update) {
174 [conversationView noteHeightOfRowsWithIndexesChanged:indexSet];
175 }
176 [conversationView reloadDataForRowIndexes: indexSet
177 columnIndexes:[NSIndexSet indexSetWithIndex:0]];
178 if (update) {
179 [conversationView scrollToEndOfDocument:nil];
180 }
181}
182
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500183-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -0500184{
185 if (convUid_ == convUid && convModel_ == model)
186 return;
187
188 cachedConv_ = nil;
189 convUid_ = convUid;
190 convModel_ = model;
191
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500192 // Signal triggered when messages are received or their status updated
193 QObject::disconnect(newInteractionSignal_);
194 QObject::disconnect(interactionStatusUpdatedSignal_);
195 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400196 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
197 if (uid != convUid_)
198 return;
199 cachedConv_ = nil;
200 [conversationView noteNumberOfRowsChanged];
201 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
202 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500203 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
204 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
205 if (uid != convUid_)
206 return;
207 cachedConv_ = nil;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400208 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
209 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
210 convModel_->refreshFilter();
211 }
212 [self reloadConversationForMessage:interactionId shouldUpdateHeight:YES];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500213 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500214
215 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
216 // after a reordering.
217 QObject::disconnect(modelSortedSignal_);
218 QObject::disconnect(filterChangedSignal_);
219 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
220 [self](){
221 cachedConv_ = nil;
222 });
223 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
224 [self](){
225 cachedConv_ = nil;
226 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500227 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400228 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400229}
230
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400231#pragma mark - configure cells
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400232
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400233-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text andTime:(NSString*) time
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500234{
235 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
236 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400237 NSTextField* timeField = [result viewWithTag:GENERIC_INT_TIME_TAG];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500238
239 // TODO: Fix symbol in LRC
240 NSString* fixedString = [text stringByReplacingOccurrencesOfString:@"🕽" withString:@"📞"];
241 [textField setStringValue:fixedString];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400242 [timeField setStringValue:time];
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500243
244 return result;
245}
246
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400247-(NSTableCellView*) configureViewforTransfer:(lrc::api::interaction::Info)interaction interactionID: (uint64_t) interactionID tableView:(NSTableView*)tableView
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500248{
249 IMTableCellView* result;
250
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400251 auto type = interaction.type;
252 auto status = interaction.status;
253
254 NSString* fileName = @"incoming file";
255
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500256 // First, view is created
257 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
258 switch (status) {
259 case lrc::api::interaction::Status::TRANSFER_CREATED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400260 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST: {
261 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner: conversationView];
262 [result.acceptButton setAction:@selector(acceptIncomingFile:)];
263 [result.acceptButton setTarget:self];
264 [result.declineButton setAction:@selector(declineIncomingFile:)];
265 [result.declineButton setTarget:self];
266 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500267 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400268 case lrc::api::interaction::Status::TRANSFER_ONGOING: {
269 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:conversationView];
270 [result.progressIndicator startAnimation:conversationView];
271 [result.declineButton setAction:@selector(declineIncomingFile:)];
272 [result.declineButton setTarget:self];
273 break;}
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500274 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400275 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
276 [result.transferedFileName setAction:@selector(imagePreview:)];
277 [result.transferedFileName setTarget:self];
278 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
279 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500280 case lrc::api::interaction::Status::TRANSFER_CANCELED:
281 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400282 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:conversationView];
283 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500284 }
285 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400286 NSString* fileName = @"sent file";
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500287 switch (status) {
288 case lrc::api::interaction::Status::TRANSFER_CREATED:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500289 case lrc::api::interaction::Status::TRANSFER_ONGOING:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400290 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500291 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400292 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500293 [result.progressIndicator startAnimation:nil];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400294 [result.declineButton setAction:@selector(declineIncomingFile:)];
295 [result.declineButton setTarget:self];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500296 break;
297 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400298 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
299 [result.transferedFileName setAction:@selector(imagePreview:)];
300 [result.transferedFileName setTarget:self];
301 [result.transferedFileName.cell setHighlightsBy:NSContentsCellMask];
302 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500303 case lrc::api::interaction::Status::TRANSFER_CANCELED:
304 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500305 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400306 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:conversationView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500307 }
308 }
309
310 // Then status label is updated if needed
311 switch (status) {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400312 [result.statusLabel setTextColor:[NSColor textColor]];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500313 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400314 [result.statusLabel setTextColor:[NSColor greenSuccessColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500315 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500316 break;
317 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400318 [result.statusLabel setTextColor:[NSColor orangeColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500319 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500320 break;
321 case lrc::api::interaction::Status::TRANSFER_ERROR:
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400322 [result.statusLabel setTextColor:[NSColor errorTransferColor]];
Anthony Léonard70638f02018-02-05 11:10:19 -0500323 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500324 break;
325 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400326 [result.statusLabel setTextColor:[NSColor textColor]];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500327 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
328 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500329 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400330 result.transferedImage.image = nil;
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400331 [result.openImagebutton setHidden:YES];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400332 [result.msgBackground setHidden:NO];
333 [result invalidateImageConstraints];
334 NSString* name = @(interaction.body.c_str());
335 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400336 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400337 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400338 NSFont *nameFont = [NSFont userFontOfSize:14.0];
339 NSColor *nameColor = [NSColor textColor];
340 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
341 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
342 paragraphStyle.alignment = NSTextAlignmentLeft;
343 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
344 nameColor,NSForegroundColorAttributeName,
345 paragraphStyle,NSParagraphStyleAttributeName, nil];
346 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
347 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400348 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400349 NSColor *higlightColor = [NSColor grayColor];
350 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
351 higlightColor,NSForegroundColorAttributeName,
352 paragraphStyle,NSParagraphStyleAttributeName, nil];
353 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
354 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400355 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400356 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400357 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400358 }
359 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400360 result.transferedImage.image = image;
361 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
362 [result.openImagebutton setAction:@selector(imagePreview:)];
363 [result.openImagebutton setTarget:self];
Kateryna Kostiukeaf1bc82018-10-12 14:33:50 -0400364 [result.openImagebutton setHidden:NO];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400365 }
366 }
367 [result setupForInteraction:interactionID];
368 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
369 NSString* timeString = [self timeForMessage: msgTime];
370 result.timeLabel.stringValue = timeString;
371 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
372 if (!isOutgoing) {
373 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
374 auto* conv = [self getCurrentConversation];
375 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
376 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500377 return result;
378}
379
Anthony Léonard2382b562017-12-13 15:51:28 -0500380#pragma mark - NSTableViewDelegate methods
381- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400382{
383 return YES;
384}
385
Anthony Léonard2382b562017-12-13 15:51:28 -0500386- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400387{
Anthony Léonard2382b562017-12-13 15:51:28 -0500388 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400389}
390
Anthony Léonard2382b562017-12-13 15:51:28 -0500391- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400392{
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400393
Anthony Léonard2382b562017-12-13 15:51:28 -0500394 auto* conv = [self getCurrentConversation];
395
396 if (conv == nil)
397 return nil;
398
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500399 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500400
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500401 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500402
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400403 IMTableCellView* result;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400404 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500405 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
406
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500407 switch (interaction.type) {
408 case lrc::api::interaction::Type::TEXT:
409 if (isOutgoing) {
410 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
411 } else {
412 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
413 }
414 break;
415 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
416 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400417 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500418 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500419 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400420 case lrc::api::interaction::Type::CALL: {
421 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
422 NSString* timeString = [self timeForMessage: msgTime];
423 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str()) andTime:timeString];
424 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500425 default: // If interaction is not of a known type
426 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400427 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400428 MessageSequencing sequence = [self computeSequencingFor:row];
429 BubbleType type = SINGLE;
430 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
431 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400432 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400433 if (sequence == MIDDLE_IN_SEQUENCE) {
434 type = MIDDLE;
435 }
436 if (sequence == LAST_IN_SEQUENCE) {
437 type = LAST;
438 }
439 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400440 bool sendingFail = false;
441 [result.messageStatus setHidden:YES];
442 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
443 if (interaction.status == lrc::api::interaction::Status::SENDING) {
444 [result.messageStatus setHidden:NO];
445 [result.sendingMessageIndicator startAnimation:nil];
446 [result.messageFailed setHidden:YES];
447 } else if (interaction.status == lrc::api::interaction::Status::FAILED) {
448 [result.messageStatus setHidden:NO];
449 [result.sendingMessageIndicator setHidden:YES];
450 [result.messageFailed setHidden:NO];
451 sendingFail = true;
452 }
453 }
454 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400455 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400456 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400457 [result.msgBackground setNeedsDisplay:YES];
458 [result setNeedsDisplay:YES];
459 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400460
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400461 NSString *text = @(interaction.body.c_str());
462 text = [text removeEmptyLinesAtBorders];
463
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400464 NSMutableAttributedString* msgAttString =
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400465 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:text]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400466 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400467
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400468 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400469
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400470 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400471 [[result.msgView textStorage] appendAttributedString:msgAttString];
472 [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500473
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400474 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
475 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
476
477 [result.msgView.textStorage beginEditing];
478
479 for (NSTextCheckingResult *match in matches) {
480 if (!match.URL) continue;
481
482 NSDictionary *linkAttributes = @{
483 NSLinkAttributeName: match.URL,
484 };
485 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
486 }
487
488 [result.msgView.textStorage endEditing];
489
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400490 if (shouldDisplayTime) {
491 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
492 NSString* timeString = [self timeForMessage: msgTime];
493 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500494 }
495
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400496 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
497 && sequence != FIRST_WITH_TIME) ? YES : NO;
498 [result.photoView setHidden:!shouldDisplayAvatar];
499 if (!isOutgoing && shouldDisplayAvatar) {
500 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
501 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
502 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400503 return result;
504}
505
Anthony Léonard2382b562017-12-13 15:51:28 -0500506- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400507{
Anthony Léonard2382b562017-12-13 15:51:28 -0500508 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400509
Anthony Léonard2382b562017-12-13 15:51:28 -0500510 auto* conv = [self getCurrentConversation];
511
512 if (conv == nil)
513 return 0;
514
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500515 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500516
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500517 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500518
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400519 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500520
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400521 MessageSequencing sequence = [self computeSequencingFor:row];
522
523 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
524
525
526 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
527
528 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
529 NSString* name = @(interaction.body.c_str());
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400530 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400531 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400532 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400533 }
534 if (image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400535 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
536 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
537 CGFloat heigt = 0;
538 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
539 heigt = image.size.height;
540 } else {
541 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
542 heigt = image.size.height * scale;
543 }
544 return heigt + TIME_BOX_HEIGHT;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400545 }
546 }
547 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
548 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500549
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500550 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400551 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500552
Anthony Léonard2382b562017-12-13 15:51:28 -0500553 // TODO Implement interactions other than messages
554 if(interaction.type != lrc::api::interaction::Type::TEXT) {
555 return 0;
556 }
557
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400558 NSString *text = @(interaction.body.c_str());
559 text = [text removeEmptyLinesAtBorders];
560
561 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400562 CGFloat singleLignMessageHeight = 15;
563
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400564 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
565
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400566 if (shouldDisplayTime) {
567 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
568 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
569 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400570 if(shouldApplyPadding) {
571 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
572 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
573 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400574 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
575 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
576}
577
578#pragma mark - message view parameters
579
580-(NSString *) getDataTransferPath:(uint64_t)interactionId {
581 lrc::api::datatransfer::Info info = {};
582 convModel_->getTransferInfo(interactionId, info);
583 double convertData = static_cast<double>(info.totalSize);
584 return @(info.path.c_str());
585}
586
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400587-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400588 if (path.length <= 0) {return nil;}
589 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
590 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400591 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400592}
593
594-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
595 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500596 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400597 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500598 attributes:[self messageAttributes]];
599
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400600 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
601 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400602 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400603 [[tv textStorage] setAttributedString:msgAttString];
604 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400605 return tv.frame.size;
606}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400607
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400608-(MessageSequencing) computeSequencingFor:(NSInteger) row {
609 auto* conv = [self getCurrentConversation];
610 if (conv == nil)
611 return SINGLE_WITHOUT_TIME;
612 auto it = conv->interactions.begin();
613 std::advance(it, row);
614 auto interaction = it->second;
615 if (interaction.type != lrc::api::interaction::Type::TEXT) {
616 return SINGLE_WITH_TIME;
617 }
618 if (row == 0) {
619 if (it == conv->interactions.end()) {
620 return SINGLE_WITH_TIME;
621 }
622 auto nextIt = it;
623 nextIt++;
624 auto nextInteraction = nextIt->second;
625 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
626 return SINGLE_WITH_TIME;
627 }
628 return FIRST_WITH_TIME;
629 }
630
631 if (row == conversationView.numberOfRows - 1) {
632 if(it == conv->interactions.begin()) {
633 return SINGLE_WITH_TIME;
634 }
635 auto previousIt = it;
636 previousIt--;
637 auto previousInteraction = previousIt->second;
638 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
639 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
640 if (!timeChanged && !authorChanged) {
641 return LAST_IN_SEQUENCE;
642 }
643 if (!timeChanged && authorChanged) {
644 return SINGLE_WITHOUT_TIME;
645 }
646 return SINGLE_WITH_TIME;
647 }
648 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
649 return SINGLE_WITH_TIME;
650 }
651 auto previousIt = it;
652 previousIt--;
653 auto previousInteraction = previousIt->second;
654 auto nextIt = it;
655 nextIt++;
656 auto nextInteraction = nextIt->second;
657
658 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
659 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
660 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400661 if (previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER ||
662 previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
663 if(!sequenceWillChange) {
664 return FIRST_WITH_TIME;
665 }
666 return SINGLE_WITH_TIME;
667 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400668 if (!sequenceWillChange) {
669 if (!timeChanged && !authorChanged) {
670 return MIDDLE_IN_SEQUENCE;
671 }
672 if (timeChanged) {
673 return FIRST_WITH_TIME;
674 }
675 return FIRST_WITHOUT_TIME;
676 } if (!timeChanged && !authorChanged) {
677 return LAST_IN_SEQUENCE;
678 } if (timeChanged) {
679 return SINGLE_WITH_TIME;
680 }
681 return SINGLE_WITHOUT_TIME;
682}
683
684-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
685 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
686}
687
688-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
689 bool timeChanged = NO;
690 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
691 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
692 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
693 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
694 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
695 timeChanged = YES;
696 }
697 return timeChanged;
698}
699
700-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
701 bool authorChanged = YES;
702 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
703 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
704 authorChanged = NO;
705 }
706 return authorChanged;
707}
708
709-(NSString *)timeForMessage:(NSDate*) msgTime {
710 NSDate *today = [NSDate date];
711 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400712 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400713 if ([[NSCalendar currentCalendar] compareDate:today
714 toDate:msgTime
715 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
716 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle];
717 }
718
719 if ([[NSCalendar currentCalendar] compareDate:today
720 toDate:msgTime
721 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
722 [[NSCalendar currentCalendar] compareDate:today
723 toDate:msgTime
724 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
725 [dateFormatter setDateFormat:@"MMM dd, HH:mm"];
726 return [dateFormatter stringFromDate:msgTime];
727 }
728
729 [dateFormatter setDateFormat:@"HH:mm"];
730 return [dateFormatter stringFromDate:msgTime];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400731}
732
Anthony Léonard2382b562017-12-13 15:51:28 -0500733#pragma mark - NSTableViewDataSource
734
735- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
736{
737 auto* conv = [self getCurrentConversation];
738
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500739 if (conv)
740 return conv->interactions.size();
741 else
742 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500743}
744
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400745#pragma mark - Text formatting
746
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400747- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400748{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400749 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400750 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400751 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400752 return attrs;
753}
754
755- (NSParagraphStyle*) paragraphStyle
756{
757 /*
758 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
759 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
760 to copy, we use the default one.
761
762 The default values supplied by the default NSParagraphStyle are:
763 Alignment NSNaturalTextAlignment
764 Tab stops 12 left-aligned tabs, spaced by 28.0 points
765 Line break mode NSLineBreakByWordWrapping
766 All others 0.0
767 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400768 NSMutableParagraphStyle* aMutableParagraphStyle =
769 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
770 [aMutableParagraphStyle setHeadIndent:1.0];
771 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
772 return aMutableParagraphStyle;
773}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400774
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500775#pragma mark - Actions
776
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400777- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500778 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
779 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
780 if (convModel_ && !convUid_.empty()) {
781 NSSavePanel* filePicker = [NSSavePanel savePanel];
782 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
783
784 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
785 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
786 convModel_->acceptTransfer(convUid_, interId, fullPath);
787 }
788 }
789}
790
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400791- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500792 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
793 if (convModel_ && !convUid_.empty()) {
794 convModel_->cancelTransfer(convUid_, inter);
795 }
796}
797
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400798- (void)imagePreview:(id)sender {
799 uint64_t interId;
800 if ([[sender superview] isKindOfClass:[IMTableCellView class]]) {
801 interId = [(IMTableCellView*)[sender superview] interaction];
802 } else if ([[[sender superview] superview] isKindOfClass:[IMTableCellView class]]) {
803 interId = [(IMTableCellView*)[[sender superview] superview] interaction];
804 } else {
805 return;
806 }
807 auto it = [self getCurrentConversation]->interactions.find(interId);
808 if (it == [self getCurrentConversation]->interactions.end()) {
809 return;
810 }
811 auto& interaction = it->second;
812 NSString* name = @(interaction.body.c_str());
813 if (([name rangeOfString:@"/"].location == NSNotFound)) {
814 name = [self getDataTransferPath:interId];
815 }
816 previewImage = name;
817 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
818 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
819 } else {
820 [[QLPreviewPanel sharedPreviewPanel] updateController];
821 [QLPreviewPanel sharedPreviewPanel].dataSource = self;
822 [[QLPreviewPanel sharedPreviewPanel] setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
823 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
824 }
825}
826
827- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
828 return 1;
829}
830
831- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
832 return [NSURL fileURLWithPath:previewImage];
833}
834
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400835@end