ChooseAccountVC.mm 16.7 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
 *
 *  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 <interfaces/pixmapmanipulatori.h>
31
32
#import <api/newaccountmodel.h>
#import <api/account.h>
33

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

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

46
47
48
49
50
@interface ChooseAccountVC () <NSMenuDelegate>

@end

@implementation ChooseAccountVC {
51

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

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
66
-(void) updateWithDelegate:(id <ChooseAccountDelegate> )mainWindow andModel:(lrc::api::NewAccountModel*) accMdl {
67
    accMdl_ = accMdl;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
68
69
70
        accountSelectionManager_ = [[AccountSelectionManager alloc] initWithAccountModel:accMdl_];
        self.delegate = mainWindow;
    [self initView];
71
}
72

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
73
- (void)initView
74
75
76
77
{
    [profileImage setWantsLayer: YES];
    profileImage.layer.cornerRadius = profileImage.frame.size.width / 2;
    profileImage.layer.masksToBounds = YES;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
78
    profileImage.layer.backgroundColor = [[NSColor disabledControlTextColor] CGColor];
79

80
    accountsMenu = [[NSMenu alloc] initWithTitle:@""];
81
82
83
84
85
    if ([accountsMenu respondsToSelector: @selector(_setHasPadding:onEdge:)])
    {
        [accountsMenu _setHasPadding: NO onEdge: 1];
        [accountsMenu _setHasPadding: NO onEdge: 3];
    }
86
87
    [accountsMenu setDelegate:self];
    accountSelectionButton.menu = accountsMenu;
88
    [accountSelectionButton setAutoenablesItems:NO];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
89
    menuItemsTags = [[NSMutableDictionary alloc] init];
90
91
    rendezVousIndicator.image = [NSColor image: [NSImage imageNamed:@"ic_group.png"] tintedWithColor: [NSColor textColor]];
    [NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
92
93
    [self update];

94
95
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountAdded,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
96
                     [self] (const QString& accountID) {
97
                         [self update];
98
99
                         @try {
                             auto& account = [self selectedAccount];
100
                             [self.delegate selectAccount:account currentRemoved: NO];
101
102
103
104
                         }
                         @catch (NSException * e) {
                             NSLog(@"account selection failed");
                         }
105
                     });
106
107
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountRemoved,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
108
                     [self] (const QString& accountID) {
109
110
111
112
113
                         if ([self selectedAccount].id.compare(accountID) == 0) {
                             [accountSelectionManager_ clearSelectedAccount];
                         }
                         @try {
                             auto& account = [self selectedAccount];
114
                             [self.delegate selectAccount:account currentRemoved: YES];
115
116
                         }
                         @catch (NSException * e) {
117
                             [self.delegate allAccountsDeleted];
118
                         }
119
120
                         [self update];
                     });
121
122
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::profileUpdated,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
123
                     [self] (const QString& accountID) {
124
125
                         [self update];
                     });
126
127
    QObject::connect(accMdl_,
                     &lrc::api::NewAccountModel::accountStatusChanged,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
128
                     [self] (const QString& accountID) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
129
                         [self updateMenuItemForAccount:accountID];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
130
                         if([self selectedAccount].id == accountID) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
131
132
133
                             // update account state
                             [self updatePhoto];
                             [self setPopUpButtonSelection];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
134
                         }
135
                     });
136
137
}

138
139
140
-(void)themeChanged:(NSNotification *) notification {
    rendezVousIndicator.image = [NSColor image: [NSImage imageNamed:@"ic_group.png"] tintedWithColor: [NSColor textColor]];
}
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
141

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

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
162
163
-(void) updateMenuItemForAccount: (const QString&) accountID {
    NSMenuItem *item  =[accountsMenu itemWithTag:[menuItemsTags[accountID.toNSString()] intValue]];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
164
165
166
167
    if(!item) {return;}
    AccountMenuItemView *itemView =item.view;
    if(!itemView) {return;}
    [self configureView:itemView forAccount:accountID forMenuItem: item];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
168
169
}

