ChooseAccountVC.mm 15.8 KB
Newer Older
1 2 3
/*
 *  Copyright (C) 2015-2017 Savoir-faire Linux Inc.
 *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
4 5
 *  Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>
 *  Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 *  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.
 */

#import "ChooseAccountVC.h"

//Qt
#import <QSize>
#import <QtMacExtras/qmacfunctions.h>
#import <QPixmap>

//LRC
#import <globalinstances.h>
#import <QItemSelectionModel.h>
#import <interfaces/pixmapmanipulatori.h>
33 34
#import <api/newaccountmodel.h>
#import <api/account.h>
35

36 37
//RING
#import "views/AccountMenuItemView.h"
38
#import "AccountSelectionManager.h"
39
#import "RingWindowController.h"
40 41
#import "utils.h"
#import "views/NSColor+RingTheme.h"
42
#import "views/RoundedTextField.h"
43

44 45 46 47
@interface NSMenu ()
- (void) _setHasPadding: (BOOL) pad onEdge: (int) whatEdge;
@end

48 49 50 51 52
@interface ChooseAccountVC () <NSMenuDelegate>

@end

@implementation ChooseAccountVC {
53

54
    __unsafe_unretained IBOutlet NSImageView*   profileImage;
55 56
    __unsafe_unretained IBOutlet RoundedTextField* accountStatus;
    __unsafe_unretained IBOutlet NSTextField* selectedAccountTitle;
57
    __unsafe_unretained IBOutlet NSPopUpButton* accountSelectionButton;
58
    lrc::api::NewAccountModel* accMdl_;
59
    AccountSelectionManager* accountSelectionManager_;
60 61 62 63 64
}
Boolean menuIsOpen;
Boolean menuNeedsUpdate;
NSMenu* accountsMenu;
NSMenuItem* selectedMenuItem;
65
NSMutableDictionary* menuItemsTags;
66

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
67 68 69 70 71 72 73 74 75
//-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil model:(lrc::api::NewAccountModel*) accMdl delegate:(id <ChooseAccountDelegate> )mainWindow
//{
//    accMdl_ = accMdl;
//    accountSelectionManager_ = [[AccountSelectionManager alloc] initWithAccountModel:accMdl_];
//    self.delegate = mainWindow;
//    return [self initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
//}

-(void) updateWithDelegate:(id <ChooseAccountDelegate> )mainWindow andModel:(lrc::api::NewAccountModel*) accMdl {
76
    accMdl_ = accMdl;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
77 78 79
        accountSelectionManager_ = [[AccountSelectionManager alloc] initWithAccountModel:accMdl_];
        self.delegate = mainWindow;
    [self initView];
80
}
81

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
82
- (void)initView
83 84 85 86
{
    [profileImage setWantsLayer: YES];
    profileImage.layer.cornerRadius = profileImage.frame.size.width / 2;
    profileImage.layer.masksToBounds = YES;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
87
    profileImage.layer.backgroundColor = [[NSColor disabledControlTextColor] CGColor];
88

89
    accountsMenu = [[NSMenu alloc] initWithTitle:@""];
90 91 92 93 94
    if ([accountsMenu respondsToSelector: @selector(_setHasPadding:onEdge:)])
    {
        [accountsMenu _setHasPadding: NO onEdge: 1];
        [accountsMenu _setHasPadding: NO onEdge: 3];
    }
95 96
    [accountsMenu setDelegate:self];
    accountSelectionButton.menu = accountsMenu;
97
    [accountSelectionButton setAutoenablesItems:NO];
98
    menuItemsTags = [[NSMutableDictionary alloc] init];
99 100
    [self update];

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
101

102 103
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountAdded,
104
                     [self] (const std::string& accountID) {
105
                         [self update];
106 107
                         @try {
                             auto& account = [self selectedAccount];
108
                             [self.delegate selectAccount:account currentRemoved: NO];
109 110 111 112
                         }
                         @catch (NSException * e) {
                             NSLog(@"account selection failed");
                         }
113
                     });
