blob: 41fc300e540600aac394f8dbaba481fa1315d133 [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;
331 [result.msgBackground setHidden:NO];
332 [result invalidateImageConstraints];
333 NSString* name = @(interaction.body.c_str());
334 if (name.length > 0) {
Kateryna Kostiuk67735232018-05-10 15:05:32 -0400335 fileName = [name lastPathComponent];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400336 }
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400337 NSFont *nameFont = [NSFont userFontOfSize:14.0];
338 NSColor *nameColor = [NSColor textColor];
339 NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
340 paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
341 paragraphStyle.alignment = NSTextAlignmentLeft;
342 NSDictionary *nameAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
343 nameColor,NSForegroundColorAttributeName,
344 paragraphStyle,NSParagraphStyleAttributeName, nil];
345 NSAttributedString* nameAttributedString = [[NSAttributedString alloc] initWithString:fileName attributes:nameAttr];
346 result.transferedFileName.attributedTitle = nameAttributedString;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400347 if (status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400348 NSColor *higlightColor = [NSColor grayColor];
349 NSDictionary *alternativeNametAttr = [NSDictionary dictionaryWithObjectsAndKeys:nameFont,NSFontAttributeName,
350 higlightColor,NSForegroundColorAttributeName,
351 paragraphStyle,NSParagraphStyleAttributeName, nil];
352 NSAttributedString* alternativeString = [[NSAttributedString alloc] initWithString:fileName attributes:alternativeNametAttr];
353 result.transferedFileName.attributedAlternateTitle = alternativeString;
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400354 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400355 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400356 image = [self getImageForFilePath:[self getDataTransferPath:interactionID]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400357 }
358 if(image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400359 result.transferedImage.image = image;
360 [result updateImageConstraintWithMax: MAX_TRANSFERED_IMAGE_SIZE];
361 [result.openImagebutton setAction:@selector(imagePreview:)];
362 [result.openImagebutton setTarget:self];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400363 }
364 }
365 [result setupForInteraction:interactionID];
366 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
367 NSString* timeString = [self timeForMessage: msgTime];
368 result.timeLabel.stringValue = timeString;
369 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
370 if (!isOutgoing) {
371 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
372 auto* conv = [self getCurrentConversation];
373 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
374 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500375 return result;
376}
377
Anthony Léonard2382b562017-12-13 15:51:28 -0500378#pragma mark - NSTableViewDelegate methods
379- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400380{
381 return YES;
382}
383
Anthony Léonard2382b562017-12-13 15:51:28 -0500384- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400385{
Anthony Léonard2382b562017-12-13 15:51:28 -0500386 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400387}
388
Anthony Léonard2382b562017-12-13 15:51:28 -0500389- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400390{
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400391
Anthony Léonard2382b562017-12-13 15:51:28 -0500392 auto* conv = [self getCurrentConversation];
393
394 if (conv == nil)
395 return nil;
396
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500397 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500398
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500399 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500400
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400401 IMTableCellView* result;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400402 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500403 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
404
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500405 switch (interaction.type) {
406 case lrc::api::interaction::Type::TEXT:
407 if (isOutgoing) {
408 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
409 } else {
410 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
411 }
412 break;
413 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
414 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400415 return [self configureViewforTransfer:interaction interactionID: it->first tableView:tableView];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500416 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500417 case lrc::api::interaction::Type::CONTACT:
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400418 case lrc::api::interaction::Type::CALL: {
419 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
420 NSString* timeString = [self timeForMessage: msgTime];
421 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str()) andTime:timeString];
422 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500423 default: // If interaction is not of a known type
424 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400425 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400426 MessageSequencing sequence = [self computeSequencingFor:row];
427 BubbleType type = SINGLE;
428 if (sequence == FIRST_WITHOUT_TIME || sequence == FIRST_WITH_TIME) {
429 type = FIRST;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400430 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400431 if (sequence == MIDDLE_IN_SEQUENCE) {
432 type = MIDDLE;
433 }
434 if (sequence == LAST_IN_SEQUENCE) {
435 type = LAST;
436 }
437 result.msgBackground.type = type;
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400438 bool sendingFail = false;
439 [result.messageStatus setHidden:YES];
440 if (interaction.type == lrc::api::interaction::Type::TEXT && isOutgoing) {
441 if (interaction.status == lrc::api::interaction::Status::SENDING) {
442 [result.messageStatus setHidden:NO];
443 [result.sendingMessageIndicator startAnimation:nil];
444 [result.messageFailed setHidden:YES];
445 } else if (interaction.status == lrc::api::interaction::Status::FAILED) {
446 [result.messageStatus setHidden:NO];
447 [result.sendingMessageIndicator setHidden:YES];
448 [result.messageFailed setHidden:NO];
449 sendingFail = true;
450 }
451 }
452 [result setupForInteraction:it->first isFailed: sendingFail];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400453 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400454 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400455 [result.msgBackground setNeedsDisplay:YES];
456 [result setNeedsDisplay:YES];
457 [result.timeBox setNeedsDisplay:YES];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400458
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400459 NSString *text = @(interaction.body.c_str());
460 text = [text removeEmptyLinesAtBorders];
461
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400462 NSMutableAttributedString* msgAttString =
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400463 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:text]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400464 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400465
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400466 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400467
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400468 [result updateMessageConstraint:messageSize.width andHeight:messageSize.height timeIsVisible:shouldDisplayTime isTopPadding: shouldApplyPadding];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400469 [[result.msgView textStorage] appendAttributedString:msgAttString];
470 [result.msgView checkTextInDocument:nil];
Anthony Léonard2382b562017-12-13 15:51:28 -0500471
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400472 NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
473 NSArray *matches = [linkDetector matchesInString:result.msgView.string options:0 range:NSMakeRange(0, result.msgView.string.length)];
474
475 [result.msgView.textStorage beginEditing];
476
477 for (NSTextCheckingResult *match in matches) {
478 if (!match.URL) continue;
479
480 NSDictionary *linkAttributes = @{
481 NSLinkAttributeName: match.URL,
482 };
483 [result.msgView.textStorage addAttributes:linkAttributes range:match.range];
484 }
485
486 [result.msgView.textStorage endEditing];
487
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400488 if (shouldDisplayTime) {
489 NSDate* msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
490 NSString* timeString = [self timeForMessage: msgTime];
491 result.timeLabel.stringValue = timeString;
Anthony Léonard64e19672018-01-18 16:40:34 -0500492 }
493
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400494 bool shouldDisplayAvatar = (sequence != MIDDLE_IN_SEQUENCE && sequence != FIRST_WITHOUT_TIME
495 && sequence != FIRST_WITH_TIME) ? YES : NO;
496 [result.photoView setHidden:!shouldDisplayAvatar];
497 if (!isOutgoing && shouldDisplayAvatar) {
498 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
499 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
500 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400501 return result;
502}
503
Anthony Léonard2382b562017-12-13 15:51:28 -0500504- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400505{
Anthony Léonard2382b562017-12-13 15:51:28 -0500506 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400507
Anthony Léonard2382b562017-12-13 15:51:28 -0500508 auto* conv = [self getCurrentConversation];
509
510 if (conv == nil)
511 return 0;
512
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500513 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500514
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500515 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500516
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400517 auto interaction = it->second;
Anthony Léonard2382b562017-12-13 15:51:28 -0500518
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400519 MessageSequencing sequence = [self computeSequencingFor:row];
520
521 bool shouldDisplayTime = (sequence == FIRST_WITH_TIME || sequence == SINGLE_WITH_TIME) ? YES : NO;
522
523
524 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
525
526 if( interaction.status == lrc::api::interaction::Status::TRANSFER_FINISHED) {
527 NSString* name = @(interaction.body.c_str());
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400528 NSImage* image = [self getImageForFilePath:name];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400529 if (([name rangeOfString:@"/"].location == NSNotFound)) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400530 image = [self getImageForFilePath:[self getDataTransferPath:it->first]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400531 }
532 if (image != nil) {
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400533 CGFloat widthScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.width;
534 CGFloat heightScaleFactor = MAX_TRANSFERED_IMAGE_SIZE / image.size.height;
535 CGFloat heigt = 0;
536 if((widthScaleFactor >= 1) && (heightScaleFactor >= 1)) {
537 heigt = image.size.height;
538 } else {
539 CGFloat scale = MIN(widthScaleFactor, heightScaleFactor);
540 heigt = image.size.height * scale;
541 }
542 return heigt + TIME_BOX_HEIGHT;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400543 }
544 }
545 return BUBBLE_HEIGHT_FOR_TRANSFERED_FILE + TIME_BOX_HEIGHT;
546 }
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500547
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500548 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400549 return GENERIC_CELL_HEIGHT;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500550
Anthony Léonard2382b562017-12-13 15:51:28 -0500551 // TODO Implement interactions other than messages
552 if(interaction.type != lrc::api::interaction::Type::TEXT) {
553 return 0;
554 }
555
Kateryna Kostiuka0f16862018-05-04 09:11:41 -0400556 NSString *text = @(interaction.body.c_str());
557 text = [text removeEmptyLinesAtBorders];
558
559 CGSize messageSize = [self sizeFor: text maxWidth:tableView.frame.size.width * 0.7];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400560 CGFloat singleLignMessageHeight = 15;
561
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400562 bool shouldApplyPadding = (sequence == FIRST_WITHOUT_TIME || sequence == SINGLE_WITHOUT_TIME) ? YES : NO;
563
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400564 if (shouldDisplayTime) {
565 return MAX(messageSize.height + TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2,
566 TIME_BOX_HEIGHT + MESSAGE_TEXT_PADDING * 2 + singleLignMessageHeight);
567 }
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400568 if(shouldApplyPadding) {
569 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2 + 15,
570 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2 + 15);
571 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400572 return MAX(messageSize.height + MESSAGE_TEXT_PADDING * 2,
573 singleLignMessageHeight + MESSAGE_TEXT_PADDING * 2);
574}
575
576#pragma mark - message view parameters
577
578-(NSString *) getDataTransferPath:(uint64_t)interactionId {
579 lrc::api::datatransfer::Info info = {};
580 convModel_->getTransferInfo(interactionId, info);
581 double convertData = static_cast<double>(info.totalSize);
582 return @(info.path.c_str());
583}
584
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400585-(NSImage*) getImageForFilePath: (NSString *) path {
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400586 if (path.length <= 0) {return nil;}
587 if (![[NSFileManager defaultManager] fileExistsAtPath: path]) {return nil;}
588 NSImage* transferedImage = [[NSImage alloc] initWithContentsOfFile: path];
Kateryna Kostiukefc665d2018-09-17 15:42:43 -0400589 return transferedImage;
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400590}
591
592-(CGSize) sizeFor:(NSString *) message maxWidth:(CGFloat) width {
593 CGFloat horizaontalMargin = 6;
Anthony Léonard2382b562017-12-13 15:51:28 -0500594 NSMutableAttributedString* msgAttString =
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400595 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@", message]
Anthony Léonard2382b562017-12-13 15:51:28 -0500596 attributes:[self messageAttributes]];
597
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400598 CGFloat finalWidth = MIN(msgAttString.size.width + horizaontalMargin * 2, width);
599 NSRect frame = NSMakeRect(0, 0, finalWidth, msgAttString.size.height);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400600 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400601 [[tv textStorage] setAttributedString:msgAttString];
602 [tv sizeToFit];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400603 return tv.frame.size;
604}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400605
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400606-(MessageSequencing) computeSequencingFor:(NSInteger) row {
607 auto* conv = [self getCurrentConversation];
608 if (conv == nil)
609 return SINGLE_WITHOUT_TIME;
610 auto it = conv->interactions.begin();
611 std::advance(it, row);
612 auto interaction = it->second;
613 if (interaction.type != lrc::api::interaction::Type::TEXT) {
614 return SINGLE_WITH_TIME;
615 }
616 if (row == 0) {
617 if (it == conv->interactions.end()) {
618 return SINGLE_WITH_TIME;
619 }
620 auto nextIt = it;
621 nextIt++;
622 auto nextInteraction = nextIt->second;
623 if ([self sequenceChangedFrom:interaction to: nextInteraction]) {
624 return SINGLE_WITH_TIME;
625 }
626 return FIRST_WITH_TIME;
627 }
628
629 if (row == conversationView.numberOfRows - 1) {
630 if(it == conv->interactions.begin()) {
631 return SINGLE_WITH_TIME;
632 }
633 auto previousIt = it;
634 previousIt--;
635 auto previousInteraction = previousIt->second;
636 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
637 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
638 if (!timeChanged && !authorChanged) {
639 return LAST_IN_SEQUENCE;
640 }
641 if (!timeChanged && authorChanged) {
642 return SINGLE_WITHOUT_TIME;
643 }
644 return SINGLE_WITH_TIME;
645 }
646 if(it == conv->interactions.begin() || it == conv->interactions.end()) {
647 return SINGLE_WITH_TIME;
648 }
649 auto previousIt = it;
650 previousIt--;
651 auto previousInteraction = previousIt->second;
652 auto nextIt = it;
653 nextIt++;
654 auto nextInteraction = nextIt->second;
655
656 bool timeChanged = [self sequenceTimeChangedFrom:interaction to:previousInteraction];
657 bool authorChanged = [self sequenceAuthorChangedFrom:interaction to:previousInteraction];
658 bool sequenceWillChange = [self sequenceChangedFrom:interaction to: nextInteraction];
Kateryna Kostiuk9d8b7922018-05-02 12:52:53 -0400659 if (previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER ||
660 previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
661 if(!sequenceWillChange) {
662 return FIRST_WITH_TIME;
663 }
664 return SINGLE_WITH_TIME;
665 }
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400666 if (!sequenceWillChange) {
667 if (!timeChanged && !authorChanged) {
668 return MIDDLE_IN_SEQUENCE;
669 }
670 if (timeChanged) {
671 return FIRST_WITH_TIME;
672 }
673 return FIRST_WITHOUT_TIME;
674 } if (!timeChanged && !authorChanged) {
675 return LAST_IN_SEQUENCE;
676 } if (timeChanged) {
677 return SINGLE_WITH_TIME;
678 }
679 return SINGLE_WITHOUT_TIME;
680}
681
682-(bool) sequenceChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
683 return ([self sequenceTimeChangedFrom:firstInteraction to:secondInteraction] || [self sequenceAuthorChangedFrom:firstInteraction to:secondInteraction]);
684}
685
686-(bool) sequenceTimeChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
687 bool timeChanged = NO;
688 NSDate* firstMessageTime = [NSDate dateWithTimeIntervalSince1970:firstInteraction.timestamp];
689 NSDate* secondMessageTime = [NSDate dateWithTimeIntervalSince1970:secondInteraction.timestamp];
690 bool hourComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitHour];
691 bool minutComp = [[NSCalendar currentCalendar] compareDate:firstMessageTime toDate:secondMessageTime toUnitGranularity:NSCalendarUnitMinute];
692 if(hourComp != NSOrderedSame || minutComp != NSOrderedSame) {
693 timeChanged = YES;
694 }
695 return timeChanged;
696}
697
698-(bool) sequenceAuthorChangedFrom:(lrc::api::interaction::Info) firstInteraction to:(lrc::api::interaction::Info) secondInteraction {
699 bool authorChanged = YES;
700 bool isOutgoing = lrc::api::interaction::isOutgoing(firstInteraction);
701 if ((secondInteraction.type == lrc::api::interaction::Type::TEXT) && (isOutgoing == lrc::api::interaction::isOutgoing(secondInteraction))) {
702 authorChanged = NO;
703 }
704 return authorChanged;
705}
706
707-(NSString *)timeForMessage:(NSDate*) msgTime {
708 NSDate *today = [NSDate date];
709 NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
Kateryna Kostiukaf6d5e22018-06-12 15:00:00 -0400710 [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale currentLocale] localeIdentifier]]];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400711 if ([[NSCalendar currentCalendar] compareDate:today
712 toDate:msgTime
713 toUnitGranularity:NSCalendarUnitYear]!= NSOrderedSame) {
714 return [NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle];
715 }
716
717 if ([[NSCalendar currentCalendar] compareDate:today
718 toDate:msgTime
719 toUnitGranularity:NSCalendarUnitDay]!= NSOrderedSame ||
720 [[NSCalendar currentCalendar] compareDate:today
721 toDate:msgTime
722 toUnitGranularity:NSCalendarUnitMonth]!= NSOrderedSame) {
723 [dateFormatter setDateFormat:@"MMM dd, HH:mm"];
724 return [dateFormatter stringFromDate:msgTime];
725 }
726
727 [dateFormatter setDateFormat:@"HH:mm"];
728 return [dateFormatter stringFromDate:msgTime];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400729}
730
Anthony Léonard2382b562017-12-13 15:51:28 -0500731#pragma mark - NSTableViewDataSource
732
733- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
734{
735 auto* conv = [self getCurrentConversation];
736
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500737 if (conv)
738 return conv->interactions.size();
739 else
740 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500741}
742
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400743#pragma mark - Text formatting
744
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400745- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400746{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400747 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400748 attrs[NSForegroundColorAttributeName] = [NSColor labelColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400749 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400750 return attrs;
751}
752
753- (NSParagraphStyle*) paragraphStyle
754{
755 /*
756 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
757 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
758 to copy, we use the default one.
759
760 The default values supplied by the default NSParagraphStyle are:
761 Alignment NSNaturalTextAlignment
762 Tab stops 12 left-aligned tabs, spaced by 28.0 points
763 Line break mode NSLineBreakByWordWrapping
764 All others 0.0
765 */
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400766 NSMutableParagraphStyle* aMutableParagraphStyle =
767 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
768 [aMutableParagraphStyle setHeadIndent:1.0];
769 [aMutableParagraphStyle setFirstLineHeadIndent:1.0];
770 return aMutableParagraphStyle;
771}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400772
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500773#pragma mark - Actions
774
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400775- (void)acceptIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500776 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
777 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
778 if (convModel_ && !convUid_.empty()) {
779 NSSavePanel* filePicker = [NSSavePanel savePanel];
780 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
781
782 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
783 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
784 convModel_->acceptTransfer(convUid_, interId, fullPath);
785 }
786 }
787}
788
Kateryna Kostiukae660fd2018-04-24 14:10:41 -0400789- (void)declineIncomingFile:(id)sender {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500790 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
791 if (convModel_ && !convUid_.empty()) {
792 convModel_->cancelTransfer(convUid_, inter);
793 }
794}
795
Kateryna Kostiuk0f0ba992018-06-07 14:22:58 -0400796- (void)imagePreview:(id)sender {
797 uint64_t interId;
798 if ([[sender superview] isKindOfClass:[IMTableCellView class]]) {
799 interId = [(IMTableCellView*)[sender superview] interaction];
800 } else if ([[[sender superview] superview] isKindOfClass:[IMTableCellView class]]) {
801 interId = [(IMTableCellView*)[[sender superview] superview] interaction];
802 } else {
803 return;
804 }
805 auto it = [self getCurrentConversation]->interactions.find(interId);
806 if (it == [self getCurrentConversation]->interactions.end()) {
807 return;
808 }
809 auto& interaction = it->second;
810 NSString* name = @(interaction.body.c_str());
811 if (([name rangeOfString:@"/"].location == NSNotFound)) {
812 name = [self getDataTransferPath:interId];
813 }
814 previewImage = name;
815 if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
816 [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
817 } else {
818 [[QLPreviewPanel sharedPreviewPanel] updateController];
819 [QLPreviewPanel sharedPreviewPanel].dataSource = self;
820 [[QLPreviewPanel sharedPreviewPanel] setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
821 [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
822 }
823}
824
825- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel {
826 return 1;
827}
828
829- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
830 return [NSURL fileURLWithPath:previewImage];
831}
832
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400833@end