RingWindowController.mm 14.6 KB
Newer Older
1
/*
Alexandre Lision's avatar
Alexandre Lision committed
2
 *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */
Alexandre Lision's avatar
Alexandre Lision committed
19
#import "RingWindowController.h"
20
#import <QuartzCore/QuartzCore.h>
21
#include <qrencode.h>
22
#include <memory>
23 24 25 26

//Qt
#import <QItemSelectionModel>
#import <QItemSelection>
Alexandre Lision's avatar
Alexandre Lision committed
27

28
//LRC
Alexandre Lision's avatar
Alexandre Lision committed
29 30
#import <callmodel.h>
#import <account.h>
31
#import <call.h>
32
#import <recentmodel.h>
33
#import <AvailableAccountModel.h>
34
#import <api/lrc.h>
35
#import <api/account.h>
36 37 38 39
#import <api/newaccountmodel.h>
#import <api/newcallmodel.h>
#import <api/behaviorcontroller.h>
#import <api/conversation.h>
40 41
#import <api/contactmodel.h>
#import <api/contact.h>
Alexandre Lision's avatar
Alexandre Lision committed
42

43
// Ring
44
#import "AppDelegate.h"
45
#import "Constants.h"
Alexandre Lision's avatar
Alexandre Lision committed
46
#import "CurrentCallVC.h"
47
#import "MigrateRingAccountsWC.h"
48
#import "ConversationVC.h"
Alexandre Lision's avatar
Alexandre Lision committed
49
#import "PreferencesWC.h"
50
#import "SmartViewVC.h"
51
#import "views/IconButton.h"
Alexandre Lision's avatar
Alexandre Lision committed
52
#import "views/NSColor+RingTheme.h"
53
#import "views/BackgroundView.h"
54
#import "ChooseAccountVC.h"
55

56
@interface RingWindowController () <MigrateRingAccountsDelegate, NSToolbarDelegate>
57 58 59 60 61

@property (retain) MigrateRingAccountsWC* migrateWC;

@end

Alexandre Lision's avatar
Alexandre Lision committed
62
@implementation RingWindowController {
Alexandre Lision's avatar
Alexandre Lision committed
63

64 65 66
    __unsafe_unretained IBOutlet NSLayoutConstraint* centerYQRCodeConstraint;
    __unsafe_unretained IBOutlet NSLayoutConstraint* centerYWelcomeContainerConstraint;
    __unsafe_unretained IBOutlet NSView* welcomeContainer;
67 68 69
    __unsafe_unretained IBOutlet NSView* callView;
    __unsafe_unretained IBOutlet NSTextField* ringIDLabel;
    __unsafe_unretained IBOutlet NSButton* shareButton;
70
    __unsafe_unretained IBOutlet NSImageView* qrcodeView;
Alexandre Lision's avatar
Alexandre Lision committed
71

72 73
    std::unique_ptr<lrc::api::Lrc> lrc_;

74
    PreferencesWC* preferencesWC;
75
    IBOutlet SmartViewVC* smartViewVC;
76

77 78
    CurrentCallVC* currentCallVC;
    ConversationVC* offlineVC;
79
    // toolbar menu items
80
    ChooseAccountVC* chooseAccountVC;
Alexandre Lision's avatar
Alexandre Lision committed
81
}
Alexandre Lision's avatar
Alexandre Lision committed
82

83 84
static NSString* const kPreferencesIdentifier        = @"PreferencesIdentifier";
NSString* const kChangeAccountToolBarItemIdentifier  = @"ChangeAccountToolBarItemIdentifier";
85