114 115
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountRemoved,
116 117 118 119 120 121
                     [self] (const std::string& accountID) {
                         if ([self selectedAccount].id.compare(accountID) == 0) {
                             [accountSelectionManager_ clearSelectedAccount];
                         }
                         @try {
                             auto& account = [self selectedAccount];
122
                             [self.delegate selectAccount:account currentRemoved: YES];
123 124
                         }
                         @catch (NSException * e) {
125
                             [self.delegate allAccountsDeleted];
126
                         }
127 128
                         [self update];
                     });
129 130
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::profileUpdated,
131
                     [self] (const std::string& accountID) {
132 133
                         [self update];
                     });
134 135 136
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountStatusChanged,
                     [self] (const std::string& accountID) {
137
                         [self updateMenuItemForAccount:accountID];
138
                         if([self selectedAccount].id == accountID) {
139 140 141
                             // update account state
                             [self updatePhoto];
                             [self setPopUpButtonSelection];
142
                         }
143
                     });
144 145
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
146

147 148
-(const lrc::api::account::Info&) selectedAccount
{
149 150
    try {
        return [accountSelectionManager_ savedAccount];
151
    } catch (NSException *ex) {
152 153 154
        auto accountList = accMdl_->getAccountList();
        if (!accountList.empty()) {
            const auto& fallbackAccount = accMdl_->getAccountInfo(accountList.at(0));
155
            [accountSelectionManager_ setSavedAccount:fallbackAccount];
156
            return fallbackAccount;
157 158 159 160 161 162
        } else {
            NSException* noAccEx = [NSException
                                    exceptionWithName:@"NoAccountException"
                                    reason:@"No account in AccountModel"
                                    userInfo:nil];
            @throw noAccEx;
163 164 165 166
        }
    }
}

167
-(void) updateMenuItemForAccount: (const std::string&) accountID {
168 169 170 171 172
    NSMenuItem *item  =[accountsMenu itemWithTag:[menuItemsTags[@(accountID.c_str())] intValue]];
    if(!item) {return;}
    AccountMenuItemView *itemView =item.view;
    if(!itemView) {return;}
    [self configureView:itemView forAccount:accountID forMenuItem: item];
173 174
}

175 176 177
-(void) updateMenu {
    [accountsMenu removeAllItems];

178 179 180 181 182
    auto accList = accMdl_->getAccountList();

    for (std::string accId : accList) {
        auto& account = accMdl_->getAccountInfo(accId);

183
        NSMenuItem* menuBarItem = [[NSMenuItem alloc]
184 185 186 187
                                   initWithTitle:[self itemTitleForAccount:account]
                                   action:NULL
                                   keyEquivalent:@""];

188
        AccountMenuItemView *itemView = [[AccountMenuItemView alloc] initWithFrame:CGRectZero];
189 190 191 192
        [self configureView:itemView forAccount:accId forMenuItem: menuBarItem];
        int itemTag = arc4random_uniform(1000);
        menuItemsTags[@(accId.c_str())] = [NSNumber numberWithInt: itemTag];
        [menuBarItem setTag:itemTag];
193 194 195
        [menuBarItem setView:itemView];
        [accountsMenu addItem:menuBarItem];
    }
196 197 198

    // create "add a new account" menu item
    NSMenuItem* menuBarItem = [[NSMenuItem alloc]
199
                               initWithTitle:@"Add Account"
200 201 202 203 204 205 206 207 208 209 210 211
                               action:nil
                               keyEquivalent:@""];
    AccountMenuItemView *itemView = [[AccountMenuItemView alloc] initWithFrame:CGRectZero];
    [itemView.accountAvatar setHidden:YES];
    [itemView.accountStatus setHidden:YES];
    [itemView.accountTypeLabel setHidden:YES];
    [itemView.userNameLabel setHidden:YES];
    [itemView.accountLabel setHidden:YES];
    [itemView.createNewAccount setAction:@selector(createNewAccount:)];
    [itemView.createNewAccount setTarget:self];
    [menuBarItem setView: itemView];
    [accountsMenu addItem: menuBarItem];
212 213
    [profileImage setHidden:accList.empty()];
    [accountStatus setHidden:accList.empty()];
214 215
}

