blob: a38ba6724427b93b98374b68fae5e6c35c6b9c60 [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()]
110 attributes:[self messageAttributesFor:qIdx]];
111
112 NSAttributedString* timestampAttrString =
113 [[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
114 attributes:[self timestampAttributesFor:qIdx]];
115
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()]
146 attributes:[self messageAttributesFor:qIdx]];
147 NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
148 qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
149 attributes:[self timestampAttributesFor:qIdx]];
150
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
166- (NSMutableDictionary*) timestampAttributesFor:(QModelIndex) qIdx
167{
168 auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
169 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
170
171 if (dir == Media::Media::Direction::IN) {
172 attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
173 } else {
174 attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
175 }
176
177 NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
178 attrs[NSFontAttributeName] = systemFont;
179 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
180
181 return attrs;
182}
183
184- (NSMutableDictionary*) messageAttributesFor:(QModelIndex) qIdx
185{
186 auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
187 NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
188
189 if (dir == Media::Media::Direction::IN) {
190 attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
191 } else {
192 attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
193 }
194
195 NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
196 attrs[NSFontAttributeName] = systemFont;
197 attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
198
199 return attrs;
200}
201
202- (NSParagraphStyle*) paragraphStyle
203{
204 /*
205 The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
206 NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
207 to copy, we use the default one.
208
209 The default values supplied by the default NSParagraphStyle are:
210 Alignment NSNaturalTextAlignment
211 Tab stops 12 left-aligned tabs, spaced by 28.0 points
212 Line break mode NSLineBreakByWordWrapping
213 All others 0.0
214 */
215 NSMutableParagraphStyle* aMutableParagraphStyle =
216 [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
217
218 // Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
219 // The numeric values below are in points (72 points per inch)
220 [aMutableParagraphStyle setLineSpacing:1.5];
221 [aMutableParagraphStyle setParagraphSpacing:5.0];
222 [aMutableParagraphStyle setHeadIndent:5.0];
223 [aMutableParagraphStyle setTailIndent:-5.0];
224 [aMutableParagraphStyle setFirstLineHeadIndent:5.0];
225 return aMutableParagraphStyle;
226}
227
228@end