Commit 2382b56e authored by Anthony Léonard's avatar Anthony Léonard Committed by Kateryna Kostiuk

refactoring of messaging controller with new model

MessagesVC is now implemented using the new LRC model for
conversations.
 - Both views to display the messages (in call and off call)
   initialize their MessagesVC with the current conversation when
   needed.
 - A conversation caching system is introduced to not get the whole
   conversation::Info structure from LRC at each display request (once
   per message).

Change-Id: Ib520c1f88be78de37968d3d7741010f2c73f20ea
Reviewed-by: Kateryna Kostiuk's avatarKateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
parent be339114
......@@ -17,6 +17,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Cocoa/Cocoa.h>
#import <api/conversation.h>
#import <api/conversationmodel.h>
@interface ChatVC : NSViewController <NSTextFieldDelegate>
......@@ -27,6 +29,7 @@
*/
@property (retain) NSString* message;
- (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
- (void) takeFocus;
@end
/*
* Copyright (C) 2015-2016 Savoir-faire Linux Inc.
* Copyright (C) 2015-2017 Savoir-faire Linux Inc.
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
* Author: Anthony Léonard <anthony.leonard@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
......@@ -19,45 +20,25 @@
#import "ChatVC.h"
#import <QItemSelectionModel>
#import <qstring.h>
#import <media/media.h>
#import <media/text.h>
#import <media/textrecording.h>
#import <callmodel.h>
#import "MessagesVC.h"
@interface MediaConnectionsHolder : NSObject
@property QMetaObject::Connection newMediaAdded;
@property QMetaObject::Connection newMessage;
@end
@implementation MediaConnectionsHolder
@end
@interface ChatVC () <MessagesVCDelegate>
@interface ChatVC ()
{
IBOutlet MessagesVC* messagesViewVC;
IBOutlet MessagesVC* messagesViewVC;
std::string convUid_;
lrc::api::ConversationModel* convModel_;
}
@property (unsafe_unretained) IBOutlet NSTextField *messageField;
@property (unsafe_unretained) IBOutlet NSButton *sendButton;
@property MediaConnectionsHolder* mediaHolder;
@end
@implementation ChatVC
@synthesize messageField,sendButton, mediaHolder;
@synthesize messageField,sendButton;
- (void)awakeFromNib
{
......@@ -66,73 +47,14 @@
[self.view setWantsLayer:YES];
[self.view setLayer:[CALayer layer]];
[self.view.layer setBackgroundColor:[NSColor controlColor].CGColor];
mediaHolder = [[MediaConnectionsHolder alloc] init];
QObject::connect(CallModel::instance().selectionModel(),
&QItemSelectionModel::currentChanged,
[=](const QModelIndex &current, const QModelIndex &previous) {
[self setupChat];
});
messagesViewVC.delegate = self;
}
- (void) setupChat
-(void)setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model
{
QObject::disconnect(mediaHolder.newMediaAdded);
QObject::disconnect(mediaHolder.newMessage);
QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
if (!callIdx.isValid())
return;
Call* call = CallModel::instance().getCall(callIdx);
/* check if text media is already present */
if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
[self parseChatModel:text->recording()->instantMessagingModel()];
} else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
Media::Text *text = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
[self parseChatModel:text->recording()->instantMessagingModel()];
} else {
/* monitor media for messaging text messaging */
mediaHolder.newMediaAdded = QObject::connect(call,
&Call::mediaAdded,
[self] (Media::Media* media) {
if (media->type() == Media::Media::Type::TEXT) {
QObject::disconnect(mediaHolder.newMediaAdded);
[self parseChatModel:((Media::Text*)media)->recording()->instantMessagingModel()];
}
});
}
}
#pragma mark - MessagesVC delegate
-(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();
}
}
- (void) parseChatModel:(QAbstractItemModel *)model
convUid_ = convUid;
convModel_ = model;
{
[messagesViewVC setUpViewWithModel:model];
[messagesViewVC setConversationUid:convUid_ model:convModel_];
}
- (void) takeFocus
......@@ -141,19 +63,13 @@
}
- (IBAction)sendMessage:(id)sender {
QModelIndex callIdx = CallModel::instance().selectionModel()->currentIndex();
Call* call = CallModel::instance().getCall(callIdx);
/* make sure there is text to send */
NSString* text = self.message;
if (text && text.length > 0) {
QMap<QString, QString> messages;
messages["text/plain"] = QString::fromNSString(text);
call->addOutgoingMedia<Media::Text>()->send(messages);
// Empty the text after sending it
[self.messageField setStringValue:@""];
convModel_->sendMessage(convUid_, std::string([text UTF8String]));
self.message = @"";
[messageField setStringValue:@""];
[messagesViewVC newMessageSent];
}
}
......
......@@ -18,6 +18,8 @@
*/
#import <Cocoa/Cocoa.h>
#import <api/conversation.h>
#import <api/conversationmodel.h>
@interface ConversationVC : NSViewController
......@@ -32,4 +34,6 @@
*/
@property (retain) NSString* message;
- (void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel*)model;
@end
/*
* Copyright (C) 2016 Savoir-faire Linux Inc.
* Copyright (C) 2016-2017 Savoir-faire Linux Inc.
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
* Author: Anthony Léonard <anthony.leonard@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
......@@ -24,13 +25,7 @@
#import <QPixmap>
#import <QtMacExtras/qmacfunctions.h>
#import <media/media.h>
#import <recentmodel.h>
#import <person.h>
#import <contactmethod.h>
#import <media/text.h>
#import <media/textrecording.h>
#import <callmodel.h>
// LRC
#import <globalinstances.h>
#import "views/IconButton.h"
......@@ -43,22 +38,17 @@
#import "account.h"
#import "AvailableAccountModel.h"
#import "MessagesVC.h"
#import "utils.h"
#import <QuartzCore/QuartzCore.h>
@interface ConversationVC () <NSOutlineViewDelegate, MessagesVCDelegate> {
@interface ConversationVC () {
__unsafe_unretained IBOutlet NSTextField* messageField;
QVector<ContactMethod*> contactMethods;
NSMutableString* textSelection;
QMetaObject::Connection contactMethodChanged;
ContactMethod* selectedContactMethod;
__unsafe_unretained IBOutlet NSView* sendPanel;
__unsafe_unretained IBOutlet NSTextField* conversationTitle;
__unsafe_unretained IBOutlet NSTextField* emptyConversationPlaceHolder;
__unsafe_unretained IBOutlet IconButton* sendButton;
__unsafe_unretained IBOutlet NSPopUpButton* contactMethodsPopupButton;
__unsafe_unretained IBOutlet NSLayoutConstraint* sentContactRequestWidth;
......@@ -67,6 +57,15 @@
IBOutlet NSLayoutConstraint* titleHoverButtonConstraint;
IBOutlet NSLayoutConstraint* titleTopConstraint;
std::string convUid_;
const lrc::api::conversation::Info* cachedConv_;
lrc::api::ConversationModel* convModel_;
// Both are needed to invalidate cached conversation as pointer
// may not be referencing the same conversation anymore
QMetaObject::Connection modelSortedSignal_;
QMetaObject::Connection filterChangedSignal_;
}
......@@ -74,6 +73,68 @@
@implementation ConversationVC
-(const lrc::api::conversation::Info*) getCurrentConversation
{
if (convModel_ == nil || convUid_.empty())
return nil;
if (cachedConv_ != nil)
return cachedConv_;
auto& convQueue = convModel_->allFilteredConversations();
auto it = std::find_if(convQueue.begin(), convQueue.end(), [self](const lrc::api::conversation::Info& conv) {return conv.uid == convUid_;});
if (it != convQueue.end())
cachedConv_ = &(*it);
return cachedConv_;
}
-(void) setConversationUid:(const std::string)convUid model:(lrc::api::ConversationModel *)model {
if (convUid_ == convUid && convModel_ == model)
return;
cachedConv_ = nil;
convUid_ = convUid;
convModel_ = model;
[messagesViewVC setConversationUid:convUid_ model:convModel_];
if (convUid_.empty() || convModel_ == nil)
return;
// Signals tracking changes in conversation list, we need them as cached conversation can be invalid
// after a reordering.
QObject::disconnect(modelSortedSignal_);
QObject::disconnect(filterChangedSignal_);
modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
[self](){
cachedConv_ = nil;
});
filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
[self](){
cachedConv_ = nil;
});
auto* conv = [self getCurrentConversation];
if (conv == nil)
return;
// Setup UI elements according to new conversation
NSString* bestName = bestNameForConversation(*conv, *convModel_);
[conversationTitle setStringValue: bestName];
[contactMethodsPopupButton setEnabled:NO];
[contactMethodsPopupButton setBordered:NO];
BOOL hideCMPopupButton = [bestNameForConversation(*conv, *convModel_) isEqualTo:bestIDForConversation(*conv, *convModel_)];
[contactMethodsPopupButton setHidden:hideCMPopupButton];
[titleHoverButtonConstraint setActive:hideCMPopupButton];
[titleTopConstraint setActive:!hideCMPopupButton];
}
- (void)loadView {
[super loadView];
// Do view setup here.
......@@ -83,9 +144,6 @@
[self.view.layer setCornerRadius:5.0f];
[messageField setFocusRingType:NSFocusRingTypeNone];
[self setupChat];
}
-(Account* ) chosenAccount
......@@ -105,80 +163,26 @@
self.view.layer.position = self.view.frame.origin;
}
- (void) setupChat
{
QObject::connect(RecentModel::instance().selectionModel(),
&QItemSelectionModel::currentChanged,
[=](const QModelIndex &current, const QModelIndex &previous) {
contactMethods = RecentModel::instance().getContactMethods(current);
if (contactMethods.isEmpty()) {
return ;
}
[contactMethodsPopupButton removeAllItems];
for (auto cm : contactMethods) {
[contactMethodsPopupButton addItemWithTitle:cm->bestId().toNSString()];
}
BOOL isSMultipleCM = (contactMethods.length() > 1);
BOOL hideCMPopupButton = !isSMultipleCM && (contactMethods.first()->bestId() == contactMethods.first()->bestName());
[contactMethodsPopupButton setEnabled:isSMultipleCM];
[contactMethodsPopupButton setBordered:isSMultipleCM];
[contactMethodsPopupButton setHidden:hideCMPopupButton];
[[contactMethodsPopupButton cell] setArrowPosition: !isSMultipleCM ? NSPopUpNoArrow : NSPopUpArrowAtBottom];
[titleHoverButtonConstraint setActive:hideCMPopupButton];
[titleTopConstraint setActive:!hideCMPopupButton];
[emptyConversationPlaceHolder setHidden:NO];
// Select first cm
[contactMethodsPopupButton selectItemAtIndex:0];
[self itemChanged:contactMethodsPopupButton];
});
}
- (IBAction)sendMessage:(id)sender
{
auto* conv = [self getCurrentConversation];
/* make sure there is text to send */
NSString* text = self.message;
if (text && text.length > 0) {
QMap<QString, QString> messages;
messages["text/plain"] = QString::fromNSString(text);
contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->sendOfflineTextMessage(messages);
convModel_->sendMessage(conv->uid, std::string([text UTF8String]));
self.message = @"";
[messagesViewVC newMessageSent];
}
}
- (IBAction)placeCall:(id)sender
{
if(auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])) {
auto c = CallModel::instance().dialingCall();
c->setPeerContactMethod(cm);
c << Call::Action::ACCEPT;
CallModel::instance().selectCall(c);
}
auto* conv = [self getCurrentConversation];
convModel_->placeCall(conv->uid);
}
- (IBAction)backPressed:(id)sender {
RecentModel::instance().selectionModel()->clearCurrentIndex();
messagesViewVC.delegate = nil;
}
- (IBAction)sendContactRequest:(id)sender
{
auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem]);
if(cm) {
if(cm->account() == nullptr) {
cm->setAccount([self chosenAccount]);
}
if(cm->account() == nullptr) {
return;
}
cm->account()->sendContactRequest(cm);
}
[self animateOut];
}
# pragma mark private IN/OUT animations
......@@ -233,65 +237,5 @@
return NO;
}
-(BOOL)shouldHideSendRequestBtn {
/*to send contact request we need to meet thre condition:
1)contact method has RING protocol
2)accound is used to send request is also RING
3)contact have not acceppt request yet*/
if(selectedContactMethod->protocolHint() != URI::ProtocolHint::RING) {
return YES;
}
if(selectedContactMethod->isConfirmed()) {
return YES;
}
if(selectedContactMethod->account()) {
return selectedContactMethod->account()->protocol() != Account::Protocol::RING;
}
if([self chosenAccount]) {
return [self chosenAccount]->protocol() != Account::Protocol::RING;
}
return NO;
}
-(void)updateSendButtonVisibility
{
[sentContactRequestButton setHidden:[self shouldHideSendRequestBtn]];
sentContactRequestWidth.priority = [self shouldHideSendRequestBtn] ? 999: 250;
}
#pragma mark - NSPopUpButton item selection
- (IBAction)itemChanged:(id)sender {
NSInteger index = [(NSPopUpButton *)sender indexOfSelectedItem];
selectedContactMethod = contactMethods.at(index);
[self updateSendButtonVisibility];
[conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
QObject::disconnect(contactMethodChanged);
contactMethodChanged = QObject::connect(selectedContactMethod,
&ContactMethod::changed,
[self] {
[conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
[self updateSendButtonVisibility];
});
if (auto txtRecording = selectedContactMethod->textRecording()) {
messagesViewVC.delegate = self;
[messagesViewVC setUpViewWithModel:txtRecording->instantMessagingModel()];
[self.view.window makeFirstResponder:messageField];
}
}
#pragma mark - MessagesVC delegate
-(void) newMessageAdded {
if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
[emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
txtRecording->setAllRead();
}
}
@end
......@@ -18,6 +18,8 @@
*/
#import <Cocoa/Cocoa.h>
#import <api/conversationmodel.h>
#import <api/conversation.h>
@protocol MessagesVCDelegate
......@@ -27,7 +29,9 @@
@interface MessagesVC : NSViewController
-(void)setUpViewWithModel: (QAbstractItemModel*) model;
-(void)setConversationUid:(const std::string)convUid model:(const lrc::api::ConversationModel*)model;
-(void)newMessageSent;
@property (retain, nonatomic) id <MessagesVCDelegate> delegate;
@end
/*
* Copyright (C) 2015-2017 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Anthony Léonard <anthony.leonard@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
......@@ -18,27 +19,33 @@
*/
#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>
// LRC
#import <globalinstances.h>
#import <api/interaction.h>
#import "MessagesVC.h"
#import "QNSTreeController.h"
#import "views/IMTableCellView.h"
#import "views/MessageBubbleView.h"
#import "INDSequentialTextSelectionManager.h"
#import "delegates/ImageManipulationDelegate.h"
@interface MessagesVC () {
@interface MessagesVC () <NSTableViewDelegate, NSTableViewDataSource> {
QNSTreeController* treeController;
__unsafe_unretained IBOutlet NSOutlineView* conversationView;
__unsafe_unretained IBOutlet NSTableView* conversationView;
std::string convUid_;
const lrc::api::ConversationModel* convModel_;
const lrc::api::conversation::Info* cachedConv_;
QMetaObject::Connection newMessageSignal_;
// Both are needed to invalidate cached conversation as pointer
// may not be referencing the same conversation anymore
QMetaObject::Connection modelSortedSignal_;
QMetaObject::Connection filterChangedSignal_;
}
@property (nonatomic, strong, readonly) INDSequentialTextSelectionManager* selectionManager;
......@@ -46,107 +53,221 @@
@end
@implementation MessagesVC
QAbstractItemModel* currentModel;
-(void)setUpViewWithModel: (QAbstractItemModel*) model {
-(const lrc::api::conversation::Info*) getCurrentConversation
{
if (convModel_ == nil || convUid_.empty())
return nil;
if (cachedConv_ != nil)
return cachedConv_;
auto& convQueue = convModel_->allFilteredConversations();
_selectionManager = [[INDSequentialTextSelectionManager alloc] init];
auto it = std::find_if(convQueue.begin(), convQueue.end(), [self](const lrc::api::conversation::Info& conv) {return conv.uid == convUid_;});
[self.selectionManager unregisterAllTextViews];
if (it != convQueue.end())
cachedConv_ = &(*it);
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];
return cachedConv_;
}
-(void)setConversationUid:(const std::string)convUid model:(const lrc::api::ConversationModel *)model
{
if (convUid_ == convUid && convModel_ == model)
return;
cachedConv_ = nil;
convUid_ = convUid;
convModel_ = model;
// Signal triggered when messages are received
QObject::disconnect(newMessageSignal_);
newMessageSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::newUnreadMessage,
[self](const std::string& uid, uint64_t msgId, const lrc::api::interaction::Info& msg){
if (uid != convUid_)
return;
[conversationView reloadData];
[conversationView scrollToEndOfDocument:nil];
});
// Signals tracking changes in conversation list, we need them as cached conversation can be invalid
// after a reordering.
QObject::disconnect(modelSortedSignal_);
QObject::disconnect(filterChangedSignal_);
modelSortedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::modelSorted,
[self](){
cachedConv_ = nil;
});
filterChangedSignal_ = QObject::connect(convModel_, &lrc::api::ConversationModel::filterChanged,
[self](){
cachedConv_ = nil;
});
[conversationView reloadData];
[conversationView scrollToEndOfDocument:nil];
currentModel = model;
}
#pragma mark - NSOutlineViewDelegate methods
-(void)newMessageSent
{
[conversationView reloadData];
[conversationView scrollToEndOfDocument:nil];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
#pragma mark - NSTableViewDelegate methods
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
{
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return YES;
return NO;
}
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
if(!qIdx.isValid()) {
return [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
auto* conv = [self getCurrentConversation];
if (conv == nil)
return nil;
// HACK HACK HACK HACK HACK
// The following code has to be replaced when every views are implemented for every interaction types
// This is an iterator which "jumps over" any interaction which is not a text one.
// It behaves as if interaction list was only containing text interactions.
std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
{
int msgCount = 0;
it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
if (inter.second.type == lrc::api::interaction::Type::TEXT) {
if (msgCount == row) {
return true;
} else {
msgCount++;
return false;
}
}
return false;
});
}
auto dir = qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction));
if (it == conv->interactions.end())
return nil;
IMTableCellView* result;
if (dir == Media::Media::Direction::IN) {
result = [outlineView makeViewWithIdentifier:@"LeftMessageView" owner:self];
auto& interaction = it->second;
// TODO Implement interactions other than messages
if(interaction.type != lrc::api::interaction::Type::TEXT) {
return nil;
}
bool isOutgoing = lrc::api::interaction::isOutgoing(interaction);
if (isOutgoing) {
result = [tableView makeViewWithIdentifier:@"RightMessageView" owner:self];
} else {
result = [outlineView makeViewWithIdentifier:@"RightMessageView" owner:self];
result = [tableView makeViewWithIdentifier:@"LeftMessageView" 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);
}
if (it != conv->interactions.begin()) {
auto previousIt = it;
previousIt--;
auto& previousInteraction = previousIt->second;
if (previousInteraction.type == lrc::api::interaction::Type::TEXT && (isOutgoing == lrc::api::interaction::isOutgoing(previousInteraction)))
isFirstInSequence = false;
}
[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()]
[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",@(interaction.body.c_str())]
attributes:[self messageAttributes]];
NSDate *msgTime = [NSDate dateWithTimeIntervalSince1970:interaction.timestamp];
NSAttributedString* timestampAttrString =
[[NSAttributedString alloc] initWithString:qIdx.data((int)Media::TextRecording::Role::FormattedDate).toString().toNSString()
[[NSAttributedString alloc] initWithString:[NSDateFormatter localizedStringFromDate:msgTime dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]
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();
finalWidth = MIN(finalWidth + 30, tableView.frame.size.width * 0.7);
[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)))];
auto& imageManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
[result.photoView setImage:QtMac::toNSImage(qvariant_cast<QPixmap>(imageManip.conversationPhoto(*conv, convModel_->owner)))];
return result;
}
- (void)outlineView:(NSOutlineView *)outlineView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
{
if (IMTableCellView* cellView = [outlineView viewAtColumn:0 row:row makeIfNecessary:NO]) {
if (IMTableCellView* cellView = [tableView viewAtColumn:0 row:row makeIfNecessary:NO]) {
[self.selectionManager registerTextView:cellView.msgView withUniqueIdentifier:@(row).stringValue];
}
[self.delegate newMessageAdded];
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
double someWidth = outlineView.frame.size.width * 0.7;
double someWidth = tableView.frame.size.width * 0.7;
auto* conv = [self getCurrentConversation];
if (conv == nil)
return 0;
// HACK HACK HACK HACK HACK
// The following code has to be replaced when every views are implemented for every interaction types
// This is an iterator which "jumps over" any interaction which is not a text one.
// It behaves as if interaction list was only containing text interactions.
std::map<uint64_t, lrc::api::interaction::Info>::const_iterator it;
{
int msgCount = 0;
it = std::find_if(conv->interactions.begin(), conv->interactions.end(), [&msgCount, row](const std::pair<uint64_t, lrc::api::interaction::Info>& inter) {
if (inter.second.type == lrc::api::interaction::Type::TEXT) {
if (msgCount == row) {
return true;
} else {