Alexandre Lision's avatar
Alexandre Lision committed
86 87
- (void)windowDidLoad {
    [super windowDidLoad];
88
    [self.window setMovableByWindowBackground:YES];
Alexandre Lision's avatar
Alexandre Lision committed
89

90
    [self.window setBackgroundColor:[NSColor colorWithRed:242.0/255 green:242.0/255 blue:242.0/255 alpha:1.0]];
91
    self.window.titleVisibility = NSWindowTitleHidden;
92

93 94
    lrc_ = std::make_unique<lrc::api::Lrc>();

95
    currentCallVC = [[CurrentCallVC alloc] initWithNibName:@"CurrentCall" bundle:nil];
96
    offlineVC = [[ConversationVC alloc] initWithNibName:@"Conversation" bundle:nil delegate:self];
97
    // toolbar items
98
    chooseAccountVC = [[ChooseAccountVC alloc] initWithNibName:@"ChooseAccount" bundle:nil model:&(lrc_->getAccountModel()) delegate:self];
Alexandre Lision's avatar
Alexandre Lision committed
99
    [callView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
100 101
    [[currentCallVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [[offlineVC view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Alexandre Lision's avatar
Alexandre Lision committed
102

103 104
    [callView addSubview:[currentCallVC view] positioned:NSWindowAbove relativeTo:nil];
    [callView addSubview:[offlineVC view] positioned:NSWindowAbove relativeTo:nil];
Alexandre Lision's avatar
Alexandre Lision committed
105

106 107
    [currentCallVC initFrame];
    [offlineVC initFrame];
108 109 110 111 112 113
    @try {
        [smartViewVC setConversationModel: [chooseAccountVC selectedAccount].conversationModel.get()];
    }
    @catch (NSException *ex) {
        NSLog(@"Caught exception %@: %@", [ex name], [ex reason]);
    }
114

115 116 117
    // Fresh run, we need to make sure RingID appears
    [shareButton sendActionOn:NSLeftMouseDownMask];

118
    [self connect];
119 120 121 122 123
    [self updateRingID];
    // display accounts to select
    NSToolbar *toolbar = self.window.toolbar;
    toolbar.delegate = self;
    [toolbar insertItemWithItemIdentifier:kChangeAccountToolBarItemIdentifier atIndex:1];
124 125 126 127
}

- (void) connect
{
128 129 130 131 132
    QObject::connect(&lrc_->getBehaviorController(),
                     &lrc::api::BehaviorController::showCallView,
                     [self](const std::string accountId,
                            const lrc::api::conversation::Info convInfo){
                         auto* accInfo = &lrc_->getAccountModel().getAccountInfo(accountId);
133 134 135 136 137
                         if (accInfo->contactModel->getContact(convInfo.participants[0]).profileInfo.type == lrc::api::profile::Type::PENDING)
                             [smartViewVC selectPendingList];
                         else
                             [smartViewVC selectConversationList];

138 139 140
                         [currentCallVC setCurrentCall:convInfo.callId
                                          conversation:convInfo.uid
                                               account:accInfo];
141
                         [smartViewVC selectConversation: convInfo model:accInfo->conversationModel.get()];
142 143
                         [currentCallVC animateIn];
                         [offlineVC animateOut];
144
                     });
Alexandre Lision's avatar
Alexandre Lision committed
145

146 147 148 149 150
    QObject::connect(&lrc_->getBehaviorController(),
                     &lrc::api::BehaviorController::showIncomingCallView,
                     [self](const std::string accountId,
                            const lrc::api::conversation::Info convInfo){
                         auto* accInfo = &lrc_->getAccountModel().getAccountInfo(accountId);
151 152 153 154 155
                         if (accInfo->contactModel->getContact(convInfo.participants[0]).profileInfo.type == lrc::api::profile::Type::PENDING)
                             [smartViewVC selectPendingList];
                         else
                             [smartViewVC selectConversationList];

156 157 158
                         [currentCallVC setCurrentCall:convInfo.callId
                                          conversation:convInfo.uid
                                               account:accInfo];
159
                         [smartViewVC selectConversation: convInfo model:accInfo->conversationModel.get()];
160
                         [currentCallVC animateIn];
161
                         [offlineVC animateOut];
162
                     });
163 164 165 166 167 168 169

    QObject::connect(&lrc_->getBehaviorController(),
                     &lrc::api::BehaviorController::showChatView,
                     [self](const std::string& accountId,
                            const lrc::api::conversation::Info& convInfo){
                         auto& accInfo = lrc_->getAccountModel().getAccountInfo(accountId);
                         [offlineVC setConversationUid:convInfo.uid model:accInfo.conversationModel.get()];
170
                         [smartViewVC selectConversation: convInfo model:accInfo.conversationModel.get()];
171 172 173
                         [offlineVC animateIn];
                         [currentCallVC animateOut];
                     });
174 175 176 177 178 179 180 181
}

/**
 * Implement the necessary logic to choose which Ring ID to display.
 * This tries to choose the "best" ID to show
 */
- (void) updateRingID
{
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    @try {
        auto& account = [chooseAccountVC selectedAccount];

        [ringIDLabel setStringValue:@""];

        if(account.profileInfo.type != lrc::api::profile::Type::RING) {
            self.hideRingID = YES;
            return;
        }
        self.hideRingID = NO;
        auto& registeredName = account.registeredName;
        auto& ringID = account.profileInfo.uri;
        NSString* uriToDisplay = nullptr;
        if (!registeredName.empty()) {
            uriToDisplay = @(registeredName.c_str());
        } else {
            uriToDisplay = @(ringID.c_str());
        }
        [ringIDLabel setStringValue:uriToDisplay];
        [self drawQRCode:@(ringID.c_str())];
202
    }
203 204 205
    @catch (NSException *ex) {
        NSLog(@"Caught exception %@: %@", [ex name], [ex reason]);
        self.hideRingID = NO;
Anthony Léonard's avatar
Anthony Léonard committed
206
        [ringIDLabel setStringValue:NSLocalizedString(@"No account available", @"Displayed as RingID when no accounts are available for selection")];
207
    }
Alexandre Lision's avatar
Alexandre Lision committed
208 209
}

210 211
- (IBAction)shareRingID:(id)sender {
    NSSharingServicePicker* sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:[NSArray arrayWithObject:[ringIDLabel stringValue]]];
212
    [sharingServicePicker setDelegate:self];
213 214 215 216 217
    [sharingServicePicker showRelativeToRect:[sender bounds]
                                      ofView:sender
                               preferredEdge:NSMinYEdge];
}

218 219 220
- (IBAction)toggleQRCode:(id)sender {
    // Toggle pressed state of QRCode button
    [sender setPressed:![sender isPressed]];
221
    [self showQRCode:[sender isPressed]];
222 223 224 225 226
}

/**
 * Draw the QRCode in the qrCodeView
 */
227
- (void)drawQRCode:(NSString*) uriToDraw
228
{
229
    auto qrCode = QRcode_encodeString(uriToDraw.UTF8String,
230 231 232 233 234 235 236 237
                                      0,
                                      QR_ECLEVEL_L, // Lowest level of error correction
                                      QR_MODE_8, // 8-bit data mode
                                      1);
    if (!qrCode) {
        return;
    }

238 239 240 241 242 243
    unsigned char *data = 0;
    int width;
    data = qrCode->data;
    width = qrCode->width;
    int qr_margin = 3;

244 245 246 247 248 249 250 251 252 253
    CGFloat size = qrcodeView.frame.size.width;

    // create context
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);

    CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
    CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));

254
    float zoom = ceil((double)size / (qrCode->width + 2.0 * qr_margin));
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
    CGRect rectDraw = CGRectMake(0, 0, zoom, zoom);

    int ran;
    for(int i = 0; i < width; ++i) {
        for(int j = 0; j < width; ++j) {
            if(*data & 1) {
                CGContextSetFillColorWithColor(ctx, [NSColor ringDarkGrey].CGColor);
                rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
                CGContextAddRect(ctx, rectDraw);
                CGContextFillPath(ctx);
            } else {
                CGContextSetFillColorWithColor(ctx, [NSColor windowBackgroundColor].CGColor);
                rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom);
                CGContextAddRect(ctx, rectDraw);
                CGContextFillPath(ctx);
            }
            ++data;
        }
    }
274 275 276 277 278 279 280 281 282 283 284 285

    // get image
    auto qrCGImage = CGBitmapContextCreateImage(ctx);
    auto qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:qrcodeView.frame.size];

    // some releases
    CGContextRelease(ctx);
    CGImageRelease(qrCGImage);
    CGColorSpaceRelease(colorSpace);
    QRcode_free(qrCode);

    [qrcodeView setImage:qrImage];
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
}

