blob: 62e43d5e3c60bffa0bf1d441f0c14b6b2fada5cf [file] [log] [blame]
Alexandre Lision8521baa2015-03-13 11:08:00 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision8521baa2015-03-13 11:08:00 -04003 * 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.
Alexandre Lision8521baa2015-03-13 11:08:00 -040018 */
Alexandre Lision5855b6a2015-02-03 11:31:05 -050019#import "RingWindowController.h"
Alexandre Lision0f66bd32016-01-18 11:30:45 -050020#import <QuartzCore/QuartzCore.h>
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040021#include <qrencode.h>
Alexandre Lision0f66bd32016-01-18 11:30:45 -050022
23//Qt
24#import <QItemSelectionModel>
25#import <QItemSelection>
Alexandre Lision5855b6a2015-02-03 11:31:05 -050026
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040027//LRC
Alexandre Lision5855b6a2015-02-03 11:31:05 -050028#import <accountmodel.h>
29#import <callmodel.h>
30#import <account.h>
Alexandre Lision91d11e52015-03-20 17:42:05 -040031#import <call.h>
Alexandre Lision0f66bd32016-01-18 11:30:45 -050032#import <recentmodel.h>
Alexandre Lision5855b6a2015-02-03 11:31:05 -050033
Alexandre Lision624b1a82016-09-11 19:29:01 -040034// Ring
Alexandre Lision745e4d62015-03-22 20:03:10 -040035#import "AppDelegate.h"
Alexandre Lisionc65310c2015-04-23 16:44:23 -040036#import "Constants.h"
Alexandre Lision58cab672015-06-09 15:25:40 -040037#import "CurrentCallVC.h"
Alexandre Lision624b1a82016-09-11 19:29:01 -040038#import "MigrateRingAccountsWC.h"
Alexandre Lision0f66bd32016-01-18 11:30:45 -050039#import "ConversationVC.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040040#import "PreferencesWC.h"
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -040041#import "SmartViewVC.h"
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040042#import "views/IconButton.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040043#import "views/NSColor+RingTheme.h"
Alexandre Lisione77f6f92016-04-17 23:39:39 -040044#import "views/BackgroundView.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040045
Alexandre Lision624b1a82016-09-11 19:29:01 -040046@interface RingWindowController () <MigrateRingAccountsDelegate>
47
48@property (retain) MigrateRingAccountsWC* migrateWC;
49
50@end
51
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040052@implementation RingWindowController {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050053
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040054 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYQRCodeConstraint;
55 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYWelcomeContainerConstraint;
56 __unsafe_unretained IBOutlet NSView* welcomeContainer;
Alexandre Lision1abdf582016-02-09 14:21:19 -050057 __unsafe_unretained IBOutlet NSView* callView;
58 __unsafe_unretained IBOutlet NSTextField* ringIDLabel;
59 __unsafe_unretained IBOutlet NSButton* shareButton;
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040060 __unsafe_unretained IBOutlet NSImageView* qrcodeView;
Alexandre Lision58cab672015-06-09 15:25:40 -040061
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040062 PreferencesWC* preferencesWC;
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -040063 IBOutlet SmartViewVC* smartViewVC;
Alexandre Lisione77f6f92016-04-17 23:39:39 -040064
Alexandre Lision0f66bd32016-01-18 11:30:45 -050065 CurrentCallVC* currentCallVC;
66 ConversationVC* offlineVC;
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040067}
Alexandre Lision58cab672015-06-09 15:25:40 -040068
Alexandre Lision624b1a82016-09-11 19:29:01 -040069QMetaObject::Connection accountUpdate;
Alexandre Lisionc5148052015-03-04 15:10:35 -050070static NSString* const kPreferencesIdentifier = @"PreferencesIdentifier";
Alexandre Lisionc5148052015-03-04 15:10:35 -050071
Alexandre Lision5855b6a2015-02-03 11:31:05 -050072- (void)windowDidLoad {
73 [super windowDidLoad];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040074 [self.window setMovableByWindowBackground:YES];
Alexandre Lision58cab672015-06-09 15:25:40 -040075
Alexandre Lisione77f6f92016-04-17 23:39:39 -040076 [self.window setBackgroundColor:[NSColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1.0]];
77
Alexandre Lision0f66bd32016-01-18 11:30:45 -050078 currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
79 offlineVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil];
80
Alexandre Lision58cab672015-06-09 15:25:40 -040081 [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050082 [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
83 [[offlineVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision58cab672015-06-09 15:25:40 -040084
Alexandre Lision0f66bd32016-01-18 11:30:45 -050085 [callView addSubview:[currentCallVC view] positioned:NSWindowAbove relativeTo:nil];
86 [callView addSubview:[offlineVC view] positioned:NSWindowAbove relativeTo:nil];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040087
Alexandre Lision0f66bd32016-01-18 11:30:45 -050088 [currentCallVC initFrame];
89 [offlineVC initFrame];
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040090
Alexandre Lision624b1a82016-09-11 19:29:01 -040091 [self checkAccountsToMigrate];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050092}
93
94- (void) connect
95{
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040096 // Update Ring ID label based on account model changes
Alexandre Lision624b1a82016-09-11 19:29:01 -040097 QObject::disconnect(accountUpdate);
98 accountUpdate = QObject::connect(&AccountModel::instance(),
99 &AccountModel::dataChanged,
100 [=] {
101 [self updateRingID];
102 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500103
104 QObject::connect(RecentModel::instance().selectionModel(),
105 &QItemSelectionModel::currentChanged,
106 [=](const QModelIndex &current, const QModelIndex &previous) {
107 auto call = RecentModel::instance().getActiveCall(current);
Alexandre Lision261f1b92016-04-04 12:35:34 -0400108
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500109 if(!current.isValid()) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500110 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500111 [currentCallVC animateOut];
112 return;
113 }
114
115 if (!call) {
116 [currentCallVC animateOut];
117 [offlineVC animateIn];
118 } else {
119 [currentCallVC animateIn];
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500120 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500121 }
122 });
Alexandre Lision210fe212016-01-27 11:15:13 -0500123
124 QObject::connect(CallModel::instance().selectionModel(),
125 &QItemSelectionModel::currentChanged,
126 [=](const QModelIndex &current, const QModelIndex &previous) {
127 if(!current.isValid()) {
128 return;
129 }
130
131 if (previous.isValid()) {
132 // We were already on a call
133 [currentCallVC animateOut];
134 } else {
135 // Make sure Conversation view hides when selecting a valid call
136 [currentCallVC animateIn];
137 [offlineVC animateOut];
138 }
139 });
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400140}
141
142/**
143 * Implement the necessary logic to choose which Ring ID to display.
144 * This tries to choose the "best" ID to show
145 */
146- (void) updateRingID
147{
148 Account* registered = nullptr;
149 Account* enabled = nullptr;
150 Account* finalChoice = nullptr;
151
152 [ringIDLabel setStringValue:@""];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400153 auto ringList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::RING);
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400154 for (int i = 0 ; i < ringList.size() && !registered ; ++i) {
Alexandre Lision313427f2016-11-24 21:04:04 -0500155 auto account = ringList.value(i);
156 if (account->isEnabled()) {
157 if(!enabled) {
158 enabled = finalChoice = account;
159 }
160 if (account->registrationState() == Account::RegistrationState::READY) {
161 registered = enabled = finalChoice = account;
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400162 }
163 } else {
Alexandre Lision313427f2016-11-24 21:04:04 -0500164 if (!finalChoice) {
165 finalChoice = account;
166 }
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400167 }
168 }
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400169
Alexandre Lision313427f2016-11-24 21:04:04 -0500170 auto name = finalChoice->registeredName();
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400171 NSString* uriToDisplay = nullptr;
Alexandre Lision313427f2016-11-24 21:04:04 -0500172 if (!name.isNull() && !name.isEmpty()) {
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400173 uriToDisplay = name.toNSString();
Alexandre Lision313427f2016-11-24 21:04:04 -0500174 } else {
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400175 uriToDisplay = finalChoice->username().toNSString();
Alexandre Lision313427f2016-11-24 21:04:04 -0500176 }
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400177
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400178 [ringIDLabel setStringValue:uriToDisplay];
179 [self drawQRCode:finalChoice->username().toNSString()];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500180}
181
Alexandre Lision1abdf582016-02-09 14:21:19 -0500182- (IBAction)shareRingID:(id)sender {
183 NSSharingServicePicker* sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:[NSArray arrayWithObject:[ringIDLabel stringValue]]];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400184 [sharingServicePicker setDelegate:self];
Alexandre Lision1abdf582016-02-09 14:21:19 -0500185 [sharingServicePicker showRelativeToRect:[sender bounds]
186 ofView:sender
187 preferredEdge:NSMinYEdge];
188}
189
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400190- (IBAction)toggleQRCode:(id)sender {
191 // Toggle pressed state of QRCode button
192 [sender setPressed:![sender isPressed]];
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400193 [self showQRCode:[sender isPressed]];
Alexandre Lision313427f2016-11-24 21:04:04 -0500194}
195
196/**
197 * Draw the QRCode in the qrCodeView
198 */
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400199- (void)drawQRCode:(NSString*) uriToDraw
Alexandre Lision313427f2016-11-24 21:04:04 -0500200{
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400201 auto qrCode = QRcode_encodeString(uriToDraw.UTF8String,
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400202 0,
203 QR_ECLEVEL_L, // Lowest level of error correction
204 QR_MODE_8, // 8-bit data mode
205 1);
206 if (!qrCode) {
207 return;
208 }
209
Alexandre Lision313427f2016-11-24 21:04:04 -0500210 unsigned char *data = 0;
211 int width;
212 data = qrCode->data;
213 width = qrCode->width;
214 int qr_margin = 3;
215
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400216 CGFloat size = qrcodeView.frame.size.width;
217
218 // create context
219 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
220 CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);
221
222 CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
223 CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
224 CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));
225
Alexandre Lision313427f2016-11-24 21:04:04 -0500226 float zoom = ceil((double)size / (qrCode->width + 2.0 * qr_margin));
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400227 CGRect rectDraw = CGRectMake(0, 0, zoom, zoom);
228
229 int ran;
230 for(int i = 0; i < width; ++i) {
231 for(int j = 0; j < width; ++j) {
232 if(*data & 1) {
233 CGContextSetFillColorWithColor(ctx, [NSColor ringDarkGrey].CGColor);
234 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
235 CGContextAddRect(ctx, rectDraw);
236 CGContextFillPath(ctx);
237 } else {
238 CGContextSetFillColorWithColor(ctx, [NSColor windowBackgroundColor].CGColor);
239 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
240 CGContextAddRect(ctx, rectDraw);
241 CGContextFillPath(ctx);
242 }
243 ++data;
244 }
245 }
Alexandre Lision313427f2016-11-24 21:04:04 -0500246
247 // get image
248 auto qrCGImage = CGBitmapContextCreateImage(ctx);
249 auto qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:qrcodeView.frame.size];
250
251 // some releases
252 CGContextRelease(ctx);
253 CGImageRelease(qrCGImage);
254 CGColorSpaceRelease(colorSpace);
255 QRcode_free(qrCode);
256
257 [qrcodeView setImage:qrImage];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400258}
259
260/**
261 * Start the in/out animation displaying the QRCode
262 * @param show should the QRCode be animated in or out
263 */
264- (void) showQRCode:(BOOL) show
265{
266 static const NSInteger offset = 110;
267 [NSAnimationContext beginGrouping];
268 NSAnimationContext.currentContext.duration = 0.5;
269 [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
270 qrcodeView.animator.alphaValue = show ? 1.0 : 0.0;
271 [centerYQRCodeConstraint.animator setConstant: show ? offset : 0];
272 [centerYWelcomeContainerConstraint.animator setConstant:show ? -offset : 0];
273 [NSAnimationContext endGrouping];
274}
275
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500276- (IBAction)openPreferences:(id)sender
277{
Alexandre Lisionbfa68f62015-09-10 08:38:42 -0400278 preferencesWC = [[PreferencesWC alloc] initWithWindowNibName:@"PreferencesWindow"];
279 [preferencesWC.window makeKeyAndOrderFront:preferencesWC.window];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500280}
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500281
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400282- (IBAction)showHistory:(NSButton*)sender
283{
284 [smartViewVC showHistory];
285}
286
287- (IBAction)showContacts:(NSButton*)sender
288{
289 [smartViewVC showContacts];
290}
291
292- (IBAction)showSmartlist:(NSButton*)sender
293{
294 [smartViewVC showSmartlist];
295}
296
Alexandre Lision624b1a82016-09-11 19:29:01 -0400297#pragma mark - Ring account migration
298
299- (void) migrateRingAccount:(Account*) acc
300{
301 self.migrateWC = [[MigrateRingAccountsWC alloc] initWithDelegate:self actionCode:1];
302 self.migrateWC.account = acc;
303#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
304 [self.window beginSheet:self.migrateWC.window completionHandler:nil];
305#else
306 [NSApp beginSheet: self.migrateWC.window
307 modalForWindow: self.window
308 modalDelegate: self
309 didEndSelector: nil
310 contextInfo: nil];
311#endif
312}
313
314- (void)checkAccountsToMigrate
315{
316 auto ringList = AccountModel::instance().accountsToMigrate();
317 if (ringList.length() > 0){
318 Account* acc = ringList.value(0);
319 [self migrateRingAccount:acc];
320 } else {
321 // Fresh run, we need to make sure RingID appears
322 [self updateRingID];
323 [shareButton sendActionOn:NSLeftMouseDownMask];
324
325 [self connect];
326 }
327}
328
329- (void)migrationDidComplete
330{
331 [self checkAccountsToMigrate];
332}
333
334- (void)migrationDidCompleteWithError
335{
336 [self checkAccountsToMigrate];
337}
338
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500339@end