blob: a44ba20457c8b09a206da005e7c488c9f0654509 [file] [log] [blame]
/*
* Copyright (C) 2015-2017 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <QItemSelectionModel>
#import <qstring.h>
#import <QPixmap>
#import <QtMacExtras/qmacfunctions.h>
#import <media/media.h>
#import <person.h>
#import <media/text.h>
#import <media/textrecording.h>
#import <globalinstances.h>
#import "MessagesVC.h"
#import "QNSTreeController.h"
#import "views/IMTableCellView.h"
#import "views/MessageBubbleView.h"
#import "INDSequentialTextSelectionManager.h"
@interface MessagesVC () {
QNSTreeController* treeController;
__unsafe_unretained IBOutlet NSOutlineView* conversationView;
}
@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
@end
@implementation MessagesVC
QAbstractItemModel* currentModel;
-(void)setUpViewWithModel: (QAbstractItemModel*) model {
_selectionManager = [[INDSequentialTextSelectionManager alloc] init];
[self.selectionManager unregisterAllTextViews];
treeController = [[QNSTreeController alloc] initWithQModel:model];
[treeController setAvoidsEmptySelection:NO];
[treeController setChildrenKeyPath:@"children"];
[conversationView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
[conversationView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
[conversationView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
[conversationView scrollToEndOfDocument:nil];
currentModel = model;
}
#pragma mark - NSOutlineViewDelegate methods
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
{
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
return YES;
}
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
if(!qIdx.isValid()) {
return [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
}
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
IMTableCellView* result;
if (dir == Media::Media::Direction::IN) {
result = [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
} else {
result = [outlineView makeViewWithIdentifier:@"RightMessageView" owner:self];
}
// check if the message first in incoming or outgoing messages sequence
Boolean isFirstInSequence = true;
int row = qIdx.row() - 1;
if(row >= 0) {
QModelIndex index = currentModel->index(row, 0);
if(index.isValid()) {
auto dirOld = qvariant_cast<Media::Media::Direction>(index.data((int)Media::TextRecording::Role::Direction));
isFirstInSequence = !(dirOld == dir);
}
}
[result.photoView setHidden:!isFirstInSequence];
result.msgBackground.needPointer = isFirstInSequence;
[result setup];
NSMutableAttributedString* msgAttString =
[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
attributes:[self messageAttributes]];
NSAttributedString* timestampAttrString =
[[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributes]];
CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
finalWidth = MIN(finalWidth + 30, outlineView.frame.size.width * 0.7);
NSString* msgString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
NSString* dateString = qIdx.data((int)Qt::DisplayRole).toString().toNSString();
[msgAttString appendAttributedString:timestampAttrString];
[[result.msgView textStorage] appendAttributedString:msgAttString];
[result.msgView checkTextInDocument:nil];
[result updateWidthConstraint:finalWidth];
[result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(qIdx.data(Qt::DecorationRole)))];
return result;
}
- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
{
if (IMTableCellView* cellView = [outlineView viewAtColumn:0 row:row makeIfNecessary:NO]) {
[self.selectionManager registerTextView:cellView.msgView withUniqueIdentifier:@(row).stringValue];
}
[self.delegate newMessageAdded];
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
double someWidth = outlineView.frame.size.width * 0.7;
NSMutableAttributedString* msgAttString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
attributes:[self messageAttributes]];
NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributes]];
[msgAttString appendAttributedString:timestampAttrString];
NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT);
NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
[tv setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
[tv setAutomaticLinkDetectionEnabled:YES];
[[tv textStorage] setAttributedString:msgAttString];
[tv sizeToFit];
double height = tv.frame.size.height + 10;
return MAX(height, 50.0f);
}
#pragma mark - Text formatting
- (NSMutableDictionary*) timestampAttributes
{
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
attrs[NSFontAttributeName] = systemFont;
attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
return attrs;
}
- (NSMutableDictionary*) messageAttributes
{
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
NSFont* systemFont = [NSFont systemFontOfSize:14.0f];
attrs[NSFontAttributeName] = systemFont;
attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
return attrs;
}
- (NSParagraphStyle*) paragraphStyle
{
/*
The only way to instantiate an NSMutableParagraphStyle is to mutably copy an
NSParagraphStyle. And since we don't have an existing NSParagraphStyle available
to copy, we use the default one.
The default values supplied by the default NSParagraphStyle are:
Alignment NSNaturalTextAlignment
Tab stops 12 left-aligned tabs, spaced by 28.0 points
Line break mode NSLineBreakByWordWrapping
All others 0.0
*/
NSMutableParagraphStyle* aMutableParagraphStyle =
[[NSParagraphStyle defaultParagraphStyle] mutableCopy];
// Now adjust our NSMutableParagraphStyle formatting to be whatever we want.
// The numeric values below are in points (72 points per inch)
[aMutableParagraphStyle setLineSpacing:1.5];
[aMutableParagraphStyle setParagraphSpacing:5.0];
[aMutableParagraphStyle setHeadIndent:5.0];
[aMutableParagraphStyle setTailIndent:-5.0];
[aMutableParagraphStyle setFirstLineHeadIndent:5.0];
return aMutableParagraphStyle;
}
@end