/**
 * Start the in/out animation displaying the QRCode
 * @param show should the QRCode be animated in or out
 */
- (void) showQRCode:(BOOL) show
{
    static const NSInteger offset = 110;
    [NSAnimationContext beginGrouping];
    NSAnimationContext.currentContext.duration = 0.5;
    [[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
    qrcodeView.animator.alphaValue = show ? 1.0 : 0.0;
    [centerYQRCodeConstraint.animator setConstant: show ? offset : 0];
    [centerYWelcomeContainerConstraint.animator setConstant:show ? -offset : 0];
    [NSAnimationContext endGrouping];
}

Alexandre Lision's avatar
Alexandre Lision committed
304 305
- (IBAction)openPreferences:(id)sender
{
Alexandre Lision's avatar
Alexandre Lision committed
306 307
    preferencesWC = [[PreferencesWC alloc] initWithWindowNibName:@"PreferencesWindow"];
    [preferencesWC.window makeKeyAndOrderFront:preferencesWC.window];
308
}
309

310 311 312 313 314 315 316 317 318 319 320 321
- (IBAction)callClickedAtRow:(id)sender
{
    NSTabViewItem *selectedTab = [smartViewVC.tabbar selectedTabViewItem];
    int index = [smartViewVC.tabbar indexOfTabViewItem:selectedTab];
    switch (index) {
        case 0:
            [smartViewVC startCallForRow:sender];
            break;
        default:
            break;
    }
}
Anthony Léonard's avatar
Anthony Léonard committed
322

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
#pragma mark - Ring account migration

- (void) migrateRingAccount:(Account*) acc
{
    self.migrateWC = [[MigrateRingAccountsWC alloc] initWithDelegate:self actionCode:1];
    self.migrateWC.account = acc;
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
    [self.window beginSheet:self.migrateWC.window completionHandler:nil];
#else
    [NSApp beginSheet: self.migrateWC.window
       modalForWindow: self.window
        modalDelegate: self
       didEndSelector: nil
          contextInfo: nil];
#endif
}

340
// TODO: Reimplement as a blocking loop when new LRC models handle migration
341 342 343 344 345 346 347 348 349 350 351
- (void)checkAccountsToMigrate
{
    auto ringList = AccountModel::instance().accountsToMigrate();
    if (ringList.length() > 0){
        Account* acc = ringList.value(0);
        [self migrateRingAccount:acc];
    } else {
        // Fresh run, we need to make sure RingID appears
        [shareButton sendActionOn:NSLeftMouseDownMask];

        [self connect];
352
        [self updateRingID];
353 354 355 356
        // display accounts to select
        NSToolbar *toolbar = self.window.toolbar;
        toolbar.delegate = self;
        [toolbar insertItemWithItemIdentifier:kChangeAccountToolBarItemIdentifier atIndex:1];
357 358 359 360 361 362 363 364 365 366 367 368 369
    }
}

- (void)migrationDidComplete
{
    [self checkAccountsToMigrate];
}

- (void)migrationDidCompleteWithError
{
    [self checkAccountsToMigrate];
}

370 371 372 373 374 375 376
-(void)selectAccount:(const lrc::api::account::Info&)accInfo
{
    // If the selected account has been changed, we close any open panel
    if ([smartViewVC setConversationModel:accInfo.conversationModel.get()]) {
        [currentCallVC animateOut];
        [offlineVC animateOut];
    }
377 378 379

    // Welcome view informations are also updated
    [self updateRingID];
380 381
}

382 383 384 385 386
-(void)rightPanelClosed
{
    [smartViewVC deselect];
}

Anthony Léonard's avatar
Anthony Léonard committed
387 388 389 390 391
-(void)currentConversationTrusted
{
    [smartViewVC selectConversationList];
}

392 393 394 395 396
-(void) listTypeChanged {
    [offlineVC animateOut];
    [currentCallVC animateOut];
}

397
#pragma mark - NSToolbarDelegate
398 399 400 401 402 403 404 405
- (nullable NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
    if(itemIdentifier == kChangeAccountToolBarItemIdentifier) {
        NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:kChangeAccountToolBarItemIdentifier];
        toolbarItem.view = chooseAccountVC.view;
        return toolbarItem;
    }
    return nil;
406 407
}

Alexandre Lision's avatar
Alexandre Lision committed
408
@end