blob: 48921376ad1fde1023f349a07b8a5f6d57e36291 [file] [log] [blame]
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001/*
Anthony Léonarde7d62ed2018-01-25 10:51:47 -05002 * Copyright (C) 2015-2018 Savoir-faire Linux Inc.
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04003 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Anthony Léonard2382b562017-12-13 15:51:28 -05004 * Anthony Léonard <anthony.leonard@savoirfairelinux.com>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#import <QItemSelectionModel>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040022#import <QPixmap>
23#import <QtMacExtras/qmacfunctions.h>
24
Anthony Léonard2382b562017-12-13 15:51:28 -050025// LRC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040026#import <globalinstances.h>
Anthony Léonard2382b562017-12-13 15:51:28 -050027#import <api/interaction.h>
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040028
29#import "MessagesVC.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040030#import "views/IMTableCellView.h"
31#import "views/MessageBubbleView.h"
32#import "INDSequentialTextSelectionManager.h"
Anthony Léonard2382b562017-12-13 15:51:28 -050033#import "delegates/ImageManipulationDelegate.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050034#import "utils.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040035
Anthony Léonard2382b562017-12-13 15:51:28 -050036@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040037
Anthony Léonard2382b562017-12-13 15:51:28 -050038 __unsafe_unretained IBOutlet NSTableView* conversationView;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040039
Anthony Léonard2382b562017-12-13 15:51:28 -050040 std::string convUid_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050041 lrc::api::ConversationModel* convModel_;
Anthony Léonard2382b562017-12-13 15:51:28 -050042 const lrc::api::conversation::Info* cachedConv_;
43
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050044 QMetaObject::Connection newInteractionSignal_;
Anthony Léonard2382b562017-12-13 15:51:28 -050045
46 // Both are needed to invalidate cached conversation as pointer
47 // may not be referencing the same conversation anymore
48 QMetaObject::Connection modelSortedSignal_;
49 QMetaObject::Connection filterChangedSignal_;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050050 QMetaObject::Connection interactionStatusUpdatedSignal_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040051}
52
53@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
54
55@end
56
Anthony Léonardf2bb17d2018-02-15 17:18:09 -050057// Tags for view
58NSInteger const GENERIC_INT_TEXT_TAG = 100;
59
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040060@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040061
Anthony Léonard2382b562017-12-13 15:51:28 -050062-(const lrc::api::conversation::Info*) getCurrentConversation
63{
64 if (convModel_ == nil || convUid_.empty())
65 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040066
Anthony Léonard2382b562017-12-13 15:51:28 -050067 if (cachedConv_ != nil)
68 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040069
Anthony Léonard2382b562017-12-13 15:51:28 -050070 auto& convQueue = convModel_->allFilteredConversations();
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040071
Anthony Léonard6f819752018-01-05 09:53:40 -050072 auto it = getConversationFromUid(convUid_, *convModel_);
Anthony Léonard2382b562017-12-13 15:51:28 -050073
74 if (it != convQueue.end())
75 cachedConv_ = &(*it);
76
77 return cachedConv_;
78}
79
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050080-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -050081{
82 if (convUid_ == convUid && convModel_ == model)
83 return;
84
85 cachedConv_ = nil;
86 convUid_ = convUid;
87 convModel_ = model;
88
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050089 // Signal triggered when messages are received or their status updated
90 QObject::disconnect(newInteractionSignal_);
91 QObject::disconnect(interactionStatusUpdatedSignal_);
92 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Anthony Léonardff0cb032018-01-25 10:52:10 -050093 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Anthony Léonard2382b562017-12-13 15:51:28 -050094 if (uid != convUid_)
95 return;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050096 cachedConv_ = nil;
Anthony Léonard2382b562017-12-13 15:51:28 -050097 [conversationView reloadData];
98 [conversationView scrollToEndOfDocument:nil];
99 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500100 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
101 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
102 if (uid != convUid_)
103 return;
104 cachedConv_ = nil;
105 [conversationView reloadData];
106 [conversationView scrollToEndOfDocument:nil];
107 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500108
109 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
110 // after a reordering.
111 QObject::disconnect(modelSortedSignal_);
112 QObject::disconnect(filterChangedSignal_);
113 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
114 [self](){
115 cachedConv_ = nil;
116 });
117 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
118 [self](){
119 cachedConv_ = nil;
120 });
121
122
123
124 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400125 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400126}
127
Anthony Léonard2382b562017-12-13 15:51:28 -0500128-(void)newMessageSent
129{
130 [conversationView reloadData];
131 [conversationView scrollToEndOfDocument:nil];
132}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400133
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500134-(NSTableCellView*) makeGenericInteractionViewForTableView:(NSTableView*)tableView withText:(NSString*)text
135{
136 NSTableCellView* result = [tableView makeViewWithIdentifier:@"GenericInteractionView" owner:self];
137 NSTextField* textField = [result viewWithTag:GENERIC_INT_TEXT_TAG];
138
139 // TODO: Fix symbol in LRC
140 NSString* fixedString = [text stringByReplacingOccurrencesOfString:@"🕽" withString:@"📞"];
141 [textField setStringValue:fixedString];
142
143 return result;
144}
145
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500146-(IMTableCellView*) makeViewforTransferStatus:(lrc::api::interaction::Status)status type:(lrc::api::interaction::Type)type tableView:(NSTableView*)tableView
147{
148 IMTableCellView* result;
149
150 // First, view is created
151 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
152 switch (status) {
153 case lrc::api::interaction::Status::TRANSFER_CREATED:
Andreas Traczykc8920842018-03-22 17:02:01 -0400154 case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500155 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner:self];
156 break;
157 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
158 case lrc::api::interaction::Status::TRANSFER_ONGOING:
159 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:self];
160 [result.progressIndicator startAnimation:nil];
161 break;
162 case lrc::api::interaction::Status::TRANSFER_FINISHED:
163 case lrc::api::interaction::Status::TRANSFER_CANCELED:
164 case lrc::api::interaction::Status::TRANSFER_ERROR:
165 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:self];
166 }
167 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
168 switch (status) {
169 case lrc::api::interaction::Status::TRANSFER_CREATED:
Andreas Traczykc8920842018-03-22 17:02:01 -0400170 case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500171 case lrc::api::interaction::Status::TRANSFER_ONGOING:
172 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
173 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:self];
174 [result.progressIndicator startAnimation:nil];
175 break;
176 case lrc::api::interaction::Status::TRANSFER_FINISHED:
177 case lrc::api::interaction::Status::TRANSFER_CANCELED:
178 case lrc::api::interaction::Status::TRANSFER_ERROR:
Olivier Soldanoe521a182018-02-26 16:55:19 -0500179 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500180 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:self];
181 }
182 }
183
184 // Then status label is updated if needed
185 switch (status) {
186 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500187 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500188 break;
189 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500190 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500191 break;
192 case lrc::api::interaction::Status::TRANSFER_ERROR:
Anthony Léonard70638f02018-02-05 11:10:19 -0500193 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Olivier Soldanoe521a182018-02-26 16:55:19 -0500194 break;
195 case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
196 [result.statusLabel setStringValue:NSLocalizedString(@"Unjoinable", @"File transfer peer unjoinable label")];
197 break;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500198 }
199 return result;
200}
201
Anthony Léonard2382b562017-12-13 15:51:28 -0500202#pragma mark - NSTableViewDelegate methods
203- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400204{
205 return YES;
206}
207
Anthony Léonard2382b562017-12-13 15:51:28 -0500208- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400209{
Anthony Léonard2382b562017-12-13 15:51:28 -0500210 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400211}
212
Anthony Léonard2382b562017-12-13 15:51:28 -0500213- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400214{
Anthony Léonard2382b562017-12-13 15:51:28 -0500215 auto* conv = [self getCurrentConversation];
216
217 if (conv == nil)
218 return nil;
219
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500220 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500221
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500222 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500223
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400224 IMTableCellView* result;
225
Anthony Léonard2382b562017-12-13 15:51:28 -0500226 auto& interaction = it->second;
227
Anthony Léonard2382b562017-12-13 15:51:28 -0500228 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
229
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500230 switch (interaction.type) {
231 case lrc::api::interaction::Type::TEXT:
232 if (isOutgoing) {
233 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
234 } else {
235 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
236 }
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500237 if (interaction.status == lrc::api::interaction::Status::UNREAD)
238 convModel_->setInteractionRead(convUid_, it->first);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500239 break;
240 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
241 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
242 result = [self makeViewforTransferStatus:interaction.status type:interaction.type tableView:tableView];
243 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500244 case lrc::api::interaction::Type::CONTACT:
245 case lrc::api::interaction::Type::CALL:
246 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str())];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500247 default: // If interaction is not of a known type
248 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400249 }
250
251 // check if the message first in incoming or outgoing messages sequence
252 Boolean isFirstInSequence = true;
Anthony Léonard2382b562017-12-13 15:51:28 -0500253 if (it != conv->interactions.begin()) {
254 auto previousIt = it;
255 previousIt--;
256 auto& previousInteraction = previousIt->second;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500257 if ((previousInteraction.type == lrc::api::interaction::Type::TEXT
258 || previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
259 || previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) && (isOutgoing == lrc::api::interaction::isOutgoing(previousInteraction)))
Anthony Léonard2382b562017-12-13 15:51:28 -0500260 isFirstInSequence = false;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400261 }
262 [result.photoView setHidden:!isFirstInSequence];
263 result.msgBackground.needPointer = isFirstInSequence;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500264 [result setupForInteraction:it->first];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400265
266 NSMutableAttributedString* msgAttString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500267 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400268 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400269
Anthony Léonard2382b562017-12-13 15:51:28 -0500270 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400271 NSAttributedString* timestampAttrString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500272 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400273 attributes:[self timestampAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400274
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400275 CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
276
Anthony Léonard2382b562017-12-13 15:51:28 -0500277 finalWidth = MIN(finalWidth + 30, tableView.frame.size.width * 0.7);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400278
279 [msgAttString appendAttributedString:timestampAttrString];
280 [[result.msgView textStorage] appendAttributedString:msgAttString];
281 [result.msgView checkTextInDocument:nil];
282 [result updateWidthConstraint:finalWidth];
Anthony Léonard2382b562017-12-13 15:51:28 -0500283
284 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
Kateryna Kostiukdb95b472018-04-17 16:35:57 -0400285 if (!isOutgoing) {
286 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
Anthony Léonard64e19672018-01-18 16:40:34 -0500287 }
288
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400289 return result;
290}
291
Anthony Léonard2382b562017-12-13 15:51:28 -0500292- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400293{
Anthony Léonard2382b562017-12-13 15:51:28 -0500294 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400295
Anthony Léonard2382b562017-12-13 15:51:28 -0500296 auto* conv = [self getCurrentConversation];
297
298 if (conv == nil)
299 return 0;
300
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500301 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500302
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500303 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500304
305 auto& interaction = it->second;
306
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500307 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER)
308 return 52.0;
309
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500310 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
311 return 27.0;
312
Anthony Léonard2382b562017-12-13 15:51:28 -0500313 // TODO Implement interactions other than messages
314 if(interaction.type != lrc::api::interaction::Type::TEXT) {
315 return 0;
316 }
317
318 NSMutableAttributedString* msgAttString =
319 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
320 attributes:[self messageAttributes]];
321
322 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
323 NSAttributedString* timestampAttrString =
324 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
325 attributes:[self timestampAttributes]];
326
327 [msgAttString appendAttributedString:timestampAttrString];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400328
329 [msgAttString appendAttributedString:timestampAttrString];
330
331 NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
332 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
333 [tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
334 [tv setAutomaticLinkDetectionEnabled:YES];
335 [[tv textStorage] setAttributedString:msgAttString];
336 [tv sizeToFit];
337
338 double height = tv.frame.size.height + 10;
339 return MAX(height, 50.0f);
340}
341
Anthony Léonard2382b562017-12-13 15:51:28 -0500342#pragma mark - NSTableViewDataSource
343
344- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
345{
346 auto* conv = [self getCurrentConversation];
347
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500348 if (conv)
349 return conv->interactions.size();
350 else
351 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500352}
353
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400354#pragma mark - Text formatting
355
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400356- (NSMutableDictionary*) timestampAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400357{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400358 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400359 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400360 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
361 attrs[NSFontAttributeName] = systemFont;
362 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
363
364 return attrs;
365}
366
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400367- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400368{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400369 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400370 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400371 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
372 attrs[NSFontAttributeName] = systemFont;
373 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
374
375 return attrs;
376}
377
378- (NSParagraphStyle*) paragraphStyle
379{
380 /*
381 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
382 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
383 to copy, we use the default one.
384
385 The default values supplied by the default NSParagraphStyle are:
386 Alignment NSNaturalTextAlignment
387 Tab stops 12 left-aligned tabs, spaced by 28.0 points
388 Line break mode NSLineBreakByWordWrapping
389 All others 0.0
390 */
391 NSMutableParagraphStyle* aMutableParagraphStyle =
392 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
393
394 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
395 // The numeric values below are in points (72 points per inch)
396 [aMutableParagraphStyle setLineSpacing:1.5];
397 [aMutableParagraphStyle setParagraphSpacing:5.0];
398 [aMutableParagraphStyle setHeadIndent:5.0];
399 [aMutableParagraphStyle setTailIndent:-5.0];
400 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
401 return aMutableParagraphStyle;
402}
403
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500404#pragma mark - Actions
405
406- (IBAction)acceptIncomingFile:(id)sender {
407 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
408 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
409 if (convModel_ && !convUid_.empty()) {
410 NSSavePanel* filePicker = [NSSavePanel savePanel];
411 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
412
413 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
414 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
415 convModel_->acceptTransfer(convUid_, interId, fullPath);
416 }
417 }
418}
419
420- (IBAction)declineIncomingFile:(id)sender {
421 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
422 if (convModel_ && !convUid_.empty()) {
423 convModel_->cancelTransfer(convUid_, inter);
424 }
425}
426
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400427@end