blob: 01417ed47891df7b7e15bcf7781b75b428aa1105 [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 Lision745e4d62015-03-22 20:03:10 -040034#import "AppDelegate.h"
Alexandre Lisionc65310c2015-04-23 16:44:23 -040035#import "Constants.h"
Alexandre Lision58cab672015-06-09 15:25:40 -040036#import "CurrentCallVC.h"
Alexandre Lision0f66bd32016-01-18 11:30:45 -050037#import "ConversationVC.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040038
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040039#import "PreferencesWC.h"
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040040#import "views/IconButton.h"
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040041#import "views/NSColor+RingTheme.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040042
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040043@implementation RingWindowController {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050044
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040045 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYQRCodeConstraint;
46 __unsafe_unretained IBOutlet NSLayoutConstraint* centerYWelcomeContainerConstraint;
47 __unsafe_unretained IBOutlet NSView* welcomeContainer;
Alexandre Lision1abdf582016-02-09 14:21:19 -050048 __unsafe_unretained IBOutlet NSView* callView;
49 __unsafe_unretained IBOutlet NSTextField* ringIDLabel;
50 __unsafe_unretained IBOutlet NSButton* shareButton;
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040051 __unsafe_unretained IBOutlet NSImageView* qrcodeView;
Alexandre Lision58cab672015-06-09 15:25:40 -040052
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -040053 PreferencesWC* preferencesWC;
Alexandre Lision0f66bd32016-01-18 11:30:45 -050054 CurrentCallVC* currentCallVC;
55 ConversationVC* offlineVC;
Alexandre Lisionbfa68f62015-09-10 08:38:42 -040056}
Alexandre Lision58cab672015-06-09 15:25:40 -040057
Alexandre Lisionc5148052015-03-04 15:10:35 -050058static NSString* const kPreferencesIdentifier = @"PreferencesIdentifier";
Alexandre Lisionc5148052015-03-04 15:10:35 -050059
Alexandre Lision5855b6a2015-02-03 11:31:05 -050060- (void)windowDidLoad {
61 [super windowDidLoad];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040062 [self.window setMovableByWindowBackground:YES];
Alexandre Lision58cab672015-06-09 15:25:40 -040063
Alexandre Lision0f66bd32016-01-18 11:30:45 -050064 currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
65 offlineVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil];
66
Alexandre Lision58cab672015-06-09 15:25:40 -040067 [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision0f66bd32016-01-18 11:30:45 -050068 [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
69 [[offlineVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision58cab672015-06-09 15:25:40 -040070
Alexandre Lision0f66bd32016-01-18 11:30:45 -050071 [callView addSubview:[currentCallVC view] positioned:NSWindowAbove relativeTo:nil];
72 [callView addSubview:[offlineVC view] positioned:NSWindowAbove relativeTo:nil];
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040073
Alexandre Lision0f66bd32016-01-18 11:30:45 -050074 [currentCallVC initFrame];
75 [offlineVC initFrame];
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040076
Alexandre Lision08abfac2015-09-22 12:20:51 -040077 // Fresh run, we need to make sure RingID appears
78 [self updateRingID];
Alexandre Lision1abdf582016-02-09 14:21:19 -050079 [shareButton sendActionOn:NSLeftMouseDownMask];
Alexandre Lision08abfac2015-09-22 12:20:51 -040080
Alexandre Lision0f66bd32016-01-18 11:30:45 -050081 [self connect];
82}
83
84- (void) connect
85{
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040086 // Update Ring ID label based on account model changes
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040087 QObject::connect(&AccountModel::instance(),
Alexandre Lisionbb5c2462015-07-30 12:55:37 -040088 &AccountModel::dataChanged,
89 [=] {
90 [self updateRingID];
91 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -050092
93 QObject::connect(RecentModel::instance().selectionModel(),
94 &QItemSelectionModel::currentChanged,
95 [=](const QModelIndex &current, const QModelIndex &previous) {
96 auto call = RecentModel::instance().getActiveCall(current);
Alexandre Lision261f1b92016-04-04 12:35:34 -040097
Alexandre Lision0f66bd32016-01-18 11:30:45 -050098 if(!current.isValid()) {
Alexandre Lision01cf5e32016-01-21 15:54:30 -050099 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500100 [currentCallVC animateOut];
101 return;
102 }
103
104 if (!call) {
105 [currentCallVC animateOut];
106 [offlineVC animateIn];
107 } else {
108 [currentCallVC animateIn];
Alexandre Lision01cf5e32016-01-21 15:54:30 -0500109 [offlineVC animateOut];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500110 }
111 });
Alexandre Lision210fe212016-01-27 11:15:13 -0500112
113 QObject::connect(CallModel::instance().selectionModel(),
114 &QItemSelectionModel::currentChanged,
115 [=](const QModelIndex &current, const QModelIndex &previous) {
116 if(!current.isValid()) {
117 return;
118 }
119
120 if (previous.isValid()) {
121 // We were already on a call
122 [currentCallVC animateOut];
123 } else {
124 // Make sure Conversation view hides when selecting a valid call
125 [currentCallVC animateIn];
126 [offlineVC animateOut];
127 }
128 });
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400129}
130
131/**
132 * Implement the necessary logic to choose which Ring ID to display.
133 * This tries to choose the "best" ID to show
134 */
135- (void) updateRingID
136{
137 Account* registered = nullptr;
138 Account* enabled = nullptr;
139 Account* finalChoice = nullptr;
140
141 [ringIDLabel setStringValue:@""];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400142 auto ringList = AccountModel::instance().getAccountsByProtocol(Account::Protocol::RING);
Alexandre Lisionbb5c2462015-07-30 12:55:37 -0400143 for (int i = 0 ; i < ringList.size() && !registered ; ++i) {
144 Account* acc = ringList.value(i);
145 if (acc->isEnabled()) {
146 if(!enabled)
147 enabled = finalChoice = acc;
148 if (acc->registrationState() == Account::RegistrationState::READY) {
149 registered = enabled = finalChoice = acc;
150 }
151 } else {
152 if (!finalChoice)
153 finalChoice = acc;
154 }
155 }
156
Alexandre Lision7f8351b2015-08-20 11:43:37 -0400157 [ringIDLabel setStringValue:[[NSString alloc] initWithFormat:@"%@", finalChoice->username().toNSString()]];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500158}
159
Alexandre Lision1abdf582016-02-09 14:21:19 -0500160- (IBAction)shareRingID:(id)sender {
161 NSSharingServicePicker* sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:[NSArray arrayWithObject:[ringIDLabel stringValue]]];
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400162 [sharingServicePicker setDelegate:self];
Alexandre Lision1abdf582016-02-09 14:21:19 -0500163 [sharingServicePicker showRelativeToRect:[sender bounds]
164 ofView:sender
165 preferredEdge:NSMinYEdge];
166}
167
Alexandre Lisionfd0d6c82016-03-29 17:06:54 -0400168- (IBAction)toggleQRCode:(id)sender {
169 // Toggle pressed state of QRCode button
170 [sender setPressed:![sender isPressed]];
171 if (![sender isPressed]) {
172 // Recenter welcome view
173 [self showQRCode:NO];
174 return;
175 }
176
177 auto qrCode = QRcode_encodeString(ringIDLabel.stringValue.UTF8String,
178 0,
179 QR_ECLEVEL_L, // Lowest level of error correction
180 QR_MODE_8, // 8-bit data mode
181 1);
182 if (!qrCode) {
183 return;
184 }
185
186 CGFloat size = qrcodeView.frame.size.width;
187
188 // create context
189 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
190 CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);
191
192 CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
193 CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
194 CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));
195
196 // draw QR on this context
197 [self drawQRCode:qrCode context:ctx size:size];
198
199 // get image
200 auto qrCGImage = CGBitmapContextCreateImage(ctx);
201 auto qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:qrcodeView.frame.size];
202
203 // some releases
204 CGContextRelease(ctx);
205 CGImageRelease(qrCGImage);
206 CGColorSpaceRelease(colorSpace);
207 QRcode_free(qrCode);
208
209 [qrcodeView setImage:qrImage];
210 [self showQRCode:YES];
211}
212
213/**
214 * @param code the previously generated QRCode
215 * @param ctx current drawing context
216 * @param size the output size in which to draw
217 */
218- (void)drawQRCode:(QRcode *)code context:(CGContextRef)ctx size:(CGFloat)size {
219 unsigned char *data = 0;
220 int width;
221 data = code->data;
222 width = code->width;
223 int qr_margin = 3;
224 float zoom = ceil((double)size / (code->width + 2.0 * qr_margin));
225 CGRect rectDraw = CGRectMake(0, 0, zoom, zoom);
226
227 int ran;
228 for(int i = 0; i < width; ++i) {
229 for(int j = 0; j < width; ++j) {
230 if(*data & 1) {
231 CGContextSetFillColorWithColor(ctx, [NSColor ringDarkGrey].CGColor);
232 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
233 CGContextAddRect(ctx, rectDraw);
234 CGContextFillPath(ctx);
235 } else {
236 CGContextSetFillColorWithColor(ctx, [NSColor windowBackgroundColor].CGColor);
237 rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
238 CGContextAddRect(ctx, rectDraw);
239 CGContextFillPath(ctx);
240 }
241 ++data;
242 }
243 }
244}
245
246/**
247 * Start the in/out animation displaying the QRCode
248 * @param show should the QRCode be animated in or out
249 */
250- (void) showQRCode:(BOOL) show
251{
252 static const NSInteger offset = 110;
253 [NSAnimationContext beginGrouping];
254 NSAnimationContext.currentContext.duration = 0.5;
255 [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
256 qrcodeView.animator.alphaValue = show ? 1.0 : 0.0;
257 [centerYQRCodeConstraint.animator setConstant: show ? offset : 0];
258 [centerYWelcomeContainerConstraint.animator setConstant:show ? -offset : 0];
259 [NSAnimationContext endGrouping];
260}
261
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500262- (IBAction)openPreferences:(id)sender
263{
Alexandre Lisionbfa68f62015-09-10 08:38:42 -0400264 preferencesWC = [[PreferencesWC alloc] initWithWindowNibName:@"PreferencesWindow"];
265 [preferencesWC.window makeKeyAndOrderFront:preferencesWC.window];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500266}
Alexandre Lision4a7b95e2015-02-20 10:06:43 -0500267
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500268@end