216
-(void) configureView: (AccountMenuItemView *) itemView forAccount:(const std::string&) accountId forMenuItem:(NSMenuItem *) item {
217
    auto& account = accMdl_->getAccountInfo(accountId);
218
    item.attributedTitle = [self attributedItemTitleForAccount:account];
219 220 221
    [itemView.accountLabel setStringValue:@(account.profileInfo.alias.c_str())];
    NSString* userNameString = [self nameForAccount: account];
    [itemView.userNameLabel setStringValue:userNameString];
222 223 224 225
    NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(account.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSImage *image = [[NSImage alloc] initWithData:imageData];
    if(image) {
        [itemView.accountAvatar setImage: image];
226 227 228
    } else {
        [itemView.accountAvatar setImage: [NSImage imageNamed:@"default_avatar_overlay.png"]];
    }
229
    itemView.accountStatus.bgColor = colorForAccountStatus(account.status);
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    switch (account.profileInfo.type) {
        case lrc::api::profile::Type::SIP:
            [itemView.accountTypeLabel setStringValue:@"SIP"];
            break;
        case lrc::api::profile::Type::RING:
            [itemView.accountTypeLabel setStringValue:@"RING"];
            break;
        default:
            break;
    }
    [itemView.createNewAccount setHidden:YES];
    [itemView.createNewAccountImage setHidden:YES];
}


- (void)createNewAccount:(id)sender {
    [accountSelectionButton.menu cancelTrackingWithoutAnimation];
247
    [self.delegate createNewAccount];
248 249
}

250 251
-(void) updatePhoto
{
252 253 254 255
    @try {
        auto& account = [self selectedAccount];
        if(account.profileInfo.type == lrc::api::profile::Type::INVALID)
            return;
256 257 258 259
        NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(account.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
        NSImage *image = [[NSImage alloc] initWithData:imageData];
        if(image) {
            [profileImage setImage: image];
260 261 262
        } else {
            [profileImage setImage: [NSImage imageNamed:@"default_avatar_overlay.png"]];
        }
263 264
        accountStatus.bgColor = colorForAccountStatus(account.status);
        [accountStatus setNeedsDisplay: YES];
265 266 267 268
    }
    @catch (NSException *ex) {
        NSLog(@"Caught exception %@: %@", [ex name], [ex reason]);
    }
269 270 271
}

-(NSString*) nameForAccount:(const lrc::api::account::Info&) account {
272
    return bestIDForAccount(account);
273 274
}

275
-(NSString*) itemTitleForAccount:(const lrc::api::account::Info&) account {
276
    NSString* alias = bestNameForAccount(account);
277
    NSString* userNameString = [self nameForAccount: account];
278 279
    if([alias isEqualToString:userNameString] || [userNameString length] == 0) {
        return alias;
280
    }
281
    alias = [NSString stringWithFormat: @"%@\n", alias];
282 283 284
    return [alias stringByAppendingString:userNameString];
}

285
- (NSAttributedString*) attributedItemTitleForAccount:(const lrc::api::account::Info&) account {
286
    NSString* alias = bestNameForAccount(account);
287
    NSString* userNameString = [self nameForAccount: account];
288
    NSFont *fontAlias = [NSFont fontWithName:@"Helvetica Neue Light" size:16.0];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
289
    NSFont *fontUserName = [NSFont fontWithName:@"Helvetica Neue Light" size:13.0];
290 291
    NSColor *colorAlias = [NSColor textColor];
    NSColor *colorAUserName = [NSColor labelColor];
292 293
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
294
    paragraphStyle.lineSpacing = 3;
295 296
    NSDictionary *aliasAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                fontAlias,NSFontAttributeName,
297 298
                                colorAlias,NSForegroundColorAttributeName,
                                paragraphStyle,NSParagraphStyleAttributeName, nil];
299 300
    NSDictionary *userNameAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                   fontUserName,NSFontAttributeName,
301 302
                                   colorAUserName,NSForegroundColorAttributeName,
                                   paragraphStyle,NSParagraphStyleAttributeName, nil];
303 304 305 306 307 308 309 310 311

    if([alias isEqualToString:userNameString] || [userNameString length] == 0) {
        paragraphStyle.paragraphSpacingBefore = 20;
        aliasAttrs = [NSDictionary dictionaryWithObjectsAndKeys:fontAlias,NSFontAttributeName,
                      colorAlias,NSForegroundColorAttributeName,
                      paragraphStyle,NSParagraphStyleAttributeName, nil];
        return [[NSAttributedString alloc] initWithString:alias attributes:aliasAttrs];
    }
    alias = [NSString stringWithFormat: @"%@\n", alias];
312 313 314 315 316 317 318 319 320 321 322 323 324
    NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:alias attributes:aliasAttrs];
    NSAttributedString* attributedStringSecond= [[NSAttributedString alloc] initWithString:userNameString attributes:userNameAttrs];
    NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];
    [result appendAttributedString:attributedString];
    [result appendAttributedString:attributedStringSecond];
    return result;
}

