blob: 23cf1574c8a27ced259f993a3a737db6680956e0 [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;
Anthony Léonardade9f9b2017-07-26 15:51:12 -040067
68 IBOutlet NSLayoutConstraint* titleHoverButtonConstraint;
69 IBOutlet NSLayoutConstraint* titleTopConstraint;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050070}
71
Alexandre Lision83180df2016-01-18 11:32:20 -050072
Alexandre Lision0f66bd32016-01-18 11:30:45 -050073@end
74
75@implementation ConversationVC
76
Alexandre Lision4baba4c2016-02-11 13:00:57 -050077- (void)loadView {
78 [super loadView];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050079 // Do view setup here.
80 [self.view setWantsLayer:YES];
81 [self.view setLayer:[CALayer layer]];
82 [self.view.layer setBackgroundColor:[NSColor ringGreyHighlight].CGColor];
83 [self.view.layer setCornerRadius:5.0f];
84
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -040085 [messageField setFocusRingType:NSFocusRingTypeNone];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050086
87 [self setupChat];
88
89}
90
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -040091-(Account* ) chosenAccount
92{
93 QModelIndex index = AvailableAccountModel::instance().selectionModel()->currentIndex();
94 if(!index.isValid()) {
95 return nullptr;
96 }
97 Account* account = index.data(static_cast<int>(Account::Role::Object)).value<Account*>();
98 return account;
99}
100
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500101- (void) initFrame
102{
103 [self.view setFrame:self.view.superview.bounds];
104 [self.view setHidden:YES];
105 self.view.layer.position = self.view.frame.origin;
106}
107
108- (void) setupChat
109{
110 QObject::connect(RecentModel::instance().selectionModel(),
111 &QItemSelectionModel::currentChanged,
112 [=](const QModelIndex &current, const QModelIndex &previous) {
113
114 contactMethods = RecentModel::instance().getContactMethods(current);
115 if (contactMethods.isEmpty()) {
116 return ;
117 }
118
119 [contactMethodsPopupButton removeAllItems];
120 for (auto cm : contactMethods) {
Anthony Léonarda63112f2017-07-19 11:25:38 -0400121 [contactMethodsPopupButton addItemWithTitle:cm->bestId().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500122 }
123
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -0400124 BOOL isSMultipleCM = (contactMethods.length() > 1);
Anthony Léonardade9f9b2017-07-26 15:51:12 -0400125 BOOL hideCMPopupButton = !isSMultipleCM && (contactMethods.first()->bestId() == contactMethods.first()->bestName());
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -0400126
127 [contactMethodsPopupButton setEnabled:isSMultipleCM];
128 [contactMethodsPopupButton setBordered:isSMultipleCM];
Anthony Léonardade9f9b2017-07-26 15:51:12 -0400129 [contactMethodsPopupButton setHidden:hideCMPopupButton];
Kateryna Kostiuk64d025a2017-07-14 11:30:44 -0400130 [[contactMethodsPopupButton cell] setArrowPosition: !isSMultipleCM ? NSPopUpNoArrow : NSPopUpArrowAtBottom];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500131
Anthony Léonardade9f9b2017-07-26 15:51:12 -0400132 [titleHoverButtonConstraint setActive:hideCMPopupButton];
133 [titleTopConstraint setActive:!hideCMPopupButton];
134
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500135 [emptyConversationPlaceHolder setHidden:NO];
136 // Select first cm
137 [contactMethodsPopupButton selectItemAtIndex:0];
138 [self itemChanged:contactMethodsPopupButton];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500139 });
140}
141
142- (IBAction)sendMessage:(id)sender
143{
144 /* make sure there is text to send */
145 NSString* text = self.message;
146 if (text && text.length > 0) {
147 QMap<QString, QString> messages;
148 messages["text/plain"] = QString::fromNSString(text);
149 contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->sendOfflineTextMessage(messages);
150 self.message = @"";
151 }
152}
153
154- (IBAction)placeCall:(id)sender
155{
156 if(auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])) {
157 auto c = CallModel::instance().dialingCall();
158 c->setPeerContactMethod(cm);
159 c << Call::Action::ACCEPT;
160 CallModel::instance().selectCall(c);
161 }
162}
163
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500164- (IBAction)backPressed:(id)sender {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500165 RecentModel::instance().selectionModel()->clearCurrentIndex();
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400166 messagesViewVC.delegate = nil;
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500167}
168
Kateryna Kostiuk88071912017-07-11 14:41:01 -0400169- (IBAction)sendContactRequest:(id)sender
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -0400170{
Kateryna Kostiuk88071912017-07-11 14:41:01 -0400171 auto cm = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem]);
172 if(cm) {
173 if(cm->account() == nullptr) {
174 cm->setAccount([self chosenAccount]);
175 }
176
177 if(cm->account() == nullptr) {
178 return;
179 }
180 cm->account()->sendContactRequest(cm);
Kateryna Kostiuk0ba1baf2017-05-12 16:51:27 -0400181 }
182}
183
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500184# pragma mark private IN/OUT animations
185
186-(void) animateIn
187{
188 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
189 [self.view setHidden:NO];
190
191 [CATransaction begin];
192 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
193 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
194 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
195 [animation setDuration:0.2f];
196 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
197 [self.view.layer addAnimation:animation forKey:animation.keyPath];
198
199 [CATransaction commit];
200}
201
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500202-(void) animateOut
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500203{
204 if(self.view.frame.origin.x < 0) {
205 return;
206 }
207
208 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
209 [CATransaction begin];
210 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
211 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
212 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
213 [animation setDuration:0.2f];
214 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
215
216 [CATransaction setCompletionBlock:^{
217 [self.view setHidden:YES];
218 }];
219 [self.view.layer addAnimation:animation forKey:animation.keyPath];
220
221 [self.view.layer setPosition:frame.origin];
222 [CATransaction commit];
223}
224
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500225#pragma mark - NSTextFieldDelegate
226
227- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
228{
229 if (commandSelector == @selector(insertNewline:) && self.message.length > 0) {
230 [self sendMessage:nil];
231 return YES;
232 }
233 return NO;
234}
235
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400236-(BOOL)shouldHideSendRequestBtn {
237 /*to send contact request we need to meet thre condition:
238 1)contact method has RING protocol
239 2)accound is used to send request is also RING
240 3)contact have not acceppt request yet*/
241 if(selectedContactMethod->protocolHint() != URI::ProtocolHint::RING) {
242 return YES;
243 }
244 if(selectedContactMethod->isConfirmed()) {
245 return YES;
246 }
247 if(selectedContactMethod->account()) {
248 return selectedContactMethod->account()->protocol() != Account::Protocol::RING;
249 }
250 if([self chosenAccount]) {
251 return [self chosenAccount]->protocol() != Account::Protocol::RING;
252 }
253 return NO;
254}
255
256-(void)updateSendButtonVisibility
257{
258 [sentContactRequestButton setHidden:[self shouldHideSendRequestBtn]];
259 sentContactRequestWidth.priority = [self shouldHideSendRequestBtn] ? 999: 250;
260}
261
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500262#pragma mark - NSPopUpButton item selection
263
264- (IBAction)itemChanged:(id)sender {
265 NSInteger index = [(NSPopUpButton *)sender indexOfSelectedItem];
266
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500267 selectedContactMethod = contactMethods.at(index);
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -0400268
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400269 [self updateSendButtonVisibility];
Kateryna Kostiuk78e1f122017-06-14 14:52:34 -0400270
Anthony Léonardade9f9b2017-07-26 15:51:12 -0400271 [conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500272 QObject::disconnect(contactMethodChanged);
273 contactMethodChanged = QObject::connect(selectedContactMethod,
274 &ContactMethod::changed,
275 [self] {
Anthony Léonardade9f9b2017-07-26 15:51:12 -0400276 [conversationTitle setStringValue:selectedContactMethod->bestName().toNSString()];
Kateryna Kostiuk4f403b62017-07-11 16:15:32 -0400277 [self updateSendButtonVisibility];
Alexandre Lision05ca83c2016-11-24 19:31:08 -0500278 });
279
280 if (auto txtRecording = selectedContactMethod->textRecording()) {
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400281 messagesViewVC.delegate = self;
282 [messagesViewVC setUpViewWithModel:txtRecording->instantMessagingModel()];
Kateryna Kostiukb2645472017-07-05 17:44:27 -0400283 [self.view.window makeFirstResponder:messageField];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500284 }
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500285}
286
Kateryna Kostiuk58276bc2017-06-07 08:50:48 -0400287#pragma mark - MessagesVC delegate
288
289-(void) newMessageAdded {
290
291 if (auto txtRecording = contactMethods.at([contactMethodsPopupButton indexOfSelectedItem])->textRecording()) {
292 [emptyConversationPlaceHolder setHidden:txtRecording->instantMessagingModel()->rowCount() > 0];
293 txtRecording->setAllRead();
294 }
295}
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500296
297@end