170
171
172
-(void) updateMenu {
    [accountsMenu removeAllItems];

173
174
    auto accList = accMdl_->getAccountList();

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
175
    for (auto accId : accList) {
176
177
        auto& account = accMdl_->getAccountInfo(accId);

178
        NSMenuItem* menuBarItem = [[NSMenuItem alloc]
179
180
181
182
                                   initWithTitle:[self itemTitleForAccount:account]
                                   action:NULL
                                   keyEquivalent:@""];

183
        AccountMenuItemView *itemView = [[AccountMenuItemView alloc] initWithFrame:CGRectZero];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
184
185
        [self configureView:itemView forAccount:accId forMenuItem: menuBarItem];
        int itemTag = arc4random_uniform(1000);
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
186
        menuItemsTags[accId.toNSString()] = [NSNumber numberWithInt: itemTag];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
187
        [menuBarItem setTag:itemTag];
188
189
190
        [menuBarItem setView:itemView];
        [accountsMenu addItem:menuBarItem];
    }
191
192
193

    // create "add a new account" menu item
    NSMenuItem* menuBarItem = [[NSMenuItem alloc]
194
                               initWithTitle:@"Add Account"
195
196
197
198
199
200
201
202
                               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];
203
    [itemView.rendezVousIndicator setHidden:YES];
204
205
206
207
    [itemView.createNewAccount setAction:@selector(createNewAccount:)];
    [itemView.createNewAccount setTarget:self];
    [menuBarItem setView: itemView];
    [accountsMenu addItem: menuBarItem];
208
209
    [profileImage setHidden:accList.empty()];
    [accountStatus setHidden:accList.empty()];
210
211
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
212
-(void) configureView: (AccountMenuItemView *) itemView forAccount:(const QString&) accountId forMenuItem:(NSMenuItem *) item {
213
    auto& account = accMdl_->getAccountInfo(accountId);
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
214
    item.attributedTitle = [self attributedItemTitleForAccount:account];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
215
216
217
218
219
220
    auto profileName = bestNameForAccount(account);
    auto registeredName = [self nameForAccount: account];
    [itemView.accountLabel setStringValue: profileName];
    if (![profileName isEqualTo:registeredName]) {
        [itemView.userNameLabel setStringValue:registeredName];
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
221
222
223
224
225
226
227
228
    @autoreleasepool {
        NSData *imageData = [[NSData alloc] initWithBase64EncodedString:account.profileInfo.avatar.toNSString() options:NSDataBase64DecodingIgnoreUnknownCharacters];
        NSImage *image = [[NSImage alloc] initWithData:imageData];
        if(image) {
            [itemView.accountAvatar setImage: image];
        } else {
            [itemView.accountAvatar setImage: [NSImage imageNamed:@"default_avatar_overlay.png"]];
        }
229
    }
230
    itemView.accountStatus.bgColor = colorForAccountStatus(account.status);
231
232
233
234
    switch (account.profileInfo.type) {
        case lrc::api::profile::Type::SIP:
            [itemView.accountTypeLabel setStringValue:@"SIP"];
            break;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
235
        case lrc::api::profile::Type::JAMI:
236
            [itemView.accountTypeLabel setStringValue:@"Jami"];
237
238
239
240
241
242
            break;
        default:
            break;
    }
    [itemView.createNewAccount setHidden:YES];
    [itemView.createNewAccountImage setHidden:YES];
243
244
    lrc::api::account::ConfProperties_t accountProperties = account.accountModel->getAccountConfig(accountId);
    [itemView.rendezVousIndicator setHidden: !accountProperties.isRendezVous];
245
246
247
248
}

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

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

-(NSString*) nameForAccount:(const lrc::api::account::Info&) account {
276
    return bestIDForAccount(account);
277
278
}

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

289
- (NSAttributedString*) attributedItemTitleForAccount:(const lrc::api::account::Info&) account {
290
    NSString* alias = bestNameForAccount(account);
291
    NSString* userNameString = [self nameForAccount: account];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
292
293
294
    NSFont *fontAlias = [NSFont systemFontOfSize: 18 weight: NSFontWeightMedium];
    NSFont *fontUserName = [NSFont systemFontOfSize: 14 weight: NSFontWeightLight];
    NSColor *colorAlias = [NSColor labelColor];
295
    NSColor *colorAUserName = [NSColor labelColor];
296
297
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
298
    paragraphStyle.lineSpacing = 2;
299
300
    NSDictionary *aliasAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                fontAlias,NSFontAttributeName,
301
302
                                colorAlias,NSForegroundColorAttributeName,
                                paragraphStyle,NSParagraphStyleAttributeName, nil];
303
304
    NSDictionary *userNameAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
                                   fontUserName,NSFontAttributeName,
305
306
                                   colorAUserName,NSForegroundColorAttributeName,
                                   paragraphStyle,NSParagraphStyleAttributeName, nil];
307
308
309
310
311
312
313
314
315

    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];
