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: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
diff --git a/src/ChatVC.mm b/src/ChatVC.mm
index c3712ab..7655f16 100644
--- a/src/ChatVC.mm
+++ b/src/ChatVC.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:@""];
+#pragma mark - MessagesVC delegate
 
-    /* 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)];
+-(void) newMessageAdded {
+
+    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();
     }
-
-    /* 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) appendNewMessage:(const QModelIndex&) msgIdx
+- (void) parseChatModel:(QAbstractItemModel *)model
+
 {
-    if (!msgIdx.isValid())
-        return;
-
-    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)];
-
+    [messagesViewVC setUpViewWithModel:model];
 }
 
 - (void) takeFocus
diff --git a/src/ConversationVC.mm b/src/ConversationVC.mm
index fb3d996..5739e0f 100644
--- a/src/ConversationVC.mm
+++ b/src/ConversationVC.mm
@@ -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
diff --git a/src/MessagesVC.h b/src/MessagesVC.h
new file mode 100644
index 0000000..6b5430f
--- /dev/null
+++ b/src/MessagesVC.h
@@ -0,0 +1,33 @@
+/*
+ *  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
diff --git a/src/MessagesVC.mm b/src/MessagesVC.mm
new file mode 100644
index 0000000..a38ba67
--- /dev/null
+++ b/src/MessagesVC.mm
@@ -0,0 +1,228 @@
+/*
+ *  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
diff --git a/src/views/IMTableCellView.h b/src/views/IMTableCellView.h
index 68fd839..110dfb6 100644
--- a/src/views/IMTableCellView.h
+++ b/src/views/IMTableCellView.h
@@ -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;
diff --git a/src/views/IMTableCellView.mm b/src/views/IMTableCellView.mm
index a9d9497..4b8bd07 100644
--- a/src/views/IMTableCellView.mm
+++ b/src/views/IMTableCellView.mm
@@ -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
 {
diff --git a/src/views/MessageBubbleView.h b/src/views/MessageBubbleView.h
new file mode 100644
index 0000000..7b87c85
--- /dev/null
+++ b/src/views/MessageBubbleView.h
@@ -0,0 +1,34 @@
+/*
+ *  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
diff --git a/src/views/MessageBubbleView.mm b/src/views/MessageBubbleView.mm
new file mode 100644
index 0000000..1634c8c
--- /dev/null
+++ b/src/views/MessageBubbleView.mm
@@ -0,0 +1,80 @@
+/*
+ *  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);
+        CGPathAddArcToPoint(outlinePath, nil, maxx, maxy, midx, maxy, radius);
+        CGPathAddArcToPoint(outlinePath, nil, minx, maxy, minx, midy, radius);
+        if(self.needPointer) {
+            CGPathAddLineToPoint(outlinePath, nil, minx, maxy - 20);
+            CGPathAddLineToPoint(outlinePath, nil, minx - 6, maxy - 15);
+            CGPathAddLineToPoint(outlinePath, nil, minx, maxy - 10);
+        }
+
+        CGPathAddArcToPoint(outlinePath, nil, minx, miny, midx, miny, radius);
+        CGPathCloseSubpath(outlinePath);
+    }
+    else
+    {
+        maxx-=6;
+        CGPathMoveToPoint(outlinePath, nil, midx, miny);
+        CGPathAddArcToPoint(outlinePath, nil, minx, miny, minx, midy, radius);
+        CGPathAddArcToPoint(outlinePath, nil, minx, maxy, midx, maxy, radius);
+        CGPathAddArcToPoint(outlinePath, nil, maxx, maxy, maxx, midy, radius);
+        if(self.needPointer) {
+            CGPathAddLineToPoint(outlinePath, nil, maxx, maxy - 20);
+            CGPathAddLineToPoint(outlinePath, nil, maxx + 6, maxy - 15);
+            CGPathAddLineToPoint(outlinePath, nil, maxx, maxy - 10);
+        }
+        CGPathAddArcToPoint(outlinePath, nil, maxx, miny, midx, miny, radius);
+        CGPathCloseSubpath(outlinePath);
+    }
+    CGContextSetShadowWithColor(context, CGSizeMake(0,1), 1, [NSColor lightGrayColor].CGColor);
+    CGContextAddPath(context, outlinePath);
+    CGContextFillPath(context);
+
+    CGContextAddPath(context, outlinePath);
+    CGContextClip(context);
+    if(self.bgColor) {
+        CGContextSetFillColorWithColor(context, self.bgColor.CGColor);
+        CGContextStrokePath(context);
+        NSRectFill(dirtyRect);
+    }
+}
+@end