diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c403e038eb253a1b4dabcb06e944e11fc365f8a..37fc1843609a481cfea4ab4dc78550e0d3cf804a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,7 +136,11 @@ SET(ringclient_CONTROLLERS src/BrokerVC.mm src/BrokerVC.h src/ConversationVC.mm - src/ConversationVC.h) + src/ConversationVC.h + src/LoadingWCDelegate.h + src/AbstractLoadingWC.h + src/AbstractLoadingWC.mm +) SET(ringclient_BACKENDS src/backends/AddressBookBackend.mm diff --git a/src/AbstractLoadingWC.h b/src/AbstractLoadingWC.h new file mode 100644 index 0000000000000000000000000000000000000000..baa61b627666df5aac0f5c05b15ee3b307cbd979 --- /dev/null +++ b/src/AbstractLoadingWC.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015-2016 Savoir-faire Linux Inc. + * Author: LoÃŊc Siret <loic.siret@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. + */ + +#import <Cocoa/Cocoa.h> +#import "LoadingWCDelegate.h" +#import "views/ITProgressIndicator.h" + +@interface AbstractLoadingWC : NSWindowController +{ +@protected + __unsafe_unretained IBOutlet NSView* errorContainer; + + __unsafe_unretained IBOutlet NSView* progressContainer; + + __unsafe_unretained IBOutlet NSView* initialContainer; + __unsafe_unretained IBOutlet NSView* finalContainer; +} + +/* + * Delegate to inform about completion of the linking process between + * a ContactMethod and a Person. + */ +@property (retain, nonatomic) id <LoadingWCDelegate> delegate; + +/* + * caller specific code to identify ongoing action + */ +@property (nonatomic) NSInteger actionCode; + + +- (id)initWithWindowNibName:(NSString *)nibName + delegate:(id <LoadingWCDelegate>) del + actionCode:(NSInteger) code; + +- (id)initWithDelegate:(id <LoadingWCDelegate>) del + actionCode:(NSInteger) code; + +- (void)close; +- (void)showLoading; +- (void)showError; +- (void)showFinal; + +@end diff --git a/src/AbstractLoadingWC.mm b/src/AbstractLoadingWC.mm new file mode 100644 index 0000000000000000000000000000000000000000..9415af48587bff462fdba461ae3850cb44ca87fc --- /dev/null +++ b/src/AbstractLoadingWC.mm @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015-2016 Savoir-faire Linux Inc. + * Author: LoÃŊc Siret <loic.siret@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. + */ + +#import "AbstractLoadingWC.h" + +@interface AbstractLoadingWC() <NSTextFieldDelegate> +{ + +} +@end + +@implementation AbstractLoadingWC +{ + struct { + unsigned int didComplete:1; + unsigned int didCompleteWithActionCode:1; + } delegateRespondsTo; +} + +- (id)initWithWindowNibName:(NSString *)nibName delegate:(id <LoadingWCDelegate>) del actionCode:(NSInteger) code +{ + if ((self = [super initWithWindowNibName:nibName]) != nil) { + [self setDelegate:del]; + self.actionCode = code; + } + return self; +} + +- (id)initWithDelegate:(id <LoadingWCDelegate>) del actionCode:(NSInteger) code +{ + [NSException raise:NSInternalInconsistencyException + format:@"You must override %@ in a subclass",NSStringFromSelector(_cmd)]; + return nil; +} + + +- (void)windowDidLoad +{ + [super windowDidLoad]; + [initialContainer setHidden:NO]; + [progressContainer setHidden:YES]; + [errorContainer setHidden:YES]; + [finalContainer setHidden:YES]; +} + +- (IBAction) cancelPressed:(id)sender +{ + [self close]; +} + +- (IBAction)completeAction:(id)sender +{ + [NSException raise:NSInternalInconsistencyException + format:@"You must override %@ in a subclass",NSStringFromSelector(_cmd)]; +} + +- (void)close +{ + [NSApp endSheet:self.window]; + [self.window orderOut:self]; +} + + +- (void)showLoading +{ + [initialContainer setHidden:YES]; + [progressContainer setHidden:NO]; + [errorContainer setHidden:YES]; + [finalContainer setHidden:YES]; +} + +- (void)showError +{ + [initialContainer setHidden:YES]; + [progressContainer setHidden:YES]; + [errorContainer setHidden:NO]; + [finalContainer setHidden:YES]; +} + +- (void)showFinal +{ + [initialContainer setHidden:YES]; + [progressContainer setHidden:YES]; + [errorContainer setHidden:YES]; + [finalContainer setHidden:NO]; +} + +@end diff --git a/src/AccountsVC.mm b/src/AccountsVC.mm index ba3095bf44abe9d45cfe63b5dacb83e7a56b2c75..c6d14e5c201c64d8a700c04e2b3b30bd642c1275 100644 --- a/src/AccountsVC.mm +++ b/src/AccountsVC.mm @@ -81,10 +81,6 @@ NSInteger const TAG_NAME = 200; NSInteger const TAG_STATUS = 300; NSInteger const TAG_TYPE = 400; -typedef NS_ENUM(NSUInteger, Action) { - ACTION_EXPORT = 0, - ACTION_IMPORT = 1, -}; - (void)awakeFromNib { @@ -199,8 +195,8 @@ typedef NS_ENUM(NSUInteger, Action) { [configPanels insertTabViewItem:ringTabItem atIndex:0]; [configPanels insertTabViewItem:mediaTabItem atIndex:1]; [configPanels insertTabViewItem:advancedTabItem atIndex:2]; - } + - (IBAction)exportAccount:(id)sender { passwordWC = [[PathPasswordWC alloc] initWithDelegate:self actionCode:Action::ACTION_EXPORT]; @@ -214,6 +210,15 @@ typedef NS_ENUM(NSUInteger, Action) { contextInfo: nil]; #endif [passwordWC setAllowFileSelection:NO]; + if(treeController.selectedNodes.count > 0) { + QStringList accounts; + for (id item : [treeController selectedNodes]) { + QModelIndex accIdx = [treeController toQIdx:item]; + accounts << AccountModel::instance().getAccountByModelIndex(accIdx)->id(); + } + [passwordWC setAccounts:accounts]; + } + [passwordWC showWindow:self]; } - (IBAction)importAccount:(id)sender @@ -228,6 +233,8 @@ typedef NS_ENUM(NSUInteger, Action) { didEndSelector: nil contextInfo: nil]; #endif + [passwordWC setAllowFileSelection:YES]; + [passwordWC showWindow:self]; } - (IBAction)toggleAccount:(NSButton*)sender { @@ -329,66 +336,6 @@ typedef NS_ENUM(NSUInteger, Action) { } } --(void) didCompleteWithPath:(NSURL*) path Password:(NSString*) password ActionCode:(NSInteger)requestCode -{ - switch (requestCode) { - case Action::ACTION_EXPORT: - if(treeController.selectedNodes.count > 0) { - QStringList accounts; - for (id item : [treeController selectedNodes]) { - QModelIndex accIdx = [treeController toQIdx:item]; - accounts << AccountModel::instance().getAccountByModelIndex(accIdx)->id(); - - } - auto finalURL = [path URLByAppendingPathComponent:@"accounts.ring"]; - [passwordWC showLoading]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - int result = AccountModel::instance().exportAccounts(accounts, finalURL.path.UTF8String, password.UTF8String); - switch (result) { - case 0: - [[NSWorkspace sharedWorkspace] selectFile:finalURL.path inFileViewerRootedAtPath:@""]; - [passwordWC close]; - break; - default: - [passwordWC showError:NSLocalizedString(@"An error occured during the export", @"Error shown to the user" )]; - break; - } - }); - } - break; - case Action::ACTION_IMPORT: { - [passwordWC showLoading]; - SEL sel = @selector(importAccountsWithPath:andPassword:); - NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:sel]]; - [inv setSelector:sel]; - [inv setTarget:self]; - //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation - [inv setArgument:&path atIndex:2]; - [inv setArgument:&password atIndex:3]; - - // Schedule import for next iteration of event loop in order to let us start the loading anim - [inv performSelector:@selector(invoke) withObject:nil afterDelay:0]; - - } - break; - default: - NSLog(@"Unrecognized action %d", requestCode); - break; - } -} - -- (void) importAccountsWithPath:(NSURL*) path andPassword:(NSString*) password -{ - int result = AccountModel::instance().importAccounts(path.path.UTF8String, password.UTF8String); - switch (result) { - case 0: - [passwordWC close]; - break; - default: - [passwordWC showError:NSLocalizedString(@"An error occured during the import", @"Error shown to the user" )]; - break; - } -} #pragma mark - NSMenuDelegate methods @@ -407,4 +354,10 @@ typedef NS_ENUM(NSUInteger, Action) { return AccountModel::instance().protocolModel()->rowCount(); } +#pragma mark - PathPasswordDelegate methods +-(void) didCompleteExportWithPath:(NSURL*) fileUrl +{ + [[NSWorkspace sharedWorkspace] selectFile:fileUrl.path inFileViewerRootedAtPath:@""]; +} + @end diff --git a/src/LoadingWCDelegate.h b/src/LoadingWCDelegate.h new file mode 100644 index 0000000000000000000000000000000000000000..68288a3c632abc613b958a0e3ad598d211b28e4f --- /dev/null +++ b/src/LoadingWCDelegate.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015-2016 Savoir-faire Linux Inc. + * Author: LoÃŊc Siret <loic.siret@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. + */ + +#import <Foundation/Foundation.h> + +@protocol LoadingWCDelegate <NSObject> + +@end diff --git a/src/PathPasswordWC.h b/src/PathPasswordWC.h index 667e481873e5f8e7b787e00b3804b44d327c7b91..f99ecb85fb32f57b3beb0cb751b5668f05509e1c 100644 --- a/src/PathPasswordWC.h +++ b/src/PathPasswordWC.h @@ -19,32 +19,33 @@ #import <Cocoa/Cocoa.h> -@protocol PathPasswordDelegate <NSObject> + +#import <QtCore/qdir.h> + +#import "LoadingWCDelegate.h" +#import "AbstractLoadingWC.h" + +typedef NS_ENUM(NSUInteger, Action) { + ACTION_EXPORT = 0, + ACTION_IMPORT = 1, +}; + +@protocol PathPasswordDelegate <LoadingWCDelegate> @optional --(void) didCompleteWithPath:(NSURL*) path Password:(NSString*) password; --(void) didCompleteWithPath:(NSURL*) path Password:(NSString*) password ActionCode:(NSInteger) requestCode; +-(void) didCompleteExportWithPath:(NSURL*) path; +-(void) didCompleteImport; @end -@interface PathPasswordWC : NSWindowController - -/* - * Delegate to inform about completion of the linking process between - * a ContactMethod and a Person. - */ -@property (nonatomic) id <PathPasswordDelegate> delegate; +@interface PathPasswordWC : AbstractLoadingWC -/* - * caller specific code to identify ongoing action - */ -@property (nonatomic) NSInteger actionCode; -/* - * Custom init +/** + * Allow the NSPathControl of this window to select files or not */ -- (id)initWithDelegate:(id <PathPasswordDelegate>) del actionCode:(NSInteger) code; +@property (nonatomic) BOOL allowFileSelection; /** * password string contained in passwordField. @@ -54,20 +55,8 @@ @property (retain) NSString* password; /** - * Allow the NSPathControl of this window to select files or not - */ -@property (nonatomic) BOOL allowFileSelection; - -/* - * Show progress during action completion + * Object uses to store account to exports */ -- (void)showLoading; - - -/* - * Display error message to the user - */ -- (void)showError:(NSString*) error; - +@property (assign) QStringList accounts; @end diff --git a/src/PathPasswordWC.mm b/src/PathPasswordWC.mm index e2adf4e8694301da8d2edb0428ee1ff87ee4c324..31e3a5008e26c84475cdb27b4589f5d3d8b8eec5 100644 --- a/src/PathPasswordWC.mm +++ b/src/PathPasswordWC.mm @@ -18,56 +18,46 @@ */ #import "PathPasswordWC.h" +//LRC +#import <accountmodel.h> + +//Ring #import "views/ITProgressIndicator.h" @interface PathPasswordWC() <NSTextFieldDelegate>{ - - __unsafe_unretained IBOutlet NSView* errorContainer; - __unsafe_unretained IBOutlet NSTextField* errorLabel; - - __unsafe_unretained IBOutlet ITProgressIndicator* progressView; - - __unsafe_unretained IBOutlet NSView* pathPasswordContainer; - __unsafe_unretained IBOutlet NSSecureTextField* passwordField; __unsafe_unretained IBOutlet NSPathControl* path; - + __unsafe_unretained IBOutlet NSSecureTextField* passwordField; + __unsafe_unretained IBOutlet NSTextField* errorField; + __unsafe_unretained IBOutlet ITProgressIndicator* progressIndicator; } @end @implementation PathPasswordWC { struct { - unsigned int didComplete:1; - unsigned int didCompleteWithActionCode:1; + unsigned int didCompleteExport:1; + unsigned int didCompleteImport:1; } delegateRespondsTo; } +@synthesize accounts; - (id)initWithDelegate:(id <PathPasswordDelegate>) del actionCode:(NSInteger) code { - if ((self = [super initWithWindowNibName:@"PathPasswordWindow"]) != nil) { - [self setDelegate:del]; - self.actionCode = code; - } - return self; + return [super initWithWindowNibName:@"PathPasswordWindow" delegate:del actionCode:code]; } - (void)windowDidLoad { [super windowDidLoad]; [path setURL: [NSURL fileURLWithPath:NSHomeDirectory()]]; - [progressView setNumberOfLines:30]; - [progressView setWidthOfLine:2]; - [progressView setLengthOfLine:5]; - [progressView setInnerMargin:20]; - [progressView setHidden:YES]; } - (void)setDelegate:(id <PathPasswordDelegate>)aDelegate { if (self.delegate != aDelegate) { - _delegate = aDelegate; - delegateRespondsTo.didComplete = [self.delegate respondsToSelector:@selector(didCompleteWithPath:Password:)]; - delegateRespondsTo.didCompleteWithActionCode = [self.delegate respondsToSelector:@selector(didCompleteWithPath:Password:ActionCode:)]; + [super setDelegate: aDelegate]; + delegateRespondsTo.didCompleteExport = [self.delegate respondsToSelector:@selector(didCompleteExportWithPath:)]; + delegateRespondsTo.didCompleteImport = [self.delegate respondsToSelector:@selector(didCompleteWithImport)]; } } @@ -77,34 +67,83 @@ [path setAllowedTypes:_allowFileSelection ? nil : [NSArray arrayWithObject:@"public.folder"]]; } -- (IBAction) cancelPressed:(id)sender -{ - [NSApp endSheet:self.window]; - [self.window orderOut:self]; -} - (IBAction)completeAction:(id)sender { - if (delegateRespondsTo.didComplete) - [self.delegate didCompleteWithPath:path.URL Password:passwordField.stringValue]; - else if (delegateRespondsTo.didCompleteWithActionCode) - [self.delegate didCompleteWithPath:path.URL Password:passwordField.stringValue ActionCode:self.actionCode]; + [self didCompleteWithPath:path.URL Password:passwordField.stringValue ActionCode:self.actionCode]; } + - (void)showLoading { - [progressView setHidden:NO]; - [pathPasswordContainer setHidden:YES]; - [errorContainer setHidden:YES]; - [progressView setAnimates:YES]; + [progressIndicator setNumberOfLines:30]; + [progressIndicator setWidthOfLine:2]; + [progressIndicator setLengthOfLine:5]; + [progressIndicator setInnerMargin:20]; + [super showLoading]; +} + +-(void) didCompleteWithPath:(NSURL*) path Password:(NSString*) password ActionCode:(NSInteger)requestCode +{ + switch (requestCode) { + case Action::ACTION_EXPORT: + { + auto finalURL = [path URLByAppendingPathComponent:@"accounts.ring"]; + [self showLoading]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + int result = AccountModel::instance().exportAccounts(accounts, finalURL.path.UTF8String, password.UTF8String); + switch (result) { + case 0: + if (delegateRespondsTo.didCompleteExport){ + [((id<PathPasswordDelegate>)self.delegate) didCompleteExportWithPath:finalURL]; + } + [self close]; + break; + default:{ + [errorField setStringValue:NSLocalizedString(@"An error occured during the export", @"Error shown to the user" )]; + [self showError] ; + }break; + } + }); + } + break; + case Action::ACTION_IMPORT: { + [self showLoading]; + SEL sel = @selector(importAccountsWithPath:andPassword:); + NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:sel]]; + [inv setSelector:sel]; + [inv setTarget:self]; + //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + [inv setArgument:&path atIndex:2]; + [inv setArgument:&password atIndex:3]; + + // Schedule import for next iteration of event loop in order to let us start the loading anim + [inv performSelector:@selector(invoke) withObject:nil afterDelay:0]; + + } + break; + default: + NSLog(@"Unrecognized action %d", requestCode); + break; + } } -- (void)showError:(NSString*) error +- (void) importAccountsWithPath:(NSURL*) path andPassword:(NSString*) password { - [progressView setHidden:YES]; - [pathPasswordContainer setHidden:YES]; - [errorContainer setHidden:NO]; - [errorLabel setStringValue:error]; + int result = AccountModel::instance().importAccounts(path.path.UTF8String, password.UTF8String); + switch (result) { + case 0: + if (delegateRespondsTo.didCompleteImport) + [((id<PathPasswordDelegate>)self.delegate) didCompleteImport]; + [self close]; + break; + default: + { + [errorField setStringValue:NSLocalizedString(@"An error occured during the import", @"Error shown to the user" )]; + [self showError]; + } + break; + } } @end diff --git a/ui/Base.lproj/PathPasswordWindow.xib b/ui/Base.lproj/PathPasswordWindow.xib index e7aa97119b6e6646db1b14a558e8d60f295b3e1b..4fd608aeb649d261e0e9eb6718a80bb9b1ac770d 100644 --- a/ui/Base.lproj/PathPasswordWindow.xib +++ b/ui/Base.lproj/PathPasswordWindow.xib @@ -1,17 +1,20 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <dependencies> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> + <capability name="Aspect ratio constraints" minToolsVersion="5.1"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="PathPasswordWC"> <connections> <outlet property="errorContainer" destination="ty1-sj-tT6" id="eEy-Cr-yiw"/> - <outlet property="errorLabel" destination="G1N-th-ZtP" id="PMs-b1-oJo"/> + <outlet property="errorField" destination="G1N-th-ZtP" id="tha-8j-8jR"/> + <outlet property="finalContainer" destination="XMQ-jJ-yqG" id="tI5-N3-LpJ"/> + <outlet property="initialContainer" destination="xUT-yB-g8Q" id="nwe-zs-Gxs"/> <outlet property="passwordField" destination="vej-Z8-dOm" id="Ff0-Rb-Al6"/> <outlet property="path" destination="ww6-ha-GhI" id="gdx-sh-x5J"/> - <outlet property="pathPasswordContainer" destination="xUT-yB-g8Q" id="xyd-vF-fD8"/> - <outlet property="progressView" destination="Ovf-4O-7LZ" id="BgC-sc-6GS"/> + <outlet property="progressContainer" destination="3Jv-gr-8Hf" id="S5k-hc-NeK"/> + <outlet property="progressIndicator" destination="Ovf-4O-7LZ" id="mq3-1b-ts5"/> <outlet property="window" destination="QvC-M9-y7g" id="bos-rN-Jgz"/> </connections> </customObject> @@ -21,13 +24,29 @@ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="196" y="240" width="351" height="131"/> - <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1050"/> + <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> <view key="contentView" id="EiT-Mj-1SZ"> <rect key="frame" x="0.0" y="0.0" width="351" height="131"/> <autoresizingMask key="autoresizingMask"/> <subviews> - <customView hidden="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ovf-4O-7LZ" customClass="ITProgressIndicator"> - <rect key="frame" x="140" y="30" width="70" height="70"/> + <customView translatesAutoresizingMaskIntoConstraints="NO" id="3Jv-gr-8Hf"> + <rect key="frame" x="15" y="15" width="321" height="106"/> + <subviews> + <customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ovf-4O-7LZ" customClass="ITProgressIndicator"> + <rect key="frame" x="125" y="18" width="70" height="70"/> + <constraints> + <constraint firstAttribute="width" secondItem="Ovf-4O-7LZ" secondAttribute="height" multiplier="1:1" id="NK5-73-b6c"/> + </constraints> + </customView> + </subviews> + <constraints> + <constraint firstItem="Ovf-4O-7LZ" firstAttribute="centerX" secondItem="3Jv-gr-8Hf" secondAttribute="centerX" id="Zyc-7w-gXf"/> + <constraint firstItem="Ovf-4O-7LZ" firstAttribute="centerY" secondItem="3Jv-gr-8Hf" secondAttribute="centerY" id="dyd-7a-HdM"/> + <constraint firstItem="Ovf-4O-7LZ" firstAttribute="top" secondItem="3Jv-gr-8Hf" secondAttribute="top" constant="18" id="lKK-Xi-PVF"/> + </constraints> + </customView> + <customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XMQ-jJ-yqG"> + <rect key="frame" x="15" y="12" width="321" height="106"/> </customView> <customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xUT-yB-g8Q"> <rect key="frame" x="1" y="0.0" width="350" height="131"/> @@ -134,9 +153,16 @@ DQ </subviews> </customView> </subviews> + <constraints> + <constraint firstAttribute="trailing" secondItem="3Jv-gr-8Hf" secondAttribute="trailing" constant="15" id="JJw-qT-SLD"/> + <constraint firstItem="3Jv-gr-8Hf" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="15" id="LCu-zR-e4t"/> + <constraint firstItem="3Jv-gr-8Hf" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="10" id="UYR-BR-G4e"/> + <constraint firstAttribute="bottom" secondItem="3Jv-gr-8Hf" secondAttribute="bottom" constant="15" id="jr4-Qd-mQ6"/> + </constraints> </view> <connections> <outlet property="delegate" destination="-2" id="3wn-SC-48D"/> + <outlet property="initialFirstResponder" destination="xUT-yB-g8Q" id="WGS-1O-JBF"/> </connections> <point key="canvasLocation" x="-363.5" y="-97.5"/> </window>