blob: cdf061340d868fab6808a1703b270431c3bc9c96 [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());
Anthony Léonard64e19672018-01-18 16:40:34 -0500285 if (isOutgoing) {
286 [result.photoView setImage:[NSImage imageNamed:@"default_user_icon"]];
287 } else {
288 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
289 }
290
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400291 return result;
292}
293
Anthony Léonard2382b562017-12-13 15:51:28 -0500294- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400295{
Anthony Léonard2382b562017-12-13 15:51:28 -0500296 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400297
Anthony Léonard2382b562017-12-13 15:51:28 -0500298 auto* conv = [self getCurrentConversation];
299
300 if (conv == nil)
301 return 0;
302
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500303 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500304
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500305 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500306
307 auto& interaction = it->second;
308
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500309 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER)
310 return 52.0;
311
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500312 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
313 return 27.0;
314
Anthony Léonard2382b562017-12-13 15:51:28 -0500315 // TODO Implement interactions other than messages
316 if(interaction.type != lrc::api::interaction::Type::TEXT) {
317 return 0;
318 }
319
320 NSMutableAttributedString* msgAttString =
321 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
322 attributes:[self messageAttributes]];
323
324 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
325 NSAttributedString* timestampAttrString =
326 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
327 attributes:[self timestampAttributes]];
328
329 [msgAttString appendAttributedString:timestampAttrString];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400330
331 [msgAttString appendAttributedString:timestampAttrString];
332
333 NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
334 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
335 [tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
336 [tv setAutomaticLinkDetectionEnabled:YES];
337 [[tv textStorage] setAttributedString:msgAttString];
338 [tv sizeToFit];
339
340 double height = tv.frame.size.height + 10;
341 return MAX(height, 50.0f);
342}
343
Anthony Léonard2382b562017-12-13 15:51:28 -0500344#pragma mark - NSTableViewDataSource
345
346- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
347{
348 auto* conv = [self getCurrentConversation];
349
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500350 if (conv)
351 return conv->interactions.size();
352 else
353 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500354}
355
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400356#pragma mark - Text formatting
357
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400358- (NSMutableDictionary*) timestampAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400359{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400360 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400361 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400362 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
363 attrs[NSFontAttributeName] = systemFont;
364 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
365
366 return attrs;
367}
368
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400369- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400370{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400371 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400372 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400373 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
374 attrs[NSFontAttributeName] = systemFont;
375 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
376
377 return attrs;
378}
379
380- (NSParagraphStyle*) paragraphStyle
381{
382 /*
383 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
384 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
385 to copy, we use the default one.
386
387 The default values supplied by the default NSParagraphStyle are:
388 Alignment NSNaturalTextAlignment
389 Tab stops 12 left-aligned tabs, spaced by 28.0 points
390 Line break mode NSLineBreakByWordWrapping
391 All others 0.0
392 */
393 NSMutableParagraphStyle* aMutableParagraphStyle =
394 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
395
396 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
397 // The numeric values below are in points (72 points per inch)
398 [aMutableParagraphStyle setLineSpacing:1.5];
399 [aMutableParagraphStyle setParagraphSpacing:5.0];
400 [aMutableParagraphStyle setHeadIndent:5.0];
401 [aMutableParagraphStyle setTailIndent:-5.0];
402 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
403 return aMutableParagraphStyle;
404}
405
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500406#pragma mark - Actions
407
408- (IBAction)acceptIncomingFile:(id)sender {
409 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
410 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
411 if (convModel_ && !convUid_.empty()) {
412 NSSavePanel* filePicker = [NSSavePanel savePanel];
413 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
414
415 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
416 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
417 convModel_->acceptTransfer(convUid_, interId, fullPath);
418 }
419 }
420}
421
422- (IBAction)declineIncomingFile:(id)sender {
423 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
424 if (convModel_ && !convUid_.empty()) {
425 convModel_->cancelTransfer(convUid_, inter);
426 }
427}
428
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400429@end