-(void) update {
    if(menuIsOpen) {
        return;
    }
    [self updateMenu];
325
    [self setPopUpButtonSelection];
326
    [self updatePhoto];
327 328
}

329
-(void) setPopUpButtonSelection {
330 331 332 333
    if(accountsMenu.itemArray.count == 0) {
        [self.view setHidden:YES];
        return;
    }
334
    [self.view setHidden:NO];
335 336 337 338 339
    @try {
        auto& account = [self selectedAccount];
        if(account.profileInfo.type == lrc::api::profile::Type::INVALID){
            return;
        }
340
        [accountSelectionButton selectItemWithTag:[menuItemsTags[@(account.id.c_str())] intValue]];
341
        [selectedAccountTitle setAttributedStringValue: accountSelectionButton.attributedTitle];
342 343 344
    }
    @catch (NSException *ex) {
        NSLog(@"Caught exception %@: %@", [ex name], [ex reason]);
345 346 347 348 349 350
    }
}

#pragma mark - NSPopUpButton item selection

- (IBAction)itemChanged:(id)sender {
351
    NSInteger row = [(NSPopUpButton *)sender indexOfSelectedItem];
352 353
    auto accList = accMdl_->getAccountList();
    if (row >= accList.size())
354
        return;
355 356 357

    auto& account = accMdl_->getAccountInfo(accList[row]);
    [accountSelectionManager_ setSavedAccount:account];
358
    [self.delegate selectAccount:account currentRemoved: NO];
359
    [self updatePhoto];
360
    [selectedAccountTitle setAttributedStringValue: accountSelectionButton.attributedTitle];
361 362
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
363 364 365 366
- (IBAction)openMenu:(id)sender {
    [accountSelectionButton performClick:nil];
}

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
#pragma mark - NSMenuDelegate
- (void)menuWillOpen:(NSMenu *)menu {
    menuIsOpen = true;
    // remember selected item to remove highlighting when menu is open
    selectedMenuItem = [accountSelectionButton selectedItem];
}
- (void)menuDidClose:(NSMenu *)menu {

    menuIsOpen = false;
}

- (void)menu:(NSMenu *)menu willHighlightItem:(nullable NSMenuItem *)item {
    if (!selectedMenuItem || selectedMenuItem == item) {
        return;
    }
    int index = [menu indexOfItem:selectedMenuItem];
    [menu removeItemAtIndex:index];
    [menu insertItem:selectedMenuItem atIndex:index];
    [accountSelectionButton selectItemAtIndex:index];
    selectedMenuItem = nil;
}

389 390 391 392 393 394 395
-(void) enable {
    [accountSelectionButton setEnabled:YES];
}
-(void) disable {
    [accountSelectionButton setEnabled:NO];
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
396 397 398 399 400 401 402 403 404 405
- (void)selectAccount:(NSString*)accountID {
    auto accList = accMdl_->getAccountList();
    if(std::find(accList.begin(), accList.end(), [accountID UTF8String]) != accList.end()) {
        auto& account = accMdl_->getAccountInfo([accountID UTF8String]);
        [accountSelectionManager_ setSavedAccount:account];
        [self updatePhoto];
        [self setPopUpButtonSelection];
    }
}

406
@end