blob: 6b73375b7f137432dfcd409e6ba95ab728734416 [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:
154 case lrc::api::interaction::Status::TRANSFER_AWAITING:
155 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:
170 case lrc::api::interaction::Status::TRANSFER_AWAITING:
171 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:
179 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:self];
180 }
181 }
182
183 // Then status label is updated if needed
184 switch (status) {
185 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500186 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500187 break;
188 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500189 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500190 break;
191 case lrc::api::interaction::Status::TRANSFER_ERROR:
Anthony Léonard70638f02018-02-05 11:10:19 -0500192 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500193 }
194 return result;
195}
196
Anthony Léonard2382b562017-12-13 15:51:28 -0500197#pragma mark - NSTableViewDelegate methods
198- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400199{
200 return YES;
201}
202
Anthony Léonard2382b562017-12-13 15:51:28 -0500203- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400204{
Anthony Léonard2382b562017-12-13 15:51:28 -0500205 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400206}
207
Anthony Léonard2382b562017-12-13 15:51:28 -0500208- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400209{
Anthony Léonard2382b562017-12-13 15:51:28 -0500210 auto* conv = [self getCurrentConversation];
211
212 if (conv == nil)
213 return nil;
214
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500215 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500216
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500217 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500218
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400219 IMTableCellView* result;
220
Anthony Léonard2382b562017-12-13 15:51:28 -0500221 auto& interaction = it->second;
222
Anthony Léonard2382b562017-12-13 15:51:28 -0500223 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
224
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500225 switch (interaction.type) {
226 case lrc::api::interaction::Type::TEXT:
227 if (isOutgoing) {
228 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
229 } else {
230 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
231 }
Anthony Léonarddd85dee2018-02-19 14:34:35 -0500232 if (interaction.status == lrc::api::interaction::Status::UNREAD)
233 convModel_->setInteractionRead(convUid_, it->first);
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500234 break;
235 case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER:
236 case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
237 result = [self makeViewforTransferStatus:interaction.status type:interaction.type tableView:tableView];
238 break;
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500239 case lrc::api::interaction::Type::CONTACT:
240 case lrc::api::interaction::Type::CALL:
241 return [self makeGenericInteractionViewForTableView:tableView withText:@(interaction.body.c_str())];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500242 default: // If interaction is not of a known type
243 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400244 }
245
246 // check if the message first in incoming or outgoing messages sequence
247 Boolean isFirstInSequence = true;
Anthony Léonard2382b562017-12-13 15:51:28 -0500248 if (it != conv->interactions.begin()) {
249 auto previousIt = it;
250 previousIt--;
251 auto& previousInteraction = previousIt->second;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500252 if ((previousInteraction.type == lrc::api::interaction::Type::TEXT
253 || previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
254 || previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) && (isOutgoing == lrc::api::interaction::isOutgoing(previousInteraction)))
Anthony Léonard2382b562017-12-13 15:51:28 -0500255 isFirstInSequence = false;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400256 }
257 [result.photoView setHidden:!isFirstInSequence];
258 result.msgBackground.needPointer = isFirstInSequence;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500259 [result setupForInteraction:it->first];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400260
261 NSMutableAttributedString* msgAttString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500262 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400263 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400264
Anthony Léonard2382b562017-12-13 15:51:28 -0500265 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400266 NSAttributedString* timestampAttrString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500267 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400268 attributes:[self timestampAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400269
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400270 CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
271
Anthony Léonard2382b562017-12-13 15:51:28 -0500272 finalWidth = MIN(finalWidth + 30, tableView.frame.size.width * 0.7);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400273
274 [msgAttString appendAttributedString:timestampAttrString];
275 [[result.msgView textStorage] appendAttributedString:msgAttString];
276 [result.msgView checkTextInDocument:nil];
277 [result updateWidthConstraint:finalWidth];
Anthony Léonard2382b562017-12-13 15:51:28 -0500278
279 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
Anthony Léonard64e19672018-01-18 16:40:34 -0500280 if (isOutgoing) {
281 [result.photoView setImage:[NSImage imageNamed:@"default_user_icon"]];
282 } else {
283 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
284 }
285
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400286 return result;
287}
288
Anthony Léonard2382b562017-12-13 15:51:28 -0500289- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400290{
Anthony Léonard2382b562017-12-13 15:51:28 -0500291 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400292
Anthony Léonard2382b562017-12-13 15:51:28 -0500293 auto* conv = [self getCurrentConversation];
294
295 if (conv == nil)
296 return 0;
297
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500298 auto it = conv->interactions.begin();
Anthony Léonard2382b562017-12-13 15:51:28 -0500299
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500300 std::advance(it, row);
Anthony Léonard2382b562017-12-13 15:51:28 -0500301
302 auto& interaction = it->second;
303
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500304 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER)
305 return 52.0;
306
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500307 if(interaction.type == lrc::api::interaction::Type::CONTACT || interaction.type == lrc::api::interaction::Type::CALL)
308 return 27.0;
309
Anthony Léonard2382b562017-12-13 15:51:28 -0500310 // TODO Implement interactions other than messages
311 if(interaction.type != lrc::api::interaction::Type::TEXT) {
312 return 0;
313 }
314
315 NSMutableAttributedString* msgAttString =
316 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
317 attributes:[self messageAttributes]];
318
319 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
320 NSAttributedString* timestampAttrString =
321 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
322 attributes:[self timestampAttributes]];
323
324 [msgAttString appendAttributedString:timestampAttrString];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400325
326 [msgAttString appendAttributedString:timestampAttrString];
327
328 NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
329 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
330 [tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
331 [tv setAutomaticLinkDetectionEnabled:YES];
332 [[tv textStorage] setAttributedString:msgAttString];
333 [tv sizeToFit];
334
335 double height = tv.frame.size.height + 10;
336 return MAX(height, 50.0f);
337}
338
Anthony Léonard2382b562017-12-13 15:51:28 -0500339#pragma mark - NSTableViewDataSource
340
341- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
342{
343 auto* conv = [self getCurrentConversation];
344
Anthony Léonardf2bb17d2018-02-15 17:18:09 -0500345 if (conv)
346 return conv->interactions.size();
347 else
348 return 0;
Anthony Léonard2382b562017-12-13 15:51:28 -0500349}
350
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400351#pragma mark - Text formatting
352
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400353- (NSMutableDictionary*) timestampAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400354{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400355 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400356 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400357 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
358 attrs[NSFontAttributeName] = systemFont;
359 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
360
361 return attrs;
362}
363
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400364- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400365{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400366 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400367 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400368 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
369 attrs[NSFontAttributeName] = systemFont;
370 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
371
372 return attrs;
373}
374
375- (NSParagraphStyle*) paragraphStyle
376{
377 /*
378 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
379 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
380 to copy, we use the default one.
381
382 The default values supplied by the default NSParagraphStyle are:
383 Alignment NSNaturalTextAlignment
384 Tab stops 12 left-aligned tabs, spaced by 28.0 points
385 Line break mode NSLineBreakByWordWrapping
386 All others 0.0
387 */
388 NSMutableParagraphStyle* aMutableParagraphStyle =
389 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
390
391 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
392 // The numeric values below are in points (72 points per inch)
393 [aMutableParagraphStyle setLineSpacing:1.5];
394 [aMutableParagraphStyle setParagraphSpacing:5.0];
395 [aMutableParagraphStyle setHeadIndent:5.0];
396 [aMutableParagraphStyle setTailIndent:-5.0];
397 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
398 return aMutableParagraphStyle;
399}
400
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500401#pragma mark - Actions
402
403- (IBAction)acceptIncomingFile:(id)sender {
404 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
405 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
406 if (convModel_ && !convUid_.empty()) {
407 NSSavePanel* filePicker = [NSSavePanel savePanel];
408 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
409
410 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
411 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
412 convModel_->acceptTransfer(convUid_, interId, fullPath);
413 }
414 }
415}
416
417- (IBAction)declineIncomingFile:(id)sender {
418 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
419 if (convModel_ && !convUid_.empty()) {
420 convModel_->cancelTransfer(convUid_, inter);
421 }
422}
423
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400424@end