blob: 91be13d591e1a6c9c2cecdf1094c2cc6bb8236d7 [file] [log] [blame]
Alexandre Lision0f66bd32016-01-18 11:30:45 -05001/*
2 * Copyright (C) 2016 Savoir-faire Linux Inc.
3 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#import "ConversationVC.h"
21
22#import <QItemSelectionModel>
23#import <qstring.h>
24#import <QPixmap>
25#import <QtMacExtras/qmacfunctions.h>
26
27#import <media/media.h>
28#import <recentmodel.h>
29#import <person.h>
30#import <contactmethod.h>
31#import <media/text.h>
32#import <media/textrecording.h>
33#import <callmodel.h>
34#import <globalinstances.h>
35
36#import "views/IconButton.h"
37#import "views/IMTableCellView.h"
38#import "views/NSColor+RingTheme.h"
39#import "QNSTreeController.h"
Alexandre Lision83180df2016-01-18 11:32:20 -050040#import "INDSequentialTextSelectionManager.h"
Alexandre Lision0f66bd32016-01-18 11:30:45 -050041#import "delegates/ImageManipulationDelegate.h"
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -040042#import "PhoneDirectoryModel.h"
43#import "account.h"
44#import "AvailableAccountModel.h"
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040045#import "MessagesVC.h"
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -040046
Alexandre Lision0f66bd32016-01-18 11:30:45 -050047
48#import <QuartzCore/QuartzCore.h>
49
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040050@interface ConversationVC () <NSOutlineViewDelegate, MessagesVCDelegate> {
Alexandre Lision0f66bd32016-01-18 11:30:45 -050051
Alexandre Lision0f66bd32016-01-18 11:30:45 -050052 __unsafe_unretained IBOutlet NSTextField* messageField;
53 QVector<ContactMethod*> contactMethods;
54 NSMutableString* textSelection;
55
Alexandre Lision05ca83c2016-11-24 19:31:08 -050056 QMetaObject::Connection contactMethodChanged;
57 ContactMethod* selectedContactMethod;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050058
59 __unsafe_unretained IBOutlet NSView* sendPanel;
60 __unsafe_unretained IBOutlet NSTextField* conversationTitle;
61 __unsafe_unretained IBOutlet NSTextField* emptyConversationPlaceHolder;
62 __unsafe_unretained IBOutlet IconButton* sendButton;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050063 __unsafe_unretained IBOutlet NSPopUpButton* contactMethodsPopupButton;
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -040064 __unsafe_unretained IBOutlet NSLayoutConstraint* sentContactRequestWidth;
65 __unsafe_unretained IBOutlet NSButton* sentContactRequestButton;
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -040066 IBOutlet MessagesVC* messagesViewVC;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050067}
68
Alexandre Lision83180df2016-01-18 11:32:20 -050069
Alexandre Lision0f66bd32016-01-18 11:30:45 -050070@end
71
72@implementation ConversationVC
73
Alexandre Lision4baba4c2016-02-11 13:00:57 -050074- (void)loadView {
75 [super loadView];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050076 // Do view setup here.
77 [self.view setWantsLayer:YES];
78 [self.view setLayer:[CALayer layer]];
79 [self.view.layer setBackgroundColor:[NSColor ringGreyHighlight].CGColor];
80 [self.view.layer setCornerRadius:5.0f];
81
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -040082 [messageField setFocusRingType:NSFocusRingTypeNone];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050083
84 [self setupChat];
85
86}
87
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -040088-(Account* ) chosenAccount
89{
90 QModelIndex index = AvailableAccountModel::instance().selectionModel()->currentIndex();
91 if(!index.isValid()) {
92 return nullptr;
93 }
94 Account* account = index.data(static_cast<int>(Account::Role::Object)).value<Account*>();
95 return account;
96}
97
Alexandre Lision0f66bd32016-01-18 11:30:45 -050098- (void) initFrame
99{
100 [self.view setFrame:self.view.superview.bounds];
101 [self.view setHidden:YES];
102 self.view.layer.position = self.view.frame.origin;
103}
104
105- (void) setupChat
106{
107 QObject::connect(RecentModel::instance().selectionModel(),
108 &QItemSelectionModel::currentChanged,
109 [=](const QModelIndex &current, const QModelIndex &previous) {
110
111 contactMethods = RecentModel::instance().getContactMethods(current);
112 if (contactMethods.isEmpty()) {
113 return ;
114 }
115
116 [contactMethodsPopupButton removeAllItems];
117 for (auto cm : contactMethods) {
Anthony LĂ©onarda63112f2017-07-19 11:25:38 -0400118 [contactMethodsPopupButton addItemWithTitle:cm->bestId().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500119 }
120
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -0400121 BOOL isSMultipleCM = (contactMethods.length() > 1);
122
123 [contactMethodsPopupButton setEnabled:isSMultipleCM];
124 [contactMethodsPopupButton setBordered:isSMultipleCM];
125 [[contactMethodsPopupButton cell] setArrowPosition: !isSMultipleCM ? NSPopUpNoArrow : NSPopUpArrowAtBottom];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500126
127 [emptyConversationPlaceHolder setHidden:NO];
128 // Select first cm
129 [contactMethodsPopupButton selectItemAtIndex:0];
130 [self itemChanged:contactMethodsPopupButton];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500131 });
132}
133
134- (IBAction)sendMessage:(id)sender
135{
136 /* make sure there is text to send */
137 NSString* text = self.message;
138 if (text && text.length > 0) {
139 QMap<QString, QString> messages;
140 messages["text/plain"] = QString::fromNSString(text);
141 contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->sendOfflineTextMessage(messages);
142 self.message = @"";
143 }
144}
145
146- (IBAction)placeCall:(id)sender
147{
148 if(auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])) {
149 auto c = CallModel::instance().dialingCall();
150 c->setPeerContactMethod(cm);
151 c << Call::Action::ACCEPT;
152 CallModel::instance().selectCall(c);
153 }
154}
155
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500156- (IBAction)backPressed:(id)sender {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500157 RecentModel::instance().selectionModel()->clearCurrentIndex();
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400158 messagesViewVC.delegate = nil;
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500159}
160
Kateryna Kostiuk88071912017-07-11 14:41:01 -0400161- (IBAction)sendContactRequest:(id)sender
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -0400162{
Kateryna Kostiuk88071912017-07-11 14:41:01 -0400163 auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem]);
164 if(cm) {
165 if(cm->account() == nullptr) {
166 cm->setAccount([self chosenAccount]);
167 }
168
169 if(cm->account() == nullptr) {
170 return;
171 }
172 cm->account()->sendContactRequest(cm);
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -0400173 }
174}
175
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500176# pragma mark private IN/OUT animations
177
178-(void) animateIn
179{
180 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
181 [self.view setHidden:NO];
182
183 [CATransaction begin];
184 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
185 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
186 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
187 [animation setDuration:0.2f];
188 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
189 [self.view.layer addAnimation:animation forKey:animation.keyPath];
190
191 [CATransaction commit];
192}
193
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500194-(void) animateOut
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500195{
196 if(self.view.frame.origin.x < 0) {
197 return;
198 }
199
200 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
201 [CATransaction begin];
202 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
203 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
204 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
205 [animation setDuration:0.2f];
206 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
207
208 [CATransaction setCompletionBlock:^{
209 [self.view setHidden:YES];
210 }];
211 [self.view.layer addAnimation:animation forKey:animation.keyPath];
212
213 [self.view.layer setPosition:frame.origin];
214 [CATransaction commit];
215}
216
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500217#pragma mark - NSTextFieldDelegate
218
219- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
220{
221 if (commandSelector == @selector(insertNewline:) && self.message.length > 0) {
222 [self sendMessage:nil];
223 return YES;
224 }
225 return NO;
226}
227
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400228-(BOOL)shouldHideSendRequestBtn {
229 /*to send contact request we need to meet thre condition:
230 1)contact method has RING protocol
231 2)accound is used to send request is also RING
232 3)contact have not acceppt request yet*/
233 if(selectedContactMethod->protocolHint() != URI::ProtocolHint::RING) {
234 return YES;
235 }
236 if(selectedContactMethod->isConfirmed()) {
237 return YES;
238 }
239 if(selectedContactMethod->account()) {
240 return selectedContactMethod->account()->protocol() != Account::Protocol::RING;
241 }
242 if([self chosenAccount]) {
243 return [self chosenAccount]->protocol() != Account::Protocol::RING;
244 }
245 return NO;
246}
247
248-(void)updateSendButtonVisibility
249{
250 [sentContactRequestButton setHidden:[self shouldHideSendRequestBtn]];
251 sentContactRequestWidth.priority = [self shouldHideSendRequestBtn] ? 999: 250;
252}
253
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500254#pragma mark - NSPopUpButton item selection
255
256- (IBAction)itemChanged:(id)sender {
257 NSInteger index = [(NSPopUpButton *)sender indexOfSelectedItem];
258
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500259 selectedContactMethod = contactMethods.at(index);
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -0400260
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400261 [self updateSendButtonVisibility];
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -0400262
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500263 [conversationTitle setStringValue:selectedContactMethod->primaryName().toNSString()];
264 QObject::disconnect(contactMethodChanged);
265 contactMethodChanged = QObject::connect(selectedContactMethod,
266 &ContactMethod::changed,
267 [self] {
268 [conversationTitle setStringValue:selectedContactMethod->primaryName().toNSString()];
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400269 [self updateSendButtonVisibility];
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500270 });
271
272 if (auto txtRecording = selectedContactMethod->textRecording()) {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400273 messagesViewVC.delegate = self;
274 [messagesViewVC setUpViewWithModel:txtRecording->instantMessagingModel()];
Kateryna Kostiukb2645472017-07-05 17:44:27 -0400275 [self.view.window makeFirstResponder:messageField];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500276 }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500277}
278
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400279#pragma mark - MessagesVC delegate
280
281-(void) newMessageAdded {
282
283 if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
284 [emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
285 txtRecording->setAllRead();
286 }
287}
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500288
289@end