blob: 06d866c43190946477285c70fe936c045ee299f2 [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 Lisionfd0d6c82016-03-29 17:06:54 -040041#import "views/IconButton.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040042#import "views/NSColor+RingTheme.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040043
Alexandre Lision624b1a82016-09-11 19:29:01 -040044@interface RingWindowController () <MigrateRingAccountsDelegate>
45
46@property (retain) MigrateRingAccountsWC* migrateWC;
47
48@end
49
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040050@implementation RingWindowController {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050051
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040052 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYQRCodeConstraint;
53 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYWelcomeContainerConstraint;
54 __unsafe_unretained IBOutlet NSView* welcomeContainer;
Alexandre Lision1abdf582016-02-09 14:21:19 -050055 __unsafe_unretained IBOutlet NSView* callView;
56 __unsafe_unretained IBOutlet NSTextField* ringIDLabel;
57 __unsafe_unretained IBOutlet NSButton* shareButton;
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040058 __unsafe_unretained IBOutlet NSImageView* qrcodeView;
Alexandre Lision58cab672015-06-09 15:25:40 -040059
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040060 PreferencesWC* preferencesWC;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050061 CurrentCallVC* currentCallVC;
62 ConversationVC* offlineVC;
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040063}
Alexandre Lision58cab672015-06-09 15:25:40 -040064
Alexandre Lision624b1a82016-09-11 19:29:01 -040065QMetaObject::Connection accountUpdate;
Alexandre Lisionc5148052015-03-04 15:10:35 -050066static NSString* const kPreferencesIdentifier = @"PreferencesIdentifier";
Alexandre Lisionc5148052015-03-04 15:10:35 -050067
Alexandre Lision5855b6a2015-02-03 11:31:05 -050068- (void)windowDidLoad {
69 [super windowDidLoad];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040070 [self.window setMovableByWindowBackground:YES];
Alexandre Lision58cab672015-06-09 15:25:40 -040071
Alexandre Lision0f66bd32016-01-18 11:30:45 -050072 currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
73 offlineVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil];
74
Alexandre Lision58cab672015-06-09 15:25:40 -040075 [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050076 [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
77 [[offlineVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision58cab672015-06-09 15:25:40 -040078
Alexandre Lision0f66bd32016-01-18 11:30:45 -050079 [callView addSubview:[currentCallVC view] positioned:NSWindowAbove relativeTo:nil];
80 [callView addSubview:[offlineVC view] positioned:NSWindowAbove relativeTo:nil];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040081
Alexandre Lision0f66bd32016-01-18 11:30:45 -050082 [currentCallVC initFrame];
83 [offlineVC initFrame];
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040084
Alexandre Lision624b1a82016-09-11 19:29:01 -040085 [self checkAccountsToMigrate];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050086}
87
88- (void) connect
89{
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040090 // Update Ring ID label based on account model changes
Alexandre Lision624b1a82016-09-11 19:29:01 -040091 QObject::disconnect(accountUpdate);
92 accountUpdate = QObject::connect(&AccountModel::instance(),
93 &AccountModel::dataChanged,
94 [=] {
95 [self updateRingID];
96 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -050097
98 QObject::connect(RecentModel::instance().selectionModel(),
99 &QItemSelectionModel::currentChanged,
100 [=](const QModelIndex &current, const QModelIndex &previous) {
101 auto call = RecentModel::instance().getActiveCall(current);
Alexandre Lision261f1b92016-04-04 12:35:34 -0400102
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500103 if(!current.isValid()) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500104 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500105 [currentCallVC animateOut];
106 return;
107 }
108
109 if (!call) {
110 [currentCallVC animateOut];
111 [offlineVC animateIn];
112 } else {
113 [currentCallVC animateIn];
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500114 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500115 }
116 });
Alexandre Lision210fe212016-01-27 11:15:13 -0500117
118 QObject::connect(CallModel::instance().selectionModel(),
119 &QItemSelectionModel::currentChanged,
120 [=](const QModelIndex &current, const QModelIndex &previous) {
121 if(!current.isValid()) {
122 return;
123 }
124
125 if (previous.isValid()) {
126 // We were already on a call
127 [currentCallVC animateOut];
128 } else {
129 // Make sure Conversation view hides when selecting a valid call
130 [currentCallVC animateIn];
131 [offlineVC animateOut];
132 }
133 });
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400134}
135
136/**
137 * Implement the necessary logic to choose which Ring ID to display.
138 * This tries to choose the "best" ID to show
139 */
140- (void) updateRingID
141{
142 Account* registered = nullptr;
143 Account* enabled = nullptr;
144 Account* finalChoice = nullptr;
145
146 [ringIDLabel setStringValue:@""];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400147 auto ringList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::RING);
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400148 for (int i = 0 ; i < ringList.size() && !registered ; ++i) {
149 Account* acc = ringList.value(i);
150 if (acc->isEnabled()) {
151 if(!enabled)
152 enabled = finalChoice = acc;
153 if (acc->registrationState() == Account::RegistrationState::READY) {
154 registered = enabled = finalChoice = acc;
155 }
156 } else {
157 if (!finalChoice)
158 finalChoice = acc;
159 }
160 }
161
Alexandre Lision7f8351b2015-08-20 11:43:37 -0400162 [ringIDLabel setStringValue:[[NSString alloc] initWithFormat:@"%@", finalChoice->username().toNSString()]];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500163}
164
Alexandre Lision1abdf582016-02-09 14:21:19 -0500165- (IBAction)shareRingID:(id)sender {
166 NSSharingServicePicker* sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:[NSArray arrayWithObject:[ringIDLabel stringValue]]];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400167 [sharingServicePicker setDelegate:self];
Alexandre Lision1abdf582016-02-09 14:21:19 -0500168 [sharingServicePicker showRelativeToRect:[sender bounds]
169 ofView:sender
170 preferredEdge:NSMinYEdge];
171}
172
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400173- (IBAction)toggleQRCode:(id)sender {
174 // Toggle pressed state of QRCode button
175 [sender setPressed:![sender isPressed]];
176 if (![sender isPressed]) {
177 // Recenter welcome view
178 [self showQRCode:NO];
179 return;
180 }
181
182 auto qrCode = QRcode_encodeString(ringIDLabel.stringValue.UTF8String,
183 0,
184 QR_ECLEVEL_L, // Lowest level of error correction
185 QR_MODE_8, // 8-bit data mode
186 1);
187 if (!qrCode) {
188 return;
189 }
190
191 CGFloat size = qrcodeView.frame.size.width;
192
193 // create context
194 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
195 CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);
196
197 CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
198 CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
199 CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));
200
201 // draw QR on this context
202 [self drawQRCode:qrCode context:ctx size:size];
203
204 // get image
205 auto qrCGImage = CGBitmapContextCreateImage(ctx);
206 auto qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:qrcodeView.frame.size];
207
208 // some releases
209 CGContextRelease(ctx);
210 CGImageRelease(qrCGImage);
211 CGColorSpaceRelease(colorSpace);
212 QRcode_free(qrCode);
213
214 [qrcodeView setImage:qrImage];
215 [self showQRCode:YES];
216}
217
218/**
219 * @param code the previously generated QRCode
220 * @param ctx current drawing context
221 * @param size the output size in which to draw
222 */
223- (void)drawQRCode:(QRcode *)code context:(CGContextRef)ctx size:(CGFloat)size {
224 unsigned char *data = 0;
225 int width;
226 data = code->data;
227 width = code->width;
228 int qr_margin = 3;
229 float zoom = ceil((double)size / (code->width + 2.0 * qr_margin));
230 CGRect rectDraw = CGRectMake(0, 0, zoom, zoom);
231
232 int ran;
233 for(int i = 0; i < width; ++i) {
234 for(int j = 0; j < width; ++j) {
235 if(*data & 1) {
236 CGContextSetFillColorWithColor(ctx, [NSColor ringDarkGrey].CGColor);
237 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
238 CGContextAddRect(ctx, rectDraw);
239 CGContextFillPath(ctx);
240 } else {
241 CGContextSetFillColorWithColor(ctx, [NSColor windowBackgroundColor].CGColor);
242 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
243 CGContextAddRect(ctx, rectDraw);
244 CGContextFillPath(ctx);
245 }
246 ++data;
247 }
248 }
249}
250
251/**
252 * Start the in/out animation displaying the QRCode
253 * @param show should the QRCode be animated in or out
254 */
255- (void) showQRCode:(BOOL) show
256{
257 static const NSInteger offset = 110;
258 [NSAnimationContext beginGrouping];
259 NSAnimationContext.currentContext.duration = 0.5;
260 [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
261 qrcodeView.animator.alphaValue = show ? 1.0 : 0.0;
262 [centerYQRCodeConstraint.animator setConstant: show ? offset : 0];
263 [centerYWelcomeContainerConstraint.animator setConstant:show ? -offset : 0];
264 [NSAnimationContext endGrouping];
265}
266
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500267- (IBAction)openPreferences:(id)sender
268{
Alexandre Lisionbfa68f62015-09-10 08:38:42 -0400269 preferencesWC = [[PreferencesWC alloc] initWithWindowNibName:@"PreferencesWindow"];
270 [preferencesWC.window makeKeyAndOrderFront:preferencesWC.window];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500271}
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500272
Alexandre Lision624b1a82016-09-11 19:29:01 -0400273#pragma mark - Ring account migration
274
275- (void) migrateRingAccount:(Account*) acc
276{
277 self.migrateWC = [[MigrateRingAccountsWC alloc] initWithDelegate:self actionCode:1];
278 self.migrateWC.account = acc;
279#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
280 [self.window beginSheet:self.migrateWC.window completionHandler:nil];
281#else
282 [NSApp beginSheet: self.migrateWC.window
283 modalForWindow: self.window
284 modalDelegate: self
285 didEndSelector: nil
286 contextInfo: nil];
287#endif
288}
289
290- (void)checkAccountsToMigrate
291{
292 auto ringList = AccountModel::instance().accountsToMigrate();
293 if (ringList.length() > 0){
294 Account* acc = ringList.value(0);
295 [self migrateRingAccount:acc];
296 } else {
297 // Fresh run, we need to make sure RingID appears
298 [self updateRingID];
299 [shareButton sendActionOn:NSLeftMouseDownMask];
300
301 [self connect];
302 }
303}
304
305- (void)migrationDidComplete
306{
307 [self checkAccountsToMigrate];
308}
309
310- (void)migrationDidCompleteWithError
311{
312 [self checkAccountsToMigrate];
313}
314
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500315@end