316
317
318
319
320
321
322
323
324
325
326
327
328
    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];
329
    [self setPopUpButtonSelection];
330
    [self updatePhoto];
331
332
}

333
-(void) setPopUpButtonSelection {
334
335
336
337
    if(accountsMenu.itemArray.count == 0) {
        [self.view setHidden:YES];
        return;
    }
338
    [self.view setHidden:NO];
339
340
341
342
343
    @try {
        auto& account = [self selectedAccount];
        if(account.profileInfo.type == lrc::api::profile::Type::INVALID){
            return;
        }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
344
        [accountSelectionButton selectItemWithTag:[menuItemsTags[account.id.toNSString()] intValue]];
345
346
        lrc::api::account::ConfProperties_t accountProperties = account.accountModel->getAccountConfig(account.id);
        [rendezVousIndicator setHidden: !accountProperties.isRendezVous];
347
        [selectedAccountTitle setAttributedStringValue: accountSelectionButton.attributedTitle];
348
349
350
    }
    @catch (NSException *ex) {
        NSLog(@"Caught exception %@: %@", [ex name], [ex reason]);
351
352
353
354
355
356
    }
}

#pragma mark - NSPopUpButton item selection

- (IBAction)itemChanged:(id)sender {
357
    NSInteger row = [(NSPopUpButton *)sender indexOfSelectedItem];
358
359
    auto accList = accMdl_->getAccountList();
    if (row >= accList.size())
360
        return;
361
362
363

    auto& account = accMdl_->getAccountInfo(accList[row]);
    [accountSelectionManager_ setSavedAccount:account];
364
    [self.delegate selectAccount:account currentRemoved: NO];
365
    [self updatePhoto];
366
367
    lrc::api::account::ConfProperties_t accountProperties = account.accountModel->getAccountConfig(account.id);
    [rendezVousIndicator setHidden: !accountProperties.isRendezVous];
368
    [selectedAccountTitle setAttributedStringValue: accountSelectionButton.attributedTitle];
369
370
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
371
372
373
374
- (IBAction)openMenu:(id)sender {
    [accountSelectionButton performClick:nil];
}

375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#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;
}

397
398
399
400
401
402
403
-(void) enable {
    [accountSelectionButton setEnabled:YES];
}
-(void) disable {
    [accountSelectionButton setEnabled:NO];
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
404
405
- (void)selectAccount:(NSString*)accountID {
    auto accList = accMdl_->getAccountList();
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
406
407
    if (!accList.contains(QString::fromNSString(accountID))) {
        return;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
408
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
409
410
411
412
    auto& account = accMdl_->getAccountInfo(QString::fromNSString(accountID));
    [accountSelectionManager_ setSavedAccount:account];
    [self updatePhoto];
    [self setPopUpButtonSelection];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
413
414
}

415
@end