blob: 73eb2c3e78da6dcd23b339eccfda6db8597f9f26 [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
57@implementation MessagesVC
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040058
Anthony Léonard2382b562017-12-13 15:51:28 -050059-(const lrc::api::conversation::Info*) getCurrentConversation
60{
61 if (convModel_ == nil || convUid_.empty())
62 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040063
Anthony Léonard2382b562017-12-13 15:51:28 -050064 if (cachedConv_ != nil)
65 return cachedConv_;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040066
Anthony Léonard2382b562017-12-13 15:51:28 -050067 auto& convQueue = convModel_->allFilteredConversations();
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040068
Anthony Léonard6f819752018-01-05 09:53:40 -050069 auto it = getConversationFromUid(convUid_, *convModel_);
Anthony Léonard2382b562017-12-13 15:51:28 -050070
71 if (it != convQueue.end())
72 cachedConv_ = &(*it);
73
74 return cachedConv_;
75}
76
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050077-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
Anthony Léonard2382b562017-12-13 15:51:28 -050078{
79 if (convUid_ == convUid && convModel_ == model)
80 return;
81
82 cachedConv_ = nil;
83 convUid_ = convUid;
84 convModel_ = model;
85
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050086 // Signal triggered when messages are received or their status updated
87 QObject::disconnect(newInteractionSignal_);
88 QObject::disconnect(interactionStatusUpdatedSignal_);
89 newInteractionSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newInteraction,
Anthony Léonardff0cb032018-01-25 10:52:10 -050090 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
Anthony Léonard2382b562017-12-13 15:51:28 -050091 if (uid != convUid_)
92 return;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050093 cachedConv_ = nil;
Anthony Léonard2382b562017-12-13 15:51:28 -050094 [conversationView reloadData];
95 [conversationView scrollToEndOfDocument:nil];
96 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -050097 interactionStatusUpdatedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::interactionStatusUpdated,
98 [self](const std::string& uid, uint64_t interactionId, const lrc::api::interaction::Info& interaction){
99 if (uid != convUid_)
100 return;
101 cachedConv_ = nil;
102 [conversationView reloadData];
103 [conversationView scrollToEndOfDocument:nil];
104 });
Anthony Léonard2382b562017-12-13 15:51:28 -0500105
106 // Signals tracking changes in conversation list, we need them as cached conversation can be invalid
107 // after a reordering.
108 QObject::disconnect(modelSortedSignal_);
109 QObject::disconnect(filterChangedSignal_);
110 modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
111 [self](){
112 cachedConv_ = nil;
113 });
114 filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
115 [self](){
116 cachedConv_ = nil;
117 });
118
119
120
121 [conversationView reloadData];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400122 [conversationView scrollToEndOfDocument:nil];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400123}
124
Anthony Léonard2382b562017-12-13 15:51:28 -0500125-(void)newMessageSent
126{
127 [conversationView reloadData];
128 [conversationView scrollToEndOfDocument:nil];
129}
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400130
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500131-(IMTableCellView*) makeViewforTransferStatus:(lrc::api::interaction::Status)status type:(lrc::api::interaction::Type)type tableView:(NSTableView*)tableView
132{
133 IMTableCellView* result;
134
135 // First, view is created
136 if (type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER) {
137 switch (status) {
138 case lrc::api::interaction::Status::TRANSFER_CREATED:
139 case lrc::api::interaction::Status::TRANSFER_AWAITING:
140 result = [tableView makeViewWithIdentifier:@"LeftIncomingFileView" owner:self];
141 break;
142 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
143 case lrc::api::interaction::Status::TRANSFER_ONGOING:
144 result = [tableView makeViewWithIdentifier:@"LeftOngoingFileView" owner:self];
145 [result.progressIndicator startAnimation:nil];
146 break;
147 case lrc::api::interaction::Status::TRANSFER_FINISHED:
148 case lrc::api::interaction::Status::TRANSFER_CANCELED:
149 case lrc::api::interaction::Status::TRANSFER_ERROR:
150 result = [tableView makeViewWithIdentifier:@"LeftFinishedFileView" owner:self];
151 }
152 } else if (type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
153 switch (status) {
154 case lrc::api::interaction::Status::TRANSFER_CREATED:
155 case lrc::api::interaction::Status::TRANSFER_AWAITING:
156 case lrc::api::interaction::Status::TRANSFER_ONGOING:
157 case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
158 result = [tableView makeViewWithIdentifier:@"RightOngoingFileView" owner:self];
159 [result.progressIndicator startAnimation:nil];
160 break;
161 case lrc::api::interaction::Status::TRANSFER_FINISHED:
162 case lrc::api::interaction::Status::TRANSFER_CANCELED:
163 case lrc::api::interaction::Status::TRANSFER_ERROR:
164 result = [tableView makeViewWithIdentifier:@"RightFinishedFileView" owner:self];
165 }
166 }
167
168 // Then status label is updated if needed
169 switch (status) {
170 case lrc::api::interaction::Status::TRANSFER_FINISHED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500171 [result.statusLabel setStringValue:NSLocalizedString(@"Success", @"File transfer successful label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500172 break;
173 case lrc::api::interaction::Status::TRANSFER_CANCELED:
Anthony Léonard70638f02018-02-05 11:10:19 -0500174 [result.statusLabel setStringValue:NSLocalizedString(@"Canceled", @"File transfer canceled label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500175 break;
176 case lrc::api::interaction::Status::TRANSFER_ERROR:
Anthony Léonard70638f02018-02-05 11:10:19 -0500177 [result.statusLabel setStringValue:NSLocalizedString(@"Failed", @"File transfer failed label")];
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500178 }
179 return result;
180}
181
Anthony Léonard2382b562017-12-13 15:51:28 -0500182#pragma mark - NSTableViewDelegate methods
183- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400184{
185 return YES;
186}
187
Anthony Léonard2382b562017-12-13 15:51:28 -0500188- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400189{
Anthony Léonard2382b562017-12-13 15:51:28 -0500190 return NO;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400191}
192
Anthony Léonard2382b562017-12-13 15:51:28 -0500193- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400194{
Anthony Léonard2382b562017-12-13 15:51:28 -0500195 auto* conv = [self getCurrentConversation];
196
197 if (conv == nil)
198 return nil;
199
200 // HACK HACK HACK HACK HACK
201 // The following code has to be replaced when every views are implemented for every interaction types
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500202 // This is an iterator which "jumps over" any interaction which is not a text or datatransfer one.
Anthony Léonard2382b562017-12-13 15:51:28 -0500203 // It behaves as if interaction list was only containing text interactions.
204 std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
205
206 {
207 int msgCount = 0;
208 it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500209 if (inter.second.type == lrc::api::interaction::Type::TEXT
210 || inter.second.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
211 || inter.second.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Anthony Léonard2382b562017-12-13 15:51:28 -0500212 if (msgCount == row) {
213 return true;
214 } else {
215 msgCount++;
216 return false;
217 }
218 }
219 return false;
220 });
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400221 }
Anthony Léonard2382b562017-12-13 15:51:28 -0500222
223 if (it == conv->interactions.end())
224 return nil;
225
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400226 IMTableCellView* result;
227
Anthony Léonard2382b562017-12-13 15:51:28 -0500228 auto& interaction = it->second;
229
Anthony Léonard2382b562017-12-13 15:51:28 -0500230 bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
231
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500232 switch (interaction.type) {
233 case lrc::api::interaction::Type::TEXT:
234 if (isOutgoing) {
235 result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
236 } else {
237 result = [tableView makeViewWithIdentifier:@"LeftMessageView" owner:self];
238 }
239 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;
244 default: // If interaction is not of a known type
245 return nil;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400246 }
247
248 // check if the message first in incoming or outgoing messages sequence
249 Boolean isFirstInSequence = true;
Anthony Léonard2382b562017-12-13 15:51:28 -0500250 if (it != conv->interactions.begin()) {
251 auto previousIt = it;
252 previousIt--;
253 auto& previousInteraction = previousIt->second;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500254 if ((previousInteraction.type == lrc::api::interaction::Type::TEXT
255 || previousInteraction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
256 || previousInteraction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) && (isOutgoing == lrc::api::interaction::isOutgoing(previousInteraction)))
Anthony Léonard2382b562017-12-13 15:51:28 -0500257 isFirstInSequence = false;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400258 }
259 [result.photoView setHidden:!isFirstInSequence];
260 result.msgBackground.needPointer = isFirstInSequence;
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500261 [result setupForInteraction:it->first];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400262
263 NSMutableAttributedString* msgAttString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500264 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400265 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400266
Anthony Léonard2382b562017-12-13 15:51:28 -0500267 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400268 NSAttributedString* timestampAttrString =
Anthony Léonard2382b562017-12-13 15:51:28 -0500269 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400270 attributes:[self timestampAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400271
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400272 CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
273
Anthony Léonard2382b562017-12-13 15:51:28 -0500274 finalWidth = MIN(finalWidth + 30, tableView.frame.size.width * 0.7);
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400275
276 [msgAttString appendAttributedString:timestampAttrString];
277 [[result.msgView textStorage] appendAttributedString:msgAttString];
278 [result.msgView checkTextInDocument:nil];
279 [result updateWidthConstraint:finalWidth];
Anthony Léonard2382b562017-12-13 15:51:28 -0500280
281 auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
Anthony Léonard64e19672018-01-18 16:40:34 -0500282 if (isOutgoing) {
283 [result.photoView setImage:[NSImage imageNamed:@"default_user_icon"]];
284 } else {
285 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
286 }
287
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400288 return result;
289}
290
Anthony Léonard2382b562017-12-13 15:51:28 -0500291- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400292{
Anthony Léonard2382b562017-12-13 15:51:28 -0500293 if (IMTableCellView* cellView = [tableView viewAtColumn:0 row:row makeIfNecessary:NO]) {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400294 [self.selectionManager registerTextView:cellView.msgView withUniqueIdentifier:@(row).stringValue];
295 }
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400296}
297
Anthony Léonard2382b562017-12-13 15:51:28 -0500298- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400299{
Anthony Léonard2382b562017-12-13 15:51:28 -0500300 double someWidth = tableView.frame.size.width * 0.7;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400301
Anthony Léonard2382b562017-12-13 15:51:28 -0500302 auto* conv = [self getCurrentConversation];
303
304 if (conv == nil)
305 return 0;
306
307 // HACK HACK HACK HACK HACK
308 // The following code has to be replaced when every views are implemented for every interaction types
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500309 // This is an iterator which "jumps over" any interaction which is not a text or datatransfer one.
Anthony Léonard2382b562017-12-13 15:51:28 -0500310 // It behaves as if interaction list was only containing text interactions.
311 std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
312
313 {
314 int msgCount = 0;
315 it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500316 if (inter.second.type == lrc::api::interaction::Type::TEXT
317 || inter.second.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
318 || inter.second.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER) {
Anthony Léonard2382b562017-12-13 15:51:28 -0500319 if (msgCount == row) {
320 return true;
321 } else {
322 msgCount++;
323 return false;
324 }
325 }
326 return false;
327 });
328 }
329
330 if (it == conv->interactions.end())
331 return 0;
332
333 auto& interaction = it->second;
334
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500335 if(interaction.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER)
336 return 52.0;
337
Anthony Léonard2382b562017-12-13 15:51:28 -0500338 // TODO Implement interactions other than messages
339 if(interaction.type != lrc::api::interaction::Type::TEXT) {
340 return 0;
341 }
342
343 NSMutableAttributedString* msgAttString =
344 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
345 attributes:[self messageAttributes]];
346
347 NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
348 NSAttributedString* timestampAttrString =
349 [[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
350 attributes:[self timestampAttributes]];
351
352 [msgAttString appendAttributedString:timestampAttrString];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400353
354 [msgAttString appendAttributedString:timestampAttrString];
355
356 NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
357 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
358 [tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
359 [tv setAutomaticLinkDetectionEnabled:YES];
360 [[tv textStorage] setAttributedString:msgAttString];
361 [tv sizeToFit];
362
363 double height = tv.frame.size.height + 10;
364 return MAX(height, 50.0f);
365}
366
Anthony Léonard2382b562017-12-13 15:51:28 -0500367#pragma mark - NSTableViewDataSource
368
369- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
370{
371 auto* conv = [self getCurrentConversation];
372
373 if (conv) {
374 int count;
375 count = std::count_if(conv->interactions.begin(), conv->interactions.end(), [](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500376 return inter.second.type == lrc::api::interaction::Type::TEXT
377 || inter.second.type == lrc::api::interaction::Type::INCOMING_DATA_TRANSFER
378 || inter.second.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER;
Anthony Léonard2382b562017-12-13 15:51:28 -0500379 });
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500380 NSLog(@"$$$ Interaction count: %d", count);
Anthony Léonard2382b562017-12-13 15:51:28 -0500381 return count;
382 }
383 return 0;
384
385#if 0
386 // TODO: Replace above code by the following one when every interaction types implemented
387 if (conv_) {
388 return conv_->interactions.size();
389 }
390#endif
391}
392
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400393#pragma mark - Text formatting
394
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400395- (NSMutableDictionary*) timestampAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400396{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400397 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400398 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400399 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
400 attrs[NSFontAttributeName] = systemFont;
401 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
402
403 return attrs;
404}
405
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400406- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400407{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400408 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400409 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400410 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
411 attrs[NSFontAttributeName] = systemFont;
412 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
413
414 return attrs;
415}
416
417- (NSParagraphStyle*) paragraphStyle
418{
419 /*
420 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
421 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
422 to copy, we use the default one.
423
424 The default values supplied by the default NSParagraphStyle are:
425 Alignment NSNaturalTextAlignment
426 Tab stops 12 left-aligned tabs, spaced by 28.0 points
427 Line break mode NSLineBreakByWordWrapping
428 All others 0.0
429 */
430 NSMutableParagraphStyle* aMutableParagraphStyle =
431 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
432
433 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
434 // The numeric values below are in points (72 points per inch)
435 [aMutableParagraphStyle setLineSpacing:1.5];
436 [aMutableParagraphStyle setParagraphSpacing:5.0];
437 [aMutableParagraphStyle setHeadIndent:5.0];
438 [aMutableParagraphStyle setTailIndent:-5.0];
439 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
440 return aMutableParagraphStyle;
441}
442
Anthony Léonarde7d62ed2018-01-25 10:51:47 -0500443#pragma mark - Actions
444
445- (IBAction)acceptIncomingFile:(id)sender {
446 auto interId = [(IMTableCellView*)[[sender superview] superview] interaction];
447 auto& inter = [self getCurrentConversation]->interactions.find(interId)->second;
448 if (convModel_ && !convUid_.empty()) {
449 NSSavePanel* filePicker = [NSSavePanel savePanel];
450 [filePicker setNameFieldStringValue:@(inter.body.c_str())];
451
452 if ([filePicker runModal] == NSFileHandlingPanelOKButton) {
453 const char* fullPath = [[filePicker URL] fileSystemRepresentation];
454 convModel_->acceptTransfer(convUid_, interId, fullPath);
455 }
456 }
457}
458
459- (IBAction)declineIncomingFile:(id)sender {
460 auto inter = [(IMTableCellView*)[[sender superview] superview] interaction];
461 if (convModel_ && !convUid_.empty()) {
462 convModel_->cancelTransfer(convUid_, inter);
463 }
464}
465
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400466@end