blob: a44ba20457c8b09a206da005e7c488c9f0654509 [file] [log] [blame]
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -04001/*
2 * Copyright (C) 2015-2017 Savoir-faire Linux Inc.
3 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#import <QItemSelectionModel>
21#import <qstring.h>
22#import <QPixmap>
23#import <QtMacExtras/qmacfunctions.h>
24
25#import <media/media.h>
26#import <person.h>
27#import <media/text.h>
28#import <media/textrecording.h>
29#import <globalinstances.h>
30
31#import "MessagesVC.h"
32#import "QNSTreeController.h"
33#import "views/IMTableCellView.h"
34#import "views/MessageBubbleView.h"
35#import "INDSequentialTextSelectionManager.h"
36
37@interface MessagesVC () {
38
39 QNSTreeController* treeController;
40 __unsafe_unretained IBOutlet NSOutlineView* conversationView;
41
42}
43
44@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
45
46@end
47
48@implementation MessagesVC
49QAbstractItemModel* currentModel;
50
51-(void)setUpViewWithModel: (QAbstractItemModel*) model {
52
53 _selectionManager = [[INDSequentialTextSelectionManager alloc] init];
54
55 [self.selectionManager unregisterAllTextViews];
56
57 treeController = [[QNSTreeController alloc] initWithQModel:model];
58 [treeController setAvoidsEmptySelection:NO];
59 [treeController setChildrenKeyPath:@"children"];
60 [conversationView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
61 [conversationView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
62 [conversationView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
63 [conversationView scrollToEndOfDocument:nil];
64 currentModel = model;
65}
66
67#pragma mark - NSOutlineViewDelegate methods
68
69- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
70{
71 return YES;
72}
73
74- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
75{
76 return YES;
77}
78
79- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
80{
81 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
82 if(!qIdx.isValid()) {
83 return [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
84 }
85 auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
86 IMTableCellView* result;
87
88 if (dir == Media::Media::Direction::IN) {
89 result = [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
90 } else {
91 result = [outlineView makeViewWithIdentifier:@"RightMessageView" owner:self];
92 }
93
94 // check if the message first in incoming or outgoing messages sequence
95 Boolean isFirstInSequence = true;
96 int row = qIdx.row() - 1;
97 if(row >= 0) {
98 QModelIndex index = currentModel->index(row, 0);
99 if(index.isValid()) {
100 auto dirOld = qvariant_cast<Media::Media::Direction>(index.data((int)Media::TextRecording::Role::Direction));
101 isFirstInSequence = !(dirOld == dir);
102 }
103 }
104 [result.photoView setHidden:!isFirstInSequence];
105 result.msgBackground.needPointer = isFirstInSequence;
106 [result setup];
107
108 NSMutableAttributedString* msgAttString =
109 [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400110 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400111
112 NSAttributedString* timestampAttrString =
113 [[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400114 attributes:[self timestampAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400115
116
117 CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
118
119 finalWidth = MIN(finalWidth + 30, outlineView.frame.size.width * 0.7);
120
121 NSString* msgString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
122 NSString* dateString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
123
124 [msgAttString appendAttributedString:timestampAttrString];
125 [[result.msgView textStorage] appendAttributedString:msgAttString];
126 [result.msgView checkTextInDocument:nil];
127 [result updateWidthConstraint:finalWidth];
128 [result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
129 return result;
130}
131
132- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
133{
134 if (IMTableCellView* cellView = [outlineView viewAtColumn:0 row:row makeIfNecessary:NO]) {
135 [self.selectionManager registerTextView:cellView.msgView withUniqueIdentifier:@(row).stringValue];
136 }
137 [self.delegate newMessageAdded];
138}
139
140- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
141{
142 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
143 double someWidth = outlineView.frame.size.width * 0.7;
144
145 NSMutableAttributedString* msgAttString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400146 attributes:[self messageAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400147 NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
148 qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400149 attributes:[self timestampAttributes]];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400150
151 [msgAttString appendAttributedString:timestampAttrString];
152
153 NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
154 NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
155 [tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
156 [tv setAutomaticLinkDetectionEnabled:YES];
157 [[tv textStorage] setAttributedString:msgAttString];
158 [tv sizeToFit];
159
160 double height = tv.frame.size.height + 10;
161 return MAX(height, 50.0f);
162}
163
164#pragma mark - Text formatting
165
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400166- (NSMutableDictionary*) timestampAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400167{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400168 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400169 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400170 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
171 attrs[NSFontAttributeName] = systemFont;
172 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
173
174 return attrs;
175}
176
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400177- (NSMutableDictionary*) messageAttributes
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400178{
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400179 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
Kateryna Kostiuk33089872017-07-14 16:43:59 -0400180 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400181 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
182 attrs[NSFontAttributeName] = systemFont;
183 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
184
185 return attrs;
186}
187
188- (NSParagraphStyle*) paragraphStyle
189{
190 /*
191 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
192 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
193 to copy, we use the default one.
194
195 The default values supplied by the default NSParagraphStyle are:
196 Alignment NSNaturalTextAlignment
197 Tab stops 12 left-aligned tabs, spaced by 28.0 points
198 Line break mode NSLineBreakByWordWrapping
199 All others 0.0
200 */
201 NSMutableParagraphStyle* aMutableParagraphStyle =
202 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
203
204 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
205 // The numeric values below are in points (72 points per inch)
206 [aMutableParagraphStyle setLineSpacing:1.5];
207 [aMutableParagraphStyle setParagraphSpacing:5.0];
208 [aMutableParagraphStyle setHeadIndent:5.0];
209 [aMutableParagraphStyle setTailIndent:-5.0];
210 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
211 return aMutableParagraphStyle;
212}
213
214@end