Commit 58276bcb authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Anthony Léonard

UI: update chat view UI

Now view used for sending and receiving messages during the call (ChatView)
is the same as for regular ConversationView.
Also this commit fix two problems:
1)remove black imprints of text views, that appeared after window resizing
2)set status "read" for messages arriving during call, so they don't appear
in SmartList as unread.

Change-Id: I6d0cb79878395d28cfc93491a9d4cab42ed89192
Reviewed-by: default avatarAnthony Léonard <anthony.leonard@savoirfairelinux.com>
parent a725d614
......@@ -168,6 +168,8 @@ SET(ringclient_CONTROLLERS
src/SendContactRequestWC.mm
src/AccBannedContactsVC.h
src/AccBannedContactsVC.mm
src/MessagesVC.h
src/MessagesVC.mm
)
SET(ringclient_BACKENDS
......@@ -202,7 +204,10 @@ SET(ringclient_VIEWS
src/views/ContactRequestCellView.h
src/views/ContactRequestCellView.mm
src/views/RoundedTextField.h
src/views/RoundedTextField.mm)
src/views/RoundedTextField.mm
src/views/MessageBubbleView.h
src/views/MessageBubbleView.mm
)
SET(ringclient_OTHERS
src/main.mm
......
......@@ -27,6 +27,8 @@
#import <media/textrecording.h>
#import <callmodel.h>
#import "MessagesVC.h"
@interface MediaConnectionsHolder : NSObject
@property QMetaObject::Connection newMediaAdded;
......@@ -36,20 +38,26 @@
@implementation MediaConnectionsHolder
@end
@interface ChatVC ()
@interface ChatVC () <MessagesVCDelegate>
{
IBOutlet MessagesVC* messagesViewVC;
}
@property (unsafe_unretained) IBOutlet NSTextView *chatView;
@property (unsafe_unretained) IBOutlet NSTextField *messageField;
@property (unsafe_unretained) IBOutlet NSButton *sendButton;
@property MediaConnectionsHolder* mediaHolder;
@end
@implementation ChatVC
@synthesize messageField,chatView,sendButton, mediaHolder;
@synthesize messageField,sendButton, mediaHolder;
- (void)awakeFromNib
{
......@@ -57,7 +65,7 @@
[self.view setWantsLayer:YES];
[self.view setLayer:[CALayer layer]];
[self.view.layer setBackgroundColor:[NSColor blackColor].CGColor];
[self.view.layer setBackgroundColor:[NSColor controlColor].CGColor];
mediaHolder = [[MediaConnectionsHolder alloc] init];
......@@ -66,11 +74,7 @@
[=](const QModelIndex &current, const QModelIndex &previous) {
[self setupChat];
});
// Override default style to add interline space
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 8;
[chatView setDefaultParagraphStyle:paragraphStyle];
messagesViewVC.delegate = self;
}
......@@ -101,58 +105,34 @@
if (media->type() == Media::Media::Type::TEXT) {
QObject::disconnect(mediaHolder.newMediaAdded);
[self parseChatModel:((Media::Text*)media)->recording()->instantMessagingModel()];
}
});
}
}
- (void) parseChatModel:(QAbstractItemModel *)model
{
QObject::disconnect(mediaHolder.newMessage);
[self.messageField setStringValue:@""];
self.message = @"";
[self.chatView.textStorage.mutableString setString:@""];
/* put all the messages in the im model into the text view */
for (int row = 0; row < model->rowCount(); ++row) {
[self appendNewMessage:model->index(row, 0)];
}
#pragma mark - MessagesVC delegate
/* append new messages */
mediaHolder.newMessage = QObject::connect(model,
&QAbstractItemModel::rowsInserted,
[self, model] (const QModelIndex &parent, int first, int last) {
for (int row = first; row <= last; ++row) {
[self appendNewMessage:model->index(row, 0, parent)];
}
});
}
-(void) newMessageAdded {
- (void) appendNewMessage:(const QModelIndex&) msgIdx
{
if (!msgIdx.isValid())
QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
if (!callIdx.isValid())
return;
Call* call = CallModel::instance().getCall(callIdx);
if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
auto textRecording = text->recording();
textRecording->setAllRead();
} else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
auto textRecording = text->recording();
textRecording->setAllRead();
}
}
NSString* message = msgIdx.data(Qt::DisplayRole).value<QString>().toNSString();
NSString* author = msgIdx.data((int)Media::TextRecording::Role::AuthorDisplayname).value<QString>().toNSString();
NSMutableAttributedString* attr = [[NSMutableAttributedString alloc] initWithString:
[NSString stringWithFormat:@"%@: %@\n",author, message]];
// put in bold type author name
[attr applyFontTraits:NSBoldFontMask range: NSMakeRange(0, [author length])];
[[chatView textStorage] appendAttributedString:attr];
// reapply paragraph style on all the text
NSRange range = NSMakeRange(0,[chatView textStorage].length);
[[self.chatView textStorage] addAttribute:NSParagraphStyleAttributeName
value:chatView.defaultParagraphStyle
range:range];
[chatView scrollRangeToVisible:NSMakeRange([[chatView string] length], 0)];
- (void) parseChatModel:(QAbstractItemModel *)model
{
[messagesViewVC setUpViewWithModel:model];
}
- (void) takeFocus
......
......@@ -43,17 +43,17 @@
#import "PhoneDirectoryModel.h"
#import "account.h"
#import "AvailableAccountModel.h"
#import "MessagesVC.h"
#import <QuartzCore/QuartzCore.h>
@interface ConversationVC () <NSOutlineViewDelegate> {
@interface ConversationVC () <NSOutlineViewDelegate, MessagesVCDelegate> {
__unsafe_unretained IBOutlet NSTextField* messageField;
QVector<ContactMethod*> contactMethods;
NSMutableString* textSelection;
QNSTreeController* treeController;
QMetaObject::Connection contactMethodChanged;
ContactMethod* selectedContactMethod;
SendContactRequestWC* sendRequestWC;
......@@ -62,11 +62,10 @@
__unsafe_unretained IBOutlet NSTextField* conversationTitle;
__unsafe_unretained IBOutlet NSTextField* emptyConversationPlaceHolder;
__unsafe_unretained IBOutlet IconButton* sendButton;
__unsafe_unretained IBOutlet NSOutlineView* conversationView;
__unsafe_unretained IBOutlet NSPopUpButton* contactMethodsPopupButton;
IBOutlet MessagesVC* messagesViewVC;
}
@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
@end
......@@ -82,7 +81,6 @@
[sendPanel setWantsLayer:YES];
[sendPanel setLayer:[CALayer layer]];
_selectionManager = [[INDSequentialTextSelectionManager alloc] init];
[self setupChat];
......@@ -106,8 +104,6 @@
return ;
}
[self.selectionManager unregisterAllTextViews];
[contactMethodsPopupButton removeAllItems];
for (auto cm : contactMethods) {
[contactMethodsPopupButton addItemWithTitle:cm->uri().toNSString()];
......@@ -145,8 +141,8 @@
}
- (IBAction)backPressed:(id)sender {
[conversationView setDelegate:nil];
RecentModel::instance().selectionModel()->clearCurrentIndex();
messagesViewVC.delegate = nil;
}
- (IBAction)openSendContactRequestWindow:(id)sender
......@@ -199,158 +195,6 @@
[CATransaction commit];
}
#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)];
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];
}
[result setup];
NSMutableAttributedString* msgAttString =
[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
attributes:[self messageAttributesFor:qIdx]];
NSAttributedString* timestampAttrString =
[[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributesFor:qIdx]];
CGFloat finalWidth = MAX(msgAttString.size.width, timestampAttrString.size.width);
finalWidth = MIN(finalWidth + 30, result.frame.size.width - result.photoView.frame.size.width - 30);
[msgAttString appendAttributedString:timestampAttrString];
[[result.msgView textStorage] appendAttributedString:msgAttString];
[result.msgView checkTextInDocument:nil];
[result.msgView setWantsLayer:YES];
result.msgView.layer.cornerRadius = 5.0f;
[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];
}
if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
[emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
txtRecording->setAllRead();
}
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
{
QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
double someWidth = outlineView.frame.size.width;
NSMutableAttributedString* msgAttString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",qIdx.data((int)Qt::DisplayRole).toString().toNSString()]
attributes:[self messageAttributesFor:qIdx]];
NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributesFor:qIdx]];
[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 + 20;
return MAX(height, 60.0f);
}
#pragma mark - Text formatting
- (NSMutableDictionary*) timestampAttributesFor:(QModelIndex) qIdx
{
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
if (dir == Media::Media::Direction::IN) {
attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
} else {
attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
}
NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
attrs[NSFontAttributeName] = systemFont;
attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
return attrs;
}
- (NSMutableDictionary*) messageAttributesFor:(QModelIndex) qIdx
{
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
if (dir == Media::Media::Direction::IN) {
attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
} else {
attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
}
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 setAlignment:NSLeftTextAlignment];
[aMutableParagraphStyle setLineSpacing:1.5];
[aMutableParagraphStyle setParagraphSpacing:5.0];
[aMutableParagraphStyle setHeadIndent:5.0];
[aMutableParagraphStyle setTailIndent:-5.0];
[aMutableParagraphStyle setFirstLineHeadIndent:5.0];
[aMutableParagraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
return aMutableParagraphStyle;
}
#pragma mark - NSTextFieldDelegate
......@@ -378,17 +222,19 @@
});
if (auto txtRecording = selectedContactMethod->textRecording()) {
treeController = [[QNSTreeController alloc] initWithQModel:txtRecording->instantMessagingModel()];
[treeController setAvoidsEmptySelection:NO];
[treeController setChildrenKeyPath:@"children"];
[conversationView setDelegate:self];
[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];
messagesViewVC.delegate = self;
[messagesViewVC setUpViewWithModel:txtRecording->instantMessagingModel()];
}
[conversationView scrollToEndOfDocument:nil];
}
#pragma mark - MessagesVC delegate
-(void) newMessageAdded {
if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
[emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
txtRecording->setAllRead();
}
}
@end
/*
* 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 <Cocoa/Cocoa.h>
@protocol MessagesVCDelegate
-(void)newMessageAdded;
@end
@interface MessagesVC : NSViewController
-(void)setUpViewWithModel: (QAbstractItemModel*) model;
@property (retain, nonatomic) id <MessagesVCDelegate> delegate;
@end
/*
* 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 messageAttributesFor:qIdx]];
NSAttributedString* timestampAttrString =
[[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributesFor:qIdx]];
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 messageAttributesFor:qIdx]];
NSAttributedString *timestampAttrString = [[NSAttributedString alloc] initWithString:
qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
attributes:[self timestampAttributesFor:qIdx]];
[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*) timestampAttributesFor:(QModelIndex) qIdx
{
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
if (dir == Media::Media::Direction::IN) {
attrs[NSForegroundColorAttributeName] = [NSColor grayColor];
} else {
attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
}
NSFont* systemFont = [NSFont systemFontOfSize:12.0f];
attrs[NSFontAttributeName] = systemFont;
attrs[NSParagraphStyleAttributeName] = [self paragraphStyle];
return attrs;
}
- (NSMutableDictionary*) messageAttributesFor:(QModelIndex) qIdx
{
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
NSMutableDictionary* attrs = [NSMutableDictionary dictionary];
if (dir == Media::Media::Direction::IN) {
attrs[NSForegroundColorAttributeName] = [NSColor blackColor];
} else {
attrs[NSForegroundColorAttributeName] = [NSColor whiteColor];
}
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
......@@ -18,11 +18,13 @@
*/
#import <Cocoa/Cocoa.h>
#import "MessageBubbleView.h"
@interface IMTableCellView : NSTableCellView
@property (nonatomic, strong) IBOutlet NSImageView* photoView;
@property (nonatomic, strong) IBOutlet NSTextView* msgView;
@property (nonatomic, strong) IBOutlet MessageBubbleView* msgBackground;
- (void) setup;
- (void) updateWidthConstraint:(CGFloat) newWidth;
......
......@@ -29,13 +29,23 @@
- (void) setup
{
if ([self.identifier isEqualToString:@"RightMessageView"]) {
[self.msgView setBackgroundColor:[NSColor ringBlue]];
self.msgBackground.pointerDirection = RIGHT;
self.msgBackground.bgColor = [NSColor ringBlue];
}
else {
self.msgBackground.pointerDirection = LEFT;
self.msgBackground.bgColor = [NSColor whiteColor];
}
[self.msgView setBackgroundColor:[NSColor clearColor]];
[self.msgView setString:@""];
[self.msgView setAutoresizingMask:NSViewWidthSizable];
[self.msgView setAutoresizingMask:NSViewHeightSizable];
[self.msgBackground setAutoresizingMask:NSViewWidthSizable];
[self.msgBackground setAutoresizingMask:NSViewHeightSizable];
[self.msgView setEnabledTextCheckingTypes:NSTextCheckingTypeLink];
[self.msgView setAutomaticLinkDetectionEnabled:YES];
}
}
- (void) updateWidthConstraint:(CGFloat) newWidth
{
......
/*
* 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 <Cocoa/Cocoa.h>
typedef NS_ENUM(NSInteger, PointerDirection) {
LEFT = 0,
RIGHT,
BLOCK,
};
@interface MessageBubbleView: NSView
@property NSColor* bgColor;
@property Boolean needPointer;
@property enum PointerDirection pointerDirection;
@end
/*
* 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 "MessageBubbleView.h"
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
#import "NSColor+RingTheme.h"
@implementation MessageBubbleView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGFloat radius = 6;
CGFloat minx = CGRectGetMinX(dirtyRect), midx = CGRectGetMidX(dirtyRect), maxx = CGRectGetMaxX(dirtyRect);
CGFloat miny = CGRectGetMinY(dirtyRect), midy = CGRectGetMidY(dirtyRect), maxy = CGRectGetMaxY(dirtyRect);
CGMutablePathRef outlinePath = CGPathCreateMutable();
if (self.pointerDirection == LEFT)
{
minx += 6;
CGPathMoveToPoint(outlinePath, nil, midx, miny);
CGPathAddArcToPoint(outlinePath, nil, maxx, miny, maxx, midy, radius);