blob: 9f7e9ae01860c8b82e69ce17e71194ba007136fc [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>
Kateryna Kostiuk312f9132017-04-18 12:09:06 -040033#import <AvailableAccountModel.h>
34
Alexandre Lision5855b6a2015-02-03 11:31:05 -050035
Alexandre Lision624b1a82016-09-11 19:29:01 -040036// Ring
Alexandre Lision745e4d62015-03-22 20:03:10 -040037#import "AppDelegate.h"
Alexandre Lisionc65310c2015-04-23 16:44:23 -040038#import "Constants.h"
Alexandre Lision58cab672015-06-09 15:25:40 -040039#import "CurrentCallVC.h"
Alexandre Lision624b1a82016-09-11 19:29:01 -040040#import "MigrateRingAccountsWC.h"
Alexandre Lision0f66bd32016-01-18 11:30:45 -050041#import "ConversationVC.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040042#import "PreferencesWC.h"
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -040043#import "SmartViewVC.h"
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040044#import "views/IconButton.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040045#import "views/NSColor+RingTheme.h"
Alexandre Lisione77f6f92016-04-17 23:39:39 -040046#import "views/BackgroundView.h"
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040047#import "ChooseAccountVC.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040048
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040049@interface RingWindowController () <MigrateRingAccountsDelegate, NSToolbarDelegate>
Alexandre Lision624b1a82016-09-11 19:29:01 -040050
51@property (retain) MigrateRingAccountsWC* migrateWC;
52
53@end
54
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040055@implementation RingWindowController {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050056
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040057 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYQRCodeConstraint;
58 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYWelcomeContainerConstraint;
59 __unsafe_unretained IBOutlet NSView* welcomeContainer;
Alexandre Lision1abdf582016-02-09 14:21:19 -050060 __unsafe_unretained IBOutlet NSView* callView;
61 __unsafe_unretained IBOutlet NSTextField* ringIDLabel;
62 __unsafe_unretained IBOutlet NSButton* shareButton;
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040063 __unsafe_unretained IBOutlet NSImageView* qrcodeView;
Alexandre Lision58cab672015-06-09 15:25:40 -040064
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040065 PreferencesWC* preferencesWC;
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -040066 IBOutlet SmartViewVC* smartViewVC;
Alexandre Lisione77f6f92016-04-17 23:39:39 -040067
Alexandre Lision0f66bd32016-01-18 11:30:45 -050068 CurrentCallVC* currentCallVC;
69 ConversationVC* offlineVC;
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040070
71 ChooseAccountVC* chooseAccountVC;
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040072}
Alexandre Lision58cab672015-06-09 15:25:40 -040073
Alexandre Lisionc5148052015-03-04 15:10:35 -050074static NSString* const kPreferencesIdentifier = @"PreferencesIdentifier";
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040075NSString* const kChangeAccountToolBarItemIdentifier = @"ChangeAccountToolBarItemIdentifier";
Alexandre Lisionc5148052015-03-04 15:10:35 -050076
Alexandre Lision5855b6a2015-02-03 11:31:05 -050077- (void)windowDidLoad {
78 [super windowDidLoad];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040079 [self.window setMovableByWindowBackground:YES];
Alexandre Lision58cab672015-06-09 15:25:40 -040080
Alexandre Lisione77f6f92016-04-17 23:39:39 -040081 [self.window setBackgroundColor:[NSColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1.0]];
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040082 self.window.titleVisibility = NSWindowTitleHidden;
Alexandre Lisione77f6f92016-04-17 23:39:39 -040083
Alexandre Lision0f66bd32016-01-18 11:30:45 -050084 currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
85 offlineVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil];
Kateryna Kostiuk13b76882017-03-30 09:18:44 -040086 chooseAccountVC = [[ChooseAccountVC alloc] initWithNibName:@"ChooseAccount" bundle:nil];
Alexandre Lision58cab672015-06-09 15:25:40 -040087 [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050088 [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
89 [[offlineVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision58cab672015-06-09 15:25:40 -040090
Alexandre Lision0f66bd32016-01-18 11:30:45 -050091 [callView addSubview:[currentCallVC view] positioned:NSWindowAbove relativeTo:nil];
92 [callView addSubview:[offlineVC view] positioned:NSWindowAbove relativeTo:nil];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040093
Alexandre Lision0f66bd32016-01-18 11:30:45 -050094 [currentCallVC initFrame];
95 [offlineVC initFrame];
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040096
Alexandre Lision624b1a82016-09-11 19:29:01 -040097 [self checkAccountsToMigrate];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050098}
99
100- (void) connect
101{
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400102 // Update Ring ID label based on account model changes
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500103 QObject::connect(RecentModel::instance().selectionModel(),
104 &QItemSelectionModel::currentChanged,
105 [=](const QModelIndex &current, const QModelIndex &previous) {
106 auto call = RecentModel::instance().getActiveCall(current);
Alexandre Lision261f1b92016-04-04 12:35:34 -0400107
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500108 if(!current.isValid()) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500109 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500110 [currentCallVC animateOut];
111 return;
112 }
113
114 if (!call) {
115 [currentCallVC animateOut];
116 [offlineVC animateIn];
117 } else {
118 [currentCallVC animateIn];
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500119 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500120 }
121 });
Alexandre Lision210fe212016-01-27 11:15:13 -0500122
123 QObject::connect(CallModel::instance().selectionModel(),
124 &QItemSelectionModel::currentChanged,
125 [=](const QModelIndex &current, const QModelIndex &previous) {
126 if(!current.isValid()) {
127 return;
128 }
129
130 if (previous.isValid()) {
131 // We were already on a call
132 [currentCallVC animateOut];
133 } else {
134 // Make sure Conversation view hides when selecting a valid call
135 [currentCallVC animateIn];
136 [offlineVC animateOut];
137 }
138 });
Kateryna Kostiuk312f9132017-04-18 12:09:06 -0400139 QObject::connect(AvailableAccountModel::instance().selectionModel(),
Kateryna Kostiuk13b76882017-03-30 09:18:44 -0400140 &QItemSelectionModel::currentChanged,
Kateryna Kostiuk312f9132017-04-18 12:09:06 -0400141 [self](const QModelIndex& idx){
Kateryna Kostiuk13b76882017-03-30 09:18:44 -0400142 [self updateRingID];
143 });
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400144}
145
146/**
147 * Implement the necessary logic to choose which Ring ID to display.
148 * This tries to choose the "best" ID to show
149 */
150- (void) updateRingID
151{
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400152 Account* finalChoice = nullptr;
153
154 [ringIDLabel setStringValue:@""];
Kateryna Kostiuk312f9132017-04-18 12:09:06 -0400155 QModelIndex index = AvailableAccountModel::instance().selectionModel()->currentIndex();
156 finalChoice = index.data(static_cast<int>(Account::Role::Object)).value<Account*>();
157 if(finalChoice == nil || (finalChoice->protocol() != Account::Protocol::RING)) {
158 return;
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400159 }
Alexandre Lision313427f2016-11-24 21:04:04 -0500160 auto name = finalChoice->registeredName();
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400161 NSString* uriToDisplay = nullptr;
Alexandre Lision313427f2016-11-24 21:04:04 -0500162 if (!name.isNull() && !name.isEmpty()) {
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400163 uriToDisplay = name.toNSString();
Alexandre Lision313427f2016-11-24 21:04:04 -0500164 } else {
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400165 uriToDisplay = finalChoice->username().toNSString();
Alexandre Lision313427f2016-11-24 21:04:04 -0500166 }
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400167
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400168 [ringIDLabel setStringValue:uriToDisplay];
169 [self drawQRCode:finalChoice->username().toNSString()];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500170}
171
Alexandre Lision1abdf582016-02-09 14:21:19 -0500172- (IBAction)shareRingID:(id)sender {
173 NSSharingServicePicker* sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:[NSArray arrayWithObject:[ringIDLabel stringValue]]];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400174 [sharingServicePicker setDelegate:self];
Alexandre Lision1abdf582016-02-09 14:21:19 -0500175 [sharingServicePicker showRelativeToRect:[sender bounds]
176 ofView:sender
177 preferredEdge:NSMinYEdge];
178}
179
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400180- (IBAction)toggleQRCode:(id)sender {
181 // Toggle pressed state of QRCode button
182 [sender setPressed:![sender isPressed]];
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400183 [self showQRCode:[sender isPressed]];
Alexandre Lision313427f2016-11-24 21:04:04 -0500184}
185
186/**
187 * Draw the QRCode in the qrCodeView
188 */
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400189- (void)drawQRCode:(NSString*) uriToDraw
Alexandre Lision313427f2016-11-24 21:04:04 -0500190{
Kateryna Kostiuk65ba43e2017-03-30 15:10:04 -0400191 auto qrCode = QRcode_encodeString(uriToDraw.UTF8String,
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400192 0,
193 QR_ECLEVEL_L, // Lowest level of error correction
194 QR_MODE_8, // 8-bit data mode
195 1);
196 if (!qrCode) {
197 return;
198 }
199
Alexandre Lision313427f2016-11-24 21:04:04 -0500200 unsigned char *data = 0;
201 int width;
202 data = qrCode->data;
203 width = qrCode->width;
204 int qr_margin = 3;
205
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400206 CGFloat size = qrcodeView.frame.size.width;
207
208 // create context
209 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
210 CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);
211
212 CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
213 CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
214 CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));
215
Alexandre Lision313427f2016-11-24 21:04:04 -0500216 float zoom = ceil((double)size / (qrCode->width + 2.0 * qr_margin));
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400217 CGRect rectDraw = CGRectMake(0, 0, zoom, zoom);
218
219 int ran;
220 for(int i = 0; i < width; ++i) {
221 for(int j = 0; j < width; ++j) {
222 if(*data & 1) {
223 CGContextSetFillColorWithColor(ctx, [NSColor ringDarkGrey].CGColor);
224 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
225 CGContextAddRect(ctx, rectDraw);
226 CGContextFillPath(ctx);
227 } else {
228 CGContextSetFillColorWithColor(ctx, [NSColor windowBackgroundColor].CGColor);
229 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
230 CGContextAddRect(ctx, rectDraw);
231 CGContextFillPath(ctx);
232 }
233 ++data;
234 }
235 }
Alexandre Lision313427f2016-11-24 21:04:04 -0500236
237 // get image
238 auto qrCGImage = CGBitmapContextCreateImage(ctx);
239 auto qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:qrcodeView.frame.size];
240
241 // some releases
242 CGContextRelease(ctx);
243 CGImageRelease(qrCGImage);
244 CGColorSpaceRelease(colorSpace);
245 QRcode_free(qrCode);
246
247 [qrcodeView setImage:qrImage];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400248}
249
250/**
251 * Start the in/out animation displaying the QRCode
252 * @param show should the QRCode be animated in or out
253 */
254- (void) showQRCode:(BOOL) show
255{
256 static const NSInteger offset = 110;
257 [NSAnimationContext beginGrouping];
258 NSAnimationContext.currentContext.duration = 0.5;
259 [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
260 qrcodeView.animator.alphaValue = show ? 1.0 : 0.0;
261 [centerYQRCodeConstraint.animator setConstant: show ? offset : 0];
262 [centerYWelcomeContainerConstraint.animator setConstant:show ? -offset : 0];
263 [NSAnimationContext endGrouping];
264}
265
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500266- (IBAction)openPreferences:(id)sender
267{
Alexandre Lisionbfa68f62015-09-10 08:38:42 -0400268 preferencesWC = [[PreferencesWC alloc] initWithWindowNibName:@"PreferencesWindow"];
269 [preferencesWC.window makeKeyAndOrderFront:preferencesWC.window];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500270}
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500271
Alexandre Lisiona3a43dc2017-03-30 16:21:30 -0400272- (IBAction)showHistory:(NSButton*)sender
273{
274 [smartViewVC showHistory];
275}
276
277- (IBAction)showContacts:(NSButton*)sender
278{
279 [smartViewVC showContacts];
280}
281
282- (IBAction)showSmartlist:(NSButton*)sender
283{
284 [smartViewVC showSmartlist];
285}
286
Alexandre Lision624b1a82016-09-11 19:29:01 -0400287#pragma mark - Ring account migration
288
289- (void) migrateRingAccount:(Account*) acc
290{
291 self.migrateWC = [[MigrateRingAccountsWC alloc] initWithDelegate:self actionCode:1];
292 self.migrateWC.account = acc;
293#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
294 [self.window beginSheet:self.migrateWC.window completionHandler:nil];
295#else
296 [NSApp beginSheet: self.migrateWC.window
297 modalForWindow: self.window
298 modalDelegate: self
299 didEndSelector: nil
300 contextInfo: nil];
301#endif
302}
303
304- (void)checkAccountsToMigrate
305{
306 auto ringList = AccountModel::instance().accountsToMigrate();
307 if (ringList.length() > 0){
308 Account* acc = ringList.value(0);
309 [self migrateRingAccount:acc];
310 } else {
311 // Fresh run, we need to make sure RingID appears
Alexandre Lision624b1a82016-09-11 19:29:01 -0400312 [shareButton sendActionOn:NSLeftMouseDownMask];
313
314 [self connect];
Kateryna Kostiuk13b76882017-03-30 09:18:44 -0400315 // display accounts to select
316 NSToolbar *toolbar = self.window.toolbar;
317 toolbar.delegate = self;
318 [toolbar insertItemWithItemIdentifier:kChangeAccountToolBarItemIdentifier atIndex:1];
Alexandre Lision624b1a82016-09-11 19:29:01 -0400319 }
320}
321
322- (void)migrationDidComplete
323{
324 [self checkAccountsToMigrate];
325}
326
327- (void)migrationDidCompleteWithError
328{
329 [self checkAccountsToMigrate];
330}
331
Kateryna Kostiuk13b76882017-03-30 09:18:44 -0400332#pragma mark - NSToolbarDelegate
333- (nullable NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag{
334 if(!(itemIdentifier == kChangeAccountToolBarItemIdentifier)) {
335 return nil;
336 }
337 NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:kChangeAccountToolBarItemIdentifier];
338 CGRect frame = chooseAccountVC.view.frame;
339 toolbarItem.view = chooseAccountVC.view;
340 return toolbarItem;
341}
342
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500343@end