├── LICENCE.txt ├── M3Accessibility ├── M3AccessibilityController.h ├── M3AccessibilityController.m ├── M3AccessibleUIElement.h └── M3AccessibleUIElement.m ├── M3BetaController ├── M3BetaController.h └── M3BetaController.m ├── M3DiscreteFiniteAutomata ├── M3DFA.h └── M3DFA.m ├── M3InstallController ├── M3InstallController.h └── M3InstallController.m ├── M3SplitView ├── M3SplitView.h ├── M3SplitView.m └── M3SplitViewKnob.png ├── Minim UI Components ├── M3AudioLevelBar.h ├── M3AudioLevelBar.m ├── M3DigitalClockView.h ├── M3DigitalClockView.m ├── M3GlossyBar.h ├── M3GlossyBar.m ├── M3GlossyBarDelegate.h ├── M3InfoPanel.h ├── M3InfoPanel.m ├── M3InfoPanelDelegate.h ├── M3InfoPanelViewController.h ├── M3InfoPanelViewController.m ├── M3PittedRubberView.h ├── M3PittedRubberView.m ├── M3SegmentedTabsCell.h └── M3SegmentedTabsCell.m ├── README.md ├── Table & OutlineView enhancements ├── M3OutlineView.h ├── M3OutlineView.m ├── M3OutlineViewDataSource.h ├── M3OutlineViewDelegate.h ├── M3SectionedTableView.h ├── M3SectionedTableView.m ├── M3TableView.h ├── M3TableView.m ├── M3TableViewDataSource.h └── M3TableViewDelegate.h ├── TextViewListExample ├── README.md ├── TextViewListExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── TextViewListExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── MainMenu.xib │ ├── Info.plist │ ├── ListCalculator.swift │ ├── ListController.swift │ ├── NSTextView+M3Extensions.swift │ └── TextViewListExample.entitlements └── Token Cloud ├── M3TokenCloud.h ├── M3TokenCloud.m ├── M3TokenController.h ├── M3TokenController.m └── M3TokenControllerDelegate.h /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2021 by M Cubed Software Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /M3Accessibility/M3AccessibilityController.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AccessibilityController.h 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 03/11/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import 11 | 12 | 13 | extern NSString *M3AccessibilityErrorDomain; 14 | 15 | @class M3AccessibleUIElement; 16 | 17 | /*************************** 18 | A central controller for the accessibility APIs 19 | @since M3Foundation 1.0 and later 20 | ***************************/ 21 | @interface M3AccessibilityController : NSObject 22 | 23 | /*************************** 24 | Checks if the accessibility APIs are enabled. 25 | @result Returns YES if accessibility is enabled, otherwise NO 26 | @since M3Foundation 1.0 and later 27 | ***************************/ 28 | @property (readonly, getter = isAccessibilityEnabled) BOOL accessibilityEnabled; 29 | 30 | /*************************** 31 | Returns an accessibility element representing the current frontmost application. 32 | @result Returns an accessibility element for the active application 33 | @since M3Foundation 1.0 and later 34 | ***************************/ 35 | - (M3AccessibleUIElement *)elementForActiveApplication; 36 | 37 | /*************************** 38 | Returns an accessibility element representing the application with the supplied process id 39 | @param aProcessID The process ID of the application 40 | @result The accessibility element for the application with the supplied process id 41 | @since M3Foundation 1.0 or later 42 | **************************/ 43 | - (M3AccessibleUIElement *)elementForApplicationWithPid:(pid_t)aProcessID; 44 | 45 | /*************************** 46 | Returns the system wide element, which represents no single application. 47 | @result Returns the system wide element 48 | @since M3Foundation 1.0 and later 49 | ***************************/ 50 | @property (readonly, strong) M3AccessibleUIElement *systemWideElement; 51 | 52 | /*************************** 53 | Finds the element at the supplied point. 54 | @param point The point at which to look for the UI element in screen co-ordinates 55 | @param error A pointer to an NSError 56 | @result The UI element at the supplied point, if one exists 57 | @since M3Foundation 1.0 and later 58 | ***************************/ 59 | - (M3AccessibleUIElement *)elementAtPosition:(NSPoint)point error:(NSError **)error; 60 | 61 | /*************************** 62 | Returns the NSError object for the supplied error code 63 | @param code The error code to create the error for 64 | @result The NSError object for the supplied code 65 | @since M3Foundation 1.0 and later 66 | ***************************/ 67 | - (NSError *)errorForCode:(NSInteger)code; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /M3Accessibility/M3AccessibilityController.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AccessibilityController.m 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 03/11/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import "M3AccessibilityController.h" 11 | #import "M3AccessibleUIElement.h" 12 | 13 | NSString *M3AccessibilityErrorDomain = @"com.mcubedsw.M3Foundation.accessibility"; 14 | 15 | @implementation M3AccessibilityController 16 | 17 | @synthesize systemWideElement; 18 | 19 | //*****// 20 | - (BOOL)isAccessibilityEnabled { 21 | return (BOOL)AXAPIEnabled(); 22 | } 23 | 24 | //*****// 25 | - (M3AccessibleUIElement *)elementForActiveApplication { 26 | pid_t processid = (pid_t)[[[[NSWorkspace sharedWorkspace] activeApplication] objectForKey:@"NSApplicationProcessIdentifier"] integerValue]; 27 | return [[M3AccessibleUIElement alloc] initWithElement:AXUIElementCreateApplication(processid) accessibilityController:self]; 28 | } 29 | 30 | //*****// 31 | - (M3AccessibleUIElement *)elementForApplicationWithPid:(pid_t)processid { 32 | return [[M3AccessibleUIElement alloc] initWithElement:AXUIElementCreateApplication(processid) accessibilityController:self]; 33 | } 34 | 35 | //*****// 36 | - (M3AccessibleUIElement *)systemWideElement { 37 | if (!systemWideElement) { 38 | systemWideElement = [[M3AccessibleUIElement alloc] initWithElement:AXUIElementCreateSystemWide() accessibilityController:self]; 39 | } 40 | return systemWideElement; 41 | } 42 | 43 | //*****// 44 | - (M3AccessibleUIElement *)elementAtPosition:(NSPoint)point error:(NSError **)error { 45 | AXUIElementRef element = NULL; 46 | AXError errorCode = AXUIElementCopyElementAtPosition([[self systemWideElement] element], point.x, point.y, &element); 47 | 48 | if (error != NULL && errorCode != 0) { 49 | *error = [self errorForCode:errorCode]; 50 | } 51 | return [[M3AccessibleUIElement alloc] initWithElement:element accessibilityController:self]; 52 | } 53 | 54 | //*****// 55 | - (NSError *)errorForCode:(NSInteger)code { 56 | NSString *localisedDescription = @""; 57 | if (code == kAXErrorFailure) { 58 | localisedDescription = NSLocalizedString(@"A system error occured.", @""); 59 | } else if (code == kAXErrorIllegalArgument) { 60 | localisedDescription = NSLocalizedString(@"An illegal argument was passed to the function.", @""); 61 | } else if (code == kAXErrorInvalidUIElement) { 62 | localisedDescription = NSLocalizedString(@"The UI element passed to the function was invalid.", @""); 63 | } else if (code == kAXErrorInvalidUIElementObserver) { 64 | localisedDescription = NSLocalizedString(@"The observer passed to the function was invalid", @""); 65 | } else if (code == kAXErrorCannotComplete) { 66 | localisedDescription = NSLocalizedString(@"Could not complete the operation. The application being communicated with may be busy or unresponsive.", @""); 67 | } else if (code == kAXErrorAttributeUnsupported) { 68 | localisedDescription = NSLocalizedString(@"The supplied attribute is not supported by this UI element.", @""); 69 | } else if (code == kAXErrorActionUnsupported) { 70 | localisedDescription = NSLocalizedString(@"The supplied action is not supported by this UI element.", @""); 71 | } else if (code == kAXErrorNotificationUnsupported) { 72 | localisedDescription = NSLocalizedString(@"The supplied notification is not supported by this UI element", @""); 73 | } else if (code == kAXErrorNotImplemented) { 74 | localisedDescription = NSLocalizedString(@"The targeted application does not implement the correct accessibility API methods", @""); 75 | } else if (code == kAXErrorNotificationAlreadyRegistered) { 76 | localisedDescription = NSLocalizedString(@"The supplied notification has already been registered", @""); 77 | } else if (code == kAXErrorNotificationNotRegistered) { 78 | localisedDescription = NSLocalizedString(@"The notification is not yet registered", @""); 79 | } else if (code == kAXErrorAPIDisabled) { 80 | localisedDescription = NSLocalizedString(@"The accessibility API is not enabled", @""); 81 | } else if (code == kAXErrorNoValue) { 82 | localisedDescription = NSLocalizedString(@"The requested value does not exist", @""); 83 | } else if (code == kAXErrorParameterizedAttributeUnsupported) { 84 | localisedDescription = NSLocalizedString(@"The supplied parameterised attribute is not supported by this UI element", @""); 85 | } else if (code == kAXErrorNotEnoughPrecision) { 86 | localisedDescription = NSLocalizedString(@"Undocumented Error: Not Enough Precision", @""); 87 | } else { 88 | return nil; 89 | } 90 | 91 | NSError *error = [NSError errorWithDomain:M3AccessibilityErrorDomain 92 | code:code 93 | userInfo:[NSDictionary dictionaryWithObject:localisedDescription forKey:NSLocalizedDescriptionKey]]; 94 | 95 | return error; 96 | } 97 | 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /M3Accessibility/M3AccessibleUIElement.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AccessibleUIElement.h 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 03/11/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import 11 | 12 | @class M3AccessibilityController; 13 | 14 | /*************************** 15 | Represents an AXUIElementRef, providing a more Cocoa like interface to it. 16 | @since M3Foundation 1.0 and later 17 | ***************************/ 18 | @interface M3AccessibleUIElement : NSObject 19 | 20 | /*************************** 21 | @property element 22 | Returns the AXUIElement represented by the object 23 | @since M3Foundation 1.0 and later 24 | ***************************/ 25 | @property (readonly) AXUIElementRef element; 26 | 27 | @property (readonly, weak) M3AccessibilityController *accessibilityController; 28 | 29 | /*************************** 30 | Creates a new object with the supplied element 31 | @discussion The element is retained using CFRetain 32 | @param newElement The element to initialise the object with 33 | @result The initialised object 34 | @since M3Foundation 1.0 and later 35 | ***************************/ 36 | - (id)initWithElement:(AXUIElementRef)newElement accessibilityController:(M3AccessibilityController *)aController; 37 | 38 | /*************************** 39 | Returns the description for the supplied action 40 | @param action The action to get the description for 41 | @param error A pointer to an NSError object 42 | @result The action description 43 | @since M3Foundation 1.0 and later 44 | ***************************/ 45 | - (NSString *)descriptionForAction:(NSString *)action error:(NSError **)error; 46 | 47 | /*************************** 48 | Returns the names of the actions on the item 49 | @param error A pointer to an NSError object 50 | @result An array of action names 51 | @since M3Foundation 1.0 and later 52 | ***************************/ 53 | - (NSArray *)actionNamesAndError:(NSError **)error; 54 | 55 | /*************************** 56 | Returns the names of the attributes on the item 57 | @param error A pointer to an NSError object 58 | @result An array of attribute names 59 | @since M3Foundation 1.0 and later 60 | ***************************/ 61 | - (NSArray *)attributeNamesAndError:(NSError **)error; 62 | 63 | /*************************** 64 | Returns the value for the attribute 65 | @param attribute The attribute to get the value of 66 | @param error A pointer to an NSError object 67 | @result The value of the supplied attribute 68 | @since M3Foundation 1.0 and later 69 | ***************************/ 70 | - (id)valueForAttribute:(NSString *)attribute error:(NSError **)error; 71 | 72 | /*************************** 73 | Returns the values for the supplied attribute 74 | @param attribute The attribute to get 75 | @param range The range of values to return 76 | @param error A pointer to an NSError object 77 | @result The values for the attribute 78 | @since M3Foundation 1.0 and later 79 | ***************************/ 80 | - (id)valuesForAttribute:(NSString *)attribute inRange:(NSRange)range error:(NSError **)error; 81 | 82 | /*************************** 83 | Checks if the supplied attribute is settable 84 | @param attribute The attribute to check 85 | @param error A pointer to an NSError object 86 | @result YES if the attribute is settable, otherwise NO 87 | @since M3Foundation 1.0 and later 88 | ***************************/ 89 | - (BOOL)isAttributeSettable:(NSString *)attribute error:(NSError **)error; 90 | 91 | /*************************** 92 | Performs the supplied action 93 | @param action The action to perform 94 | @param error A pointer to an NSError object 95 | @since M3Foundation 1.0 and later 96 | ***************************/ 97 | - (BOOL)performAction:(NSString *)action error:(NSError **)error; 98 | 99 | /*************************** 100 | Posts a keyboard character 101 | @param keyChar The key character to post 102 | @param virtualKey The virtual key to post 103 | @param keyDown The key to press 104 | @param error A pointer to an NSError object 105 | @since M3Foundation 1.0 and later 106 | ***************************/ 107 | - (BOOL)postKeyboardEventWithKeyCharacter:(CGCharCode)keyChar virtualKey:(CGKeyCode)virtualKey keyDown:(BOOL)keyDown error:(NSError **)error; 108 | 109 | /*************************** 110 | Sets the value of the supplied attribute 111 | @param value The new value 112 | @param attribute The attribute to set 113 | @param error A pointer to an NSError object 114 | @since M3Foundation 1.0 and later 115 | ***************************/ 116 | - (BOOL)setValue:(id)value forAttribute:(NSString *)attribute error:(NSError **)error; 117 | 118 | /*************************** 119 | Returns the process ID for the represented element 120 | @param error A pointer to an NSError object 121 | @result The process ID for the represented element 122 | @since M3Foundation 1.0 and later 123 | ***************************/ 124 | - (pid_t)processIDAndError:(NSError **)error; 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /M3Accessibility/M3AccessibleUIElement.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AccessibleUIElement.m 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 03/11/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import "M3AccessibleUIElement.h" 11 | #import "M3AccessibilityController.h" 12 | 13 | @interface M3AccessibleUIElement () 14 | 15 | - (id)sanitiseValue:(id)value; 16 | 17 | @end 18 | 19 | 20 | @implementation M3AccessibleUIElement 21 | 22 | @synthesize element, accessibilityController; 23 | 24 | //*****// 25 | - (id)copyWithZone:(NSZone *)zone { 26 | return self; 27 | } 28 | 29 | //*****// 30 | - (id)initWithElement:(AXUIElementRef)newElement accessibilityController:(M3AccessibilityController *)aController { 31 | if ((self = [super init])) { 32 | element = CFRetain(newElement); 33 | accessibilityController = aController; 34 | } 35 | return self; 36 | } 37 | 38 | //*****// 39 | - (void)dealloc { 40 | if (element) CFRelease(element); 41 | } 42 | 43 | //*****// 44 | - (void)finalize { 45 | if (element) CFRelease(element); 46 | [super finalize]; 47 | } 48 | 49 | //*****// 50 | - (NSString *)descriptionForAction:(NSString *)action error:(NSError **)error { 51 | CFStringRef description = nil; 52 | AXError errorCode = AXUIElementCopyActionDescription (element, (__bridge_retained CFStringRef)action, &description); 53 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 54 | if (error != NULL && returnError) 55 | *error = returnError; 56 | return returnError != nil ? (__bridge_transfer NSString *)description : nil; 57 | } 58 | 59 | //*****// 60 | - (NSArray *)actionNamesAndError:(NSError **)error { 61 | CFArrayRef names = nil; 62 | AXError errorCode = AXUIElementCopyActionNames(element, &names); 63 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 64 | if (error != NULL && returnError) 65 | *error = returnError; 66 | return (__bridge_transfer NSArray *)names; 67 | } 68 | 69 | //*****// 70 | - (NSArray *)attributeNamesAndError:(NSError **)error { 71 | CFArrayRef names = nil; 72 | AXError errorCode = AXUIElementCopyAttributeNames(element, &names); 73 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 74 | if (error != NULL && returnError) 75 | *error = returnError; 76 | return (__bridge_transfer NSArray *)names; 77 | } 78 | 79 | //*****// 80 | - (id)valueForAttribute:(NSString *)attribute error:(NSError **)error { 81 | CFTypeRef value = nil; 82 | if ([[self attributeNamesAndError:NULL] containsObject:attribute]) { 83 | AXError errorCode = AXUIElementCopyAttributeValue(element, (__bridge_retained CFStringRef)attribute, &value); 84 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 85 | if (error != NULL && returnError) 86 | *error = returnError; 87 | } 88 | 89 | return [self sanitiseValue:(__bridge_transfer id)value]; 90 | } 91 | 92 | //*****// 93 | - (id)valuesForAttribute:(NSString *)attribute inRange:(NSRange)range error:(NSError **)error { 94 | CFArrayRef values = nil; 95 | AXError errorCode = AXUIElementCopyAttributeValues(element, (__bridge_retained CFStringRef)attribute, range.location, range.length, &values); 96 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 97 | if (error != NULL && returnError) 98 | *error = returnError; 99 | return [self sanitiseValue:(__bridge_transfer id)values]; 100 | } 101 | 102 | //*****// 103 | - (BOOL)isAttributeSettable:(NSString *)attribute error:(NSError **)error { 104 | Boolean settable; 105 | AXError errorCode = AXUIElementIsAttributeSettable(element, (__bridge_retained CFStringRef)attribute, &settable); 106 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 107 | if (error != NULL && returnError) 108 | *error = returnError; 109 | return (BOOL)settable; 110 | } 111 | 112 | //*****// 113 | - (BOOL)performAction:(NSString *)action error:(NSError **)error { 114 | AXError errorCode = AXUIElementPerformAction(element, (__bridge_retained CFStringRef)action); 115 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 116 | if (error != NULL && returnError) { 117 | *error = returnError; 118 | return NO; 119 | } 120 | return YES; 121 | } 122 | 123 | //*****// 124 | - (BOOL)postKeyboardEventWithKeyCharacter:(CGCharCode)keyChar virtualKey:(CGKeyCode)virtualKey keyDown:(BOOL)keyDown error:(NSError **)error { 125 | AXError errorCode = AXUIElementPostKeyboardEvent(element, keyChar, virtualKey, (Boolean)keyDown); 126 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 127 | if (error != NULL && returnError) { 128 | *error = returnError; 129 | return NO; 130 | } 131 | return YES; 132 | } 133 | 134 | //*****// 135 | - (BOOL)setValue:(id)value forAttribute:(NSString *)attribute error:(NSError **)error { 136 | AXError errorCode = AXUIElementSetAttributeValue(element, (__bridge_retained CFStringRef)attribute, (__bridge_retained CFTypeRef)value); 137 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 138 | if (error != NULL && returnError) { 139 | *error = returnError; 140 | return NO; 141 | } 142 | return YES; 143 | } 144 | 145 | //*****// 146 | - (id)sanitiseValue:(id)value { 147 | if (!value) 148 | return nil; 149 | AXValueRef valueRef = (__bridge_retained AXValueRef)value; 150 | //If we have a valid type convert to an NSPoint 151 | if (AXValueGetType(valueRef) != kAXValueIllegalType) { 152 | CGPoint rawValue; 153 | AXValueGetValue(valueRef, AXValueGetType(valueRef), &rawValue); 154 | switch (AXValueGetType(valueRef)) { 155 | case kAXValueCGPointType: { 156 | CGPoint rawValue; 157 | AXValueGetValue(valueRef, kAXValueCGPointType, &rawValue); 158 | return [NSValue valueWithPoint:rawValue]; 159 | } 160 | case kAXValueCGSizeType: { 161 | CGSize rawValue; 162 | AXValueGetValue(valueRef, kAXValueCGSizeType, &rawValue); 163 | return [NSValue valueWithSize:rawValue]; 164 | } 165 | case kAXValueCGRectType: { 166 | CGRect rawValue; 167 | AXValueGetValue(valueRef, kAXValueCGRectType, &rawValue); 168 | return [NSValue valueWithRect:rawValue]; 169 | } 170 | case kAXValueCFRangeType: { 171 | NSRange rawValue; 172 | AXValueGetValue(valueRef, kAXValueCFRangeType, &rawValue); 173 | return [NSValue valueWithRange:rawValue]; 174 | } 175 | default: {} 176 | } 177 | } 178 | 179 | //If we get back an array then create an array of values 180 | if ([value isKindOfClass:[NSArray class]]) { 181 | NSMutableArray *array = [NSMutableArray array]; 182 | for (id subvalue in value) { 183 | [array addObject:[self sanitiseValue:subvalue]]; 184 | } 185 | return [[array copy] autorelease]; 186 | //And if we have a UI element, create a new element 187 | } else if ([[value description] rangeOfString:@"AXUIElement"].location != NSNotFound) { 188 | return [[M3AccessibleUIElement alloc] initWithElement:(__bridge_retained AXUIElementRef)value 189 | accessibilityController:self.accessibilityController]; 190 | } 191 | return value; 192 | } 193 | 194 | //*****// 195 | - (NSString *)description { 196 | NSString *role = [self valueForAttribute:@"AXRole" error:nil]; 197 | id value = [self valueForAttribute:@"AXValue" error:nil]; 198 | if (value) { 199 | return [NSString stringWithFormat:@"%@ (%@)", role, value]; 200 | } 201 | return role; 202 | } 203 | 204 | //*****// 205 | - (pid_t)processIDAndError:(NSError **)error { 206 | pid_t pid = 0; 207 | AXError errorCode = AXUIElementGetPid(element, &pid); 208 | NSError *returnError = [self.accessibilityController errorForCode:(NSInteger)errorCode]; 209 | if (error != NULL && returnError) 210 | *error = returnError; 211 | 212 | return pid; 213 | } 214 | 215 | //*****// 216 | - (BOOL)isEqual:(id)object { 217 | if (![[self valueForAttribute:(NSString *)kAXRoleAttribute error:NULL] isEqualToString:[object valueForAttribute:(NSString *)kAXRoleAttribute error:NULL]]) { 218 | return NO; 219 | } 220 | NSString *subroleSelf = [self valueForAttribute:(NSString *)kAXSubroleAttribute error:NULL]; 221 | NSString *subroleObject = [object valueForAttribute:(NSString *)kAXSubroleAttribute error:NULL]; 222 | if (![subroleSelf isEqualToString:subroleObject] && subroleSelf && subroleObject) { 223 | return NO; 224 | } 225 | 226 | NSString *titleSelf = [self valueForAttribute:(NSString *)kAXTitleAttribute error:NULL]; 227 | NSString *titleObject = [object valueForAttribute:(NSString *)kAXTitleAttribute error:NULL]; 228 | if (![titleSelf isEqualToString:titleObject] && titleSelf && titleObject) { 229 | return NO; 230 | } 231 | 232 | id valueSelf = [self valueForAttribute:(NSString *)kAXValueAttribute error:NULL]; 233 | id valueObject = [object valueForAttribute:(NSString *)kAXValueAttribute error:NULL]; 234 | if (![valueSelf isEqual:valueObject] && valueSelf && valueObject) { 235 | return NO; 236 | } 237 | 238 | if (!NSEqualPoints([[self valueForAttribute:(NSString *)kAXPositionAttribute error:NULL] pointValue], [[object valueForAttribute:(NSString *)kAXPositionAttribute error:NULL] pointValue])) { 239 | return NO; 240 | } 241 | return YES; 242 | } 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /M3BetaController/M3BetaController.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3BetaController.h 3 | M3AppKit 4 | 5 | Created by Martin Pilkington on 24/10/2008. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | 11 | /** 12 | A class for handling beta versions and encouraging users to upgrade. The class integrates with Sparkle to show any updates if the beta expired 13 | 14 | Using M3BetaController 15 | =================== 16 | M3BetaController only performs the checks if the CFBundleShortVersionString propery in your Info.plist is set to a value containing a 'b', 17 | for example 1.2b3. 18 | Once you have added this to your Info.plist then M3BetaController can be used as below: 19 | ``` 20 | M3BetaController *controller = [M3BetaController new]; 21 | [controller performBetaCheckWithDataString:[NSString stringWithUTF8String:__DATE__]]; 22 | ```` 23 | This has the compiler insert the date the file was compiled. However, you can pass in any date you wish as long as it is in the same format. 24 | 25 | @since PROJECT_NAME VERSION_NAME or later 26 | */ 27 | @interface M3BetaController : NSObject 28 | 29 | /** 30 | Returns whether the beta period has expired 31 | @since PROJECT_NAME VERSION_NAME or later 32 | */ 33 | @property (readonly) BOOL betaExpired; 34 | 35 | /** 36 | The length of the beta period (by default it is set to 21 days) 37 | @since PROJECT_NAME VERSION_NAME or later 38 | */ 39 | @property (assign) NSUInteger betaLength; 40 | 41 | 42 | /** 43 | Checks if beta check needs to be performed 44 | @param aDateString The date string to match against, in MMM dd yyyy format 45 | @since PROJECT_NAME VERSION_NAME or later 46 | */ 47 | - (void)performBetaCheckWithDateString:(NSString *)aDateString; 48 | 49 | @end -------------------------------------------------------------------------------- /M3BetaController/M3BetaController.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3BetaController.m 3 | M3AppKit 4 | 5 | Created by Martin Pilkington on 24/10/2008. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import "M3BetaController.h" 11 | 12 | typedef enum { 13 | M3BetaExpirationQuit, 14 | M3BetaExpirationCheckForUpdates 15 | } M3BetaExpirationResult; 16 | 17 | @interface M3BetaController () 18 | 19 | - (NSDate *)p_expirationDateFromDateString:(NSString *)aDateString; 20 | - (M3BetaExpirationResult)p_runExpirationSheet; 21 | - (void)p_performUpdate; 22 | 23 | @end 24 | 25 | 26 | 27 | @implementation M3BetaController 28 | 29 | //*****// 30 | - (id)init { 31 | if ((self = [super init])) { 32 | NSString *notificationName = [NSString stringWithFormat:@"SU%@DriverFinished", @"Update"]; 33 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updaterFinished:) name:notificationName object:nil]; 34 | _betaLength = 21; 35 | } 36 | return self; 37 | } 38 | 39 | //*****// 40 | - (id)initWithCoder:(NSCoder *)aDecoder { 41 | if ((self = [super init])) { 42 | _betaLength = [aDecoder decodeIntegerForKey:@"betaLength"]; 43 | } 44 | return self; 45 | } 46 | 47 | //*****// 48 | - (void)encodeWithCoder:(NSCoder *)aCoder { 49 | [aCoder encodeInteger:_betaLength forKey:@"betaLength"]; 50 | } 51 | 52 | 53 | 54 | 55 | 56 | #pragma mark - 57 | #pragma mark Perform the beta checks 58 | 59 | //*****// 60 | - (void)performBetaCheckWithDateString:(NSString *)aDateString { 61 | NSString *appVersion = [NSBundle mainBundle].infoDictionary[@"CFBundleShortVersionString"]; 62 | 63 | //If we don't have a b in the version name then we don't bother checking 64 | if ([appVersion rangeOfString:@"b"].location == NSNotFound) return; 65 | 66 | NSDate *expirationDate = [self p_expirationDateFromDateString:aDateString]; 67 | if ([expirationDate laterDate:[NSDate date]] == expirationDate) return; 68 | 69 | _betaExpired = YES; 70 | 71 | if ([self p_runExpirationSheet] == M3BetaExpirationQuit) { 72 | [NSApp terminate:self]; 73 | } else { 74 | [self p_performUpdate]; 75 | } 76 | } 77 | 78 | //*****// 79 | - (NSDate *)p_expirationDateFromDateString:(NSString *)aDateString { 80 | NSString *nowString = [aDateString stringByReplacingOccurrencesOfString:@" " withString:@" "]; 81 | 82 | NSDateFormatter *formatter = [NSDateFormatter new]; 83 | [formatter setDateFormat:@"MMM dd yyyy"]; 84 | 85 | NSDate *nowDate = [formatter dateFromString:nowString]; 86 | return [nowDate dateByAddingTimeInterval:(60 * 60 * 24 * self.betaLength)]; 87 | } 88 | 89 | //*****// 90 | - (M3BetaExpirationResult)p_runExpirationSheet { 91 | //Get the sparkle updater class 92 | NSString *classNameStart = @"SUUp"; 93 | Class updaterClass = NSClassFromString([classNameStart stringByAppendingString:@"dater"]); 94 | 95 | NSString *alertQuit = NSLocalizedString(@"Quit", @"Quit"); 96 | NSString *alertCheck = NSLocalizedString(@"Check For Updates", @"Beta expiration update check prompt"); 97 | NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"This Beta Has Expired", @"Beta expiration title") 98 | defaultButton:updaterClass ? alertCheck : alertQuit 99 | alternateButton:updaterClass ? alertQuit : nil 100 | otherButton:nil 101 | informativeTextWithFormat:NSLocalizedString(@"Please download a new version to keep using %@.", @"Beta expiration message"), [NSProcessInfo processInfo].processName]; 102 | 103 | 104 | if([alert runModal] == NSOKButton) { 105 | return updaterClass ? M3BetaExpirationCheckForUpdates : M3BetaExpirationQuit; 106 | } 107 | return M3BetaExpirationQuit; 108 | } 109 | 110 | //*****// 111 | - (void)p_performUpdate { 112 | NSString *classNameStart = @"SUUp"; 113 | Class updaterClass = NSClassFromString([classNameStart stringByAppendingString:@"dater"]); 114 | id updater = [updaterClass performSelector:@selector(sharedUpdater)]; 115 | [updater performSelector:@selector(checkForUpdates:) withObject:self]; 116 | } 117 | 118 | //*****// 119 | - (void)updaterFinished:(NSNotification *)note { 120 | if (_betaExpired) { 121 | [NSApp terminate:self]; 122 | } 123 | } 124 | 125 | @end -------------------------------------------------------------------------------- /M3DiscreteFiniteAutomata/M3DFA.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3DFA.h 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 17/12/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import 11 | 12 | #if NS_BLOCKS_AVAILABLE 13 | 14 | /*************************** 15 | A deterministic finite automaton class. The automaton is defined using the format described below and passed into the initialise to create an automaton. 16 | This can then be used to parse an input string to see if the string is valid and retreive any output from it. While this may seem a trivial and academic class, it is highly 17 | useful for creating more complex regular expressions, as an automaton can be much easier to visualise and debug. 18 | 19 | __This class requires Mac OS X 10.6 or later__ 20 | 21 | **Defining an automaton** 22 | 23 | An automaton is defined as a series of states, each states being contained on a single line and given an integer value. Each state contains a series of transitions, 24 | consiting of an input value and the state to move to upon receiving that value. There is also a special state, END, which signifies that this is a valid end state. If 25 | your automaton parses a string and does not finish in a state with an END transition, the parsing of the string will have failed. 26 | 27 | Transitions are defined in the form: 28 | 29 | input > x 30 | 31 | Where input is a character, a character set or a wildcard (.) and x is the number of the state to transition to. A transition can also result in the output of the input charcter 32 | (useful for capturing text). Such a transition is defined with a double > as so: 33 | 34 | input >> x 35 | 36 | Single character input is defined by adding the character inside double quotes, eg "a", "%", "9". 37 | 38 | Character sets are defined by putting characters inside square brackets. For example, [mac42] would only match the characters a, c, m, 2 and 4. There are also 4 special 39 | character sets: 40 | 41 | \w - All whitespace character sets 42 | a-z - all lower case latin characters 43 | A-Z - all upper case latin characters 44 | 0-9 - all digits 45 | 46 | These can be grouped together. For example, a character set for hexadecimal characters and whitespace could be defined as [0-9abcdef\w] 47 | 48 | The last item is a wildcard character, which is signified by a dot . and allows any character. 49 | 50 | 51 | States are defined as the state number followed by a : which is then followed by a comma separated list of transitions. 52 | 53 | 54 | And example automaton is shown below. It looks for a URL begining with www. and ending with a TLD and extracts the site name, eg www.apple.com would return 55 | apple: 56 | 57 | 0: "w" > 1 58 | 1: "w" > 2 59 | 2: "w" > 3 60 | 3: "." > 4 61 | 4: "." > 5, . >> 4 62 | 5: . > 5, END 63 | 64 | Notice how in state 4, the transition when a . character is encontered is before the wildcard transition. Transitions are checked in the order in which they are written, 65 | so a wild card transition should come after any other transitions, or they won't be checked. 66 | 67 | @since M3Foundation 1.0 and later 68 | ***************************/ 69 | @interface M3DFA : NSObject { 70 | NSDictionary *automata; 71 | NSMutableArray *endStates; 72 | NSInteger initialState; 73 | } 74 | 75 | /*************************** 76 | Creates an automaton from a supplied string, returning an error if it fails 77 | @param aut The automaton string 78 | @param error A pointer to an NSError object 79 | @result The created automaton, of nil if invalid 80 | @since M3Foundation 1.0 and later 81 | ***************************/ 82 | - (id)initWithAutomaton:(NSString *)aut error:(NSError **)error; 83 | 84 | /*************************** 85 | Parses the supplied string, checking its validity and returning output against the automaton 86 | Output is collected upon first encountering an output transition up until it encounters a non-output transition. It then invokes the block passing in the 87 | output and the state it is moving to. Note that output is returned while parsing is continuing, but the validity of the full string is not known until it has been parsed in 88 | its entirerity. As such, if you only want to accept output of a fully valid string then you should store the output in a temporary location until the parsing is complete. 89 | 90 | @param str The string to parse 91 | @param block A block to handle any output 92 | @result YES if the string is valid, otherwise NO 93 | @since M3Foundation 1.0 and later 94 | ***************************/ 95 | - (BOOL)parseString:(NSString *)str outputBlock:(void (^)(NSString *output, NSInteger state))block; 96 | 97 | 98 | @end 99 | 100 | #endif -------------------------------------------------------------------------------- /M3DiscreteFiniteAutomata/M3DFA.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3DFA.m 3 | M3Foundation 4 | 5 | Created by Martin Pilkington on 17/12/2009. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import "M3DFA.h" 11 | 12 | #if NS_BLOCKS_AVAILABLE 13 | 14 | /** NB: Yes this is kinda ugly internally. I'll clean it up after I've written tests **/ 15 | 16 | @interface M3DFA () 17 | 18 | - (NSDictionary *)parseAutomata:(NSString *)aut error:(NSError **)error; 19 | - (id)parseRule:(NSString *)rule; 20 | 21 | @end 22 | 23 | 24 | @implementation M3DFA 25 | 26 | //*****// 27 | - (id)initWithAutomaton:(NSString *)aut error:(NSError **)error { 28 | if ((self = [super init])) { 29 | initialState = NSNotFound; 30 | endStates = [[NSMutableArray alloc] init]; 31 | automata = [[self parseAutomata:aut error:&*error] retain]; 32 | } 33 | return self; 34 | } 35 | 36 | //*****// 37 | - (void)dealloc { 38 | [endStates release]; 39 | [automata release]; 40 | [super dealloc]; 41 | } 42 | 43 | //*****// 44 | - (NSDictionary *)parseAutomata:(NSString *)aut error:(NSError **)error { 45 | NSMutableDictionary *parsedAutomata = [NSMutableDictionary dictionary]; 46 | 47 | //Loop trhough our lines 48 | [aut enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { 49 | //Get the state and the transitions 50 | NSInteger split = [line rangeOfString:@":"].location; 51 | NSInteger state = [[line substringToIndex:split] integerValue]; 52 | if (initialState == NSNotFound) { 53 | initialState = state; 54 | } 55 | 56 | NSMutableArray *transitions = [NSMutableArray array]; 57 | NSString *temp = nil; 58 | //Get individual transitions 59 | for (NSString *transitionString in [[line substringFromIndex:split+1] componentsSeparatedByString:@","]) { 60 | while ([transitionString hasPrefix:@" "]) { 61 | transitionString = [transitionString substringFromIndex:1]; 62 | } 63 | if ([transitionString hasSuffix:@"\""]) { 64 | temp = transitionString; 65 | } else if (temp) { 66 | [transitions addObject:[NSString stringWithFormat:@"%@,%@", temp, transitionString]]; 67 | temp = nil; 68 | } else { 69 | [transitions addObject:transitionString]; 70 | } 71 | } 72 | 73 | //Get the rules, states and whether it outputs 74 | NSMutableArray *finishedTransitions = [NSMutableArray array]; 75 | for (NSString *str in transitions) { 76 | str = [str stringByReplacingOccurrencesOfString:@"\">\"" withString:@"{{{{GREATERTHAN}}}}"]; 77 | str = [str stringByReplacingOccurrencesOfString:@"\">>\"" withString:@"{{{{2GREATERTHAN}}}}"]; 78 | str = [str stringByReplacingOccurrencesOfString:@">>" withString:@">OUTPUT"]; 79 | NSArray *components = [str componentsSeparatedByString:@">"]; 80 | 81 | NSString *rule = [components objectAtIndex:0]; 82 | NSString *newState = nil; 83 | 84 | if ([components count] > 1) { 85 | newState = [components objectAtIndex:1]; 86 | } 87 | 88 | rule = [rule stringByReplacingOccurrencesOfString:@"{{{{GREATERTHAN}}}}" withString:@"\">\""]; 89 | rule = [rule stringByReplacingOccurrencesOfString:@"{{{{2GREATERTHAN}}}}" withString:@"\">>\""]; 90 | 91 | id parsedRule = [self parseRule:rule]; 92 | BOOL output = NO; 93 | if ([newState hasPrefix:@"OUTPUT"]) { 94 | output = YES; 95 | newState = [newState substringFromIndex:6]; 96 | } 97 | if (parsedRule) { 98 | [finishedTransitions addObject:[NSDictionary dictionaryWithObjectsAndKeys:parsedRule, @"rule", [NSNumber numberWithBool:output], @"output", [NSNumber numberWithInteger:[newState integerValue]], @"newState", nil]]; 99 | } else { 100 | [endStates addObject:[NSNumber numberWithInteger:state]]; 101 | } 102 | } 103 | [parsedAutomata setObject:finishedTransitions forKey:[NSNumber numberWithInteger:state]]; 104 | }]; 105 | return parsedAutomata; 106 | } 107 | 108 | //*****// 109 | - (id)parseRule:(NSString *)rule { 110 | while ([rule hasPrefix:@" "]) { 111 | rule = [rule substringFromIndex:1]; 112 | } 113 | while ([rule hasSuffix:@" "]) { 114 | rule = [rule substringToIndex:[rule length]-1]; 115 | } 116 | 117 | //And now the end is near… 118 | if ([rule isEqualToString:@"END"]) { 119 | return nil; 120 | //Unquote 121 | } else if ([rule hasPrefix:@"\""] && [rule hasSuffix:@"\""]) { 122 | return [rule substringWithRange:NSMakeRange(1, [rule length] - 2)]; 123 | //Handle character sets 124 | } else if ([rule hasPrefix:@"["] && [rule hasSuffix:@"]"]) { 125 | NSMutableString *str = [NSMutableString stringWithString:rule]; 126 | NSMutableCharacterSet *characterSet = [[NSMutableCharacterSet alloc] init]; 127 | NSRange lowerCase = [str rangeOfString:@"a-z"]; 128 | if (lowerCase.location != NSNotFound) { 129 | [characterSet formUnionWithCharacterSet:[NSCharacterSet lowercaseLetterCharacterSet]]; 130 | [str deleteCharactersInRange:lowerCase]; 131 | } 132 | NSRange upperCase = [str rangeOfString:@"A-Z"]; 133 | if (upperCase.location != NSNotFound) { 134 | [characterSet formUnionWithCharacterSet:[NSCharacterSet uppercaseLetterCharacterSet]]; 135 | [str deleteCharactersInRange:upperCase]; 136 | } 137 | NSRange whitespace = [str rangeOfString:@"\\w"]; 138 | if (whitespace.location != NSNotFound) { 139 | [characterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; 140 | [str deleteCharactersInRange:whitespace]; 141 | } 142 | NSRange numbers = [str rangeOfString:@"0-9"]; 143 | if (numbers.location != NSNotFound) { 144 | [characterSet formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]]; 145 | [str deleteCharactersInRange:numbers]; 146 | } 147 | 148 | [characterSet addCharactersInString:str]; 149 | return [characterSet autorelease]; 150 | 151 | } else if ([rule isEqualToString:@"."]) { 152 | return @""; 153 | } 154 | 155 | return rule; 156 | } 157 | 158 | //*****// 159 | - (BOOL)parseString:(NSString *)str outputBlock:(void (^)(NSString *output, NSInteger state))block { 160 | NSInteger currentState = initialState; 161 | 162 | NSInteger index = 0; 163 | NSMutableString *outputString = [NSMutableString string]; 164 | 165 | //While we still have more characters 166 | while (index < [str length]) { 167 | //Get the next character and the current rules 168 | NSString *character = [str substringWithRange:NSMakeRange(index, 1)]; 169 | NSArray *rules = [automata objectForKey:[NSNumber numberWithInteger:currentState]]; 170 | //Find the matching rule and move to the next state 171 | for (NSDictionary *rule in rules) { 172 | id ruleObject = [rule objectForKey:@"rule"]; 173 | NSInteger newState = [[rule objectForKey:@"newState"] integerValue]; 174 | BOOL output = [[rule objectForKey:@"output"] boolValue]; 175 | if ([ruleObject isKindOfClass:[NSCharacterSet class]]) { 176 | if ([ruleObject characterIsMember:[character characterAtIndex:0]]) { 177 | currentState = newState; 178 | if (output) 179 | [outputString appendString:character]; 180 | else { 181 | if ([outputString length]) { 182 | block(outputString, currentState); 183 | [outputString setString:@""]; 184 | } 185 | } 186 | 187 | break; 188 | } 189 | } else if ([ruleObject isEqualToString:@""]) { 190 | currentState = newState; 191 | if (output) 192 | [outputString appendString:character]; 193 | else { 194 | if ([outputString length]) { 195 | block(outputString, currentState); 196 | [outputString setString:@""]; 197 | } 198 | } 199 | break; 200 | } else if ([ruleObject isEqualToString:character]) { 201 | currentState = newState; 202 | if (output) 203 | [outputString appendString:character]; 204 | else { 205 | if ([outputString length]) { 206 | block(outputString, currentState); 207 | [outputString setString:@""]; 208 | } 209 | } 210 | break; 211 | } 212 | } 213 | index++; 214 | } 215 | //Was the string accepted? 216 | return [endStates containsObject:[NSNumber numberWithInteger:currentState]]; 217 | } 218 | 219 | @end 220 | 221 | #endif -------------------------------------------------------------------------------- /M3InstallController/M3InstallController.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InstallController.h 3 | M3AppKit 4 | 5 | Created by Martin Pilkington on 02/06/2007. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | 11 | /** 12 | BRIEF_HERE 13 | @since PROJECT_NAME VERSION_NAME or later 14 | */ 15 | @interface M3InstallController : NSObject 16 | 17 | /** 18 | BRIEF_HERE 19 | @param PARAM_NAME PARAM_DESCRIPTION 20 | @return RETURN_DESCRIPTION 21 | @since PROJECT_NAME VERSION_NAME or later 22 | */ 23 | - (void)displayInstaller; 24 | 25 | /** 26 | BRIEF_HERE 27 | @since PROJECT_NAME VERSION_NAME or later 28 | */ 29 | - (void)installApp; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /M3InstallController/M3InstallController.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InstallController.m 3 | M3AppKit 4 | 5 | Created by Martin Pilkington on 02/06/2007. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | #import "M3InstallController.h" 11 | #import "NSWorkspace_RBAdditions.h" 12 | 13 | 14 | @implementation M3InstallController{ 15 | NSAlert *alert; 16 | } 17 | 18 | //*****// 19 | - (id)init { 20 | if ((self = [super init])) { 21 | NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; 22 | NSString *title = [NSString stringWithFormat:NSLocalizedString(@"%@ is currently running from a disk image", @"AppName is currently running from a disk image"), appName]; 23 | NSString *body = NSLocalizedString(@"Would you like to install %@ in your applications folder before quitting?", @"Would you like to install App Name in your applications folder before quitting?"); 24 | alert = [NSAlert alertWithMessageText:title 25 | defaultButton:NSLocalizedString(@"Install", @"Install") 26 | alternateButton:NSLocalizedString(@"Don't Install", @"Don't Install") 27 | otherButton:nil 28 | informativeTextWithFormat:body, appName]; 29 | [alert setShowsSuppressionButton:YES]; 30 | } 31 | return self; 32 | } 33 | 34 | //*****// 35 | - (void)displayInstaller { 36 | NSString *imageFilePath = [[NSWorkspace sharedWorkspace] propertiesForPath:[NSBundle mainBundle].bundlePath][NSWorkspace_RBimagefilepath]; 37 | 38 | //Check that we are in a disk image, but that it isn't a file vault disk image 39 | BOOL isInFileVaultSparseImage = [imageFilePath isEqualToString:[NSString stringWithFormat:@"/Users/.%@/%@.sparseimage", NSUserName(), NSUserName()]]; 40 | BOOL isInFileVaultSparseBundle = [imageFilePath isEqualToString:[NSString stringWithFormat:@"/Users/.%@/%@.sparsebundle", NSUserName(), NSUserName()]]; 41 | if (!imageFilePath || isInFileVaultSparseImage || isInFileVaultSparseBundle || [[NSUserDefaults standardUserDefaults] boolForKey:@"M3DontAskInstallAgain"]) return; 42 | 43 | NSInteger returnValue = [alert runModal]; 44 | if (returnValue == NSAlertDefaultReturn) { 45 | [self installApp]; 46 | } 47 | if (alert.suppressionButton.state == NSOnState) { 48 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"M3DontAskInstallAgain"]; 49 | } 50 | } 51 | 52 | //*****// 53 | - (void)installApp { 54 | NSString *appsPath = [@"/Applications" stringByAppendingPathComponent:[NSBundle mainBundle].bundlePath.lastPathComponent]; 55 | NSString *userAppsPath = [[@"~/Applications" stringByAppendingPathComponent:[NSBundle mainBundle].bundlePath.lastPathComponent] stringByExpandingTildeInPath]; 56 | NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; 57 | 58 | //Delete the app that is installed 59 | if ([[NSFileManager defaultManager] fileExistsAtPath:appsPath]) { 60 | [[NSFileManager defaultManager] removeItemAtPath:appsPath error:nil]; 61 | } 62 | //Delete the app that is installed 63 | if ([[NSFileManager defaultManager] copyItemAtPath:[[NSBundle mainBundle] bundlePath] toPath:appsPath error:nil]) { 64 | NSInteger result = NSRunAlertPanel([NSString stringWithFormat:NSLocalizedString(@"%@ installed successfully", @"App Name installed successfully"), appName], 65 | [NSString stringWithFormat:NSLocalizedString(@"%@ was installed in /Applications", @"App Name was installed in /Applications"), appName], 66 | NSLocalizedString(@"Show In Finder", @"Show In Finder"), NSLocalizedString(@"Quit", @"Quit"), nil); 67 | if (result == NSOKButton) { 68 | [[NSWorkspace sharedWorkspace] selectFile:appsPath inFileViewerRootedAtPath:[appsPath stringByDeletingLastPathComponent]]; 69 | } 70 | } else { 71 | if ([[NSFileManager defaultManager] fileExistsAtPath:userAppsPath]) { 72 | [[NSFileManager defaultManager] removeItemAtPath:userAppsPath error:nil]; 73 | } 74 | if ([[NSFileManager defaultManager] copyItemAtPath:[NSBundle mainBundle].bundlePath toPath:userAppsPath error:nil]) { 75 | NSInteger result = NSRunAlertPanel([NSString stringWithFormat:NSLocalizedString(@"%@ installed successfully", @"AppName installed successfully"), appName], 76 | [NSString stringWithFormat:NSLocalizedString(@"%@ was installed in %@", @"App Name was installed in %@"), appName, [@"~/Applications" stringByExpandingTildeInPath]], 77 | NSLocalizedString(@"Show In Finder", @"Show In Finder"), NSLocalizedString(@"Quit", @"Quit"), nil); 78 | if (result == NSOKButton) { 79 | [[NSWorkspace sharedWorkspace] selectFile:userAppsPath inFileViewerRootedAtPath:[userAppsPath stringByDeletingLastPathComponent]]; 80 | } 81 | } else { 82 | NSInteger result = NSRunAlertPanel([NSString stringWithFormat:NSLocalizedString(@"Could not install %@", @"Could not install App Name"), appName], 83 | NSLocalizedString(@"An error occurred when installing", @"An error occurred when installing"), NSLocalizedString(@"Quit", @"Quit"), nil, nil); 84 | if(result == NSOKButton) { 85 | [NSApp terminate:self]; 86 | } 87 | } 88 | } 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /M3SplitView/M3SplitView.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3SplitView.h 3 | M3AppKit 4 | 5 | Created by Martin Pilkington on 19/08/2011. 6 | 7 | Please read the LICENCE.txt for licensing information 8 | *****************************************************************/ 9 | 10 | @protocol M3SplitViewDelegate; 11 | 12 | 13 | /** 14 | BRIEF_HERE 15 | @since PROJECT_NAME VERSION_NAME or later 16 | */ 17 | @interface M3SplitView : NSView 18 | 19 | /** 20 | BRIEF_HERE 21 | @since PROJECT_NAME VERSION_NAME or later 22 | */ 23 | @property (assign, getter = hasVerticalDivider, nonatomic) BOOL verticalDivider; 24 | 25 | /** 26 | BRIEF_HERE 27 | @since PROJECT_NAME VERSION_NAME or later 28 | */ 29 | @property (copy) NSString *autosaveName; 30 | 31 | /** 32 | BRIEF_HERE 33 | @since PROJECT_NAME VERSION_NAME or later 34 | */ 35 | @property (strong) NSColor *backgroundColour; 36 | 37 | /** 38 | BRIEF_HERE 39 | @since PROJECT_NAME VERSION_NAME or later 40 | */ 41 | @property (unsafe_unretained) IBOutlet id delegate; 42 | 43 | 44 | /** 45 | BRIEF_HERE 46 | @param PARAM_NAME PARAM_DESCRIPTION 47 | @return RETURN_DESCRIPTION 48 | @since PROJECT_NAME VERSION_NAME or later 49 | */ 50 | - (NSView *)subviewAtIndex:(NSInteger)aIndex; 51 | 52 | /** 53 | BRIEF_HERE 54 | @param PARAM_NAME PARAM_DESCRIPTION 55 | @return RETURN_DESCRIPTION 56 | @since PROJECT_NAME VERSION_NAME or later 57 | */ 58 | - (NSInteger)indexOfSubview:(NSView *)aView; 59 | 60 | 61 | /** 62 | BRIEF_HERE 63 | @since PROJECT_NAME VERSION_NAME or later 64 | */ 65 | - (void)drawDividerInRect:(NSRect)aRect vertical:(BOOL)aVertical; 66 | 67 | @end 68 | 69 | 70 | 71 | 72 | /** 73 | BRIEF_HERE 74 | @since PROJECT_NAME VERSION_NAME or later 75 | */ 76 | @protocol M3SplitViewDelegate 77 | 78 | @optional 79 | /** 80 | BRIEF_HERE 81 | @param PARAM_NAME PARAM_DESCRIPTION 82 | @return RETURN_DESCRIPTION 83 | @since PROJECT_NAME VERSION_NAME or later 84 | */ 85 | - (BOOL)splitView:(M3SplitView *)aSplitView shouldFrameChangeResizeSubviewAtIndex:(NSUInteger)aIndex; 86 | 87 | @end -------------------------------------------------------------------------------- /M3SplitView/M3SplitViewKnob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcubedsw/loose-code/ea42218776fb9fbcbeb1c80ba1cad66aafe0a2d0/M3SplitView/M3SplitViewKnob.png -------------------------------------------------------------------------------- /Minim UI Components/M3AudioLevelBar.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AudioLevelBar.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 23/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | #import "M3GlossyBar.h" 35 | 36 | /** 37 | @class CLASS_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @interface M3AudioLevelBar : M3GlossyBar { 42 | BOOL useGreyscale; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Minim UI Components/M3AudioLevelBar.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3AudioLevelBar.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 23/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3AudioLevelBar.h" 33 | #import "NSBezierPath+MCAdditions.h" 34 | #import "NSShadow+M3Extensions.h" 35 | #import "NSBezierPath+M3Extensions.h" 36 | 37 | @implementation M3AudioLevelBar 38 | 39 | - (BOOL)acceptsFirstResponder { 40 | return NO; 41 | } 42 | 43 | /** 44 | Draw the level indicator 45 | */ 46 | - (void)drawLevelInPath:(NSBezierPath *)path { 47 | NSRect innerRect = [path bounds]; 48 | 49 | NSRect light = NSMakeRect(innerRect.origin.x + 5, innerRect.origin.y + 2, 6, 2); 50 | CGFloat widthLeft = innerRect.size.width; 51 | //For each row 52 | while (widthLeft > light.size.width) { 53 | //Set the colour and glow for the level 54 | if (light.origin.x < innerRect.size.width * level) { 55 | if (!useGreyscale) { 56 | if (light.origin.x > innerRect.size.width * 0.9) { 57 | [[NSColor colorWithCalibratedRed:0.910 green:0.253 blue:0.194 alpha:1.000] set]; 58 | [[NSShadow m3_shadowWithColor:[NSColor redColor] offset:NSZeroSize blurRadius:2.0] set]; 59 | } else if (light.origin.x > innerRect.size.width * 0.7) { 60 | [[NSColor yellowColor] set]; 61 | [[NSShadow m3_shadowWithColor:[NSColor yellowColor] offset:NSZeroSize blurRadius:2.0] set]; 62 | } else { 63 | [[NSColor colorWithCalibratedRed:0.254 green:0.910 blue:0.341 alpha:1.000] set]; 64 | [[NSShadow m3_shadowWithColor:[NSColor greenColor] offset:NSZeroSize blurRadius:2.0] set]; 65 | } 66 | } else { 67 | if (light.origin.x > innerRect.size.width * 0.9) { 68 | [[NSColor colorWithCalibratedWhite:1.0 alpha:1.0] set]; 69 | } else if (light.origin.x > innerRect.size.width * 0.7) { 70 | [[NSColor colorWithCalibratedWhite:1.0 alpha:0.6] set]; 71 | } else { 72 | [[NSColor colorWithCalibratedWhite:1.0 alpha:0.4] set]; 73 | } 74 | } 75 | } else { 76 | [[NSColor colorWithCalibratedWhite:1.0 alpha:0.15] set]; 77 | [[NSShadow m3_shadowWithColor:[NSColor clearColor] offset:NSZeroSize blurRadius:0] set]; 78 | } 79 | 80 | //Draw the current column 81 | light.origin.y = innerRect.origin.y + 2; 82 | CGFloat heightLeft = innerRect.size.height; 83 | while (heightLeft > light.size.height) { 84 | if ([path m3_containsRect:light]) { 85 | [NSBezierPath fillRect:light]; 86 | } else { 87 | NSRect halfLight = light; 88 | halfLight.size.width /= (3/2.0); 89 | if (light.origin.x < NSMidX(innerRect)) { 90 | halfLight.origin.x += light.size.width - halfLight.size.width; 91 | } 92 | if ([path m3_containsRect:halfLight]) { 93 | [NSBezierPath fillRect:halfLight]; 94 | } 95 | } 96 | light.origin.y += light.size.height + 2; 97 | heightLeft -= light.size.height +2; 98 | } 99 | light.origin.x += light.size.width + 4; 100 | widthLeft -= light.size.width + 4; 101 | } 102 | } 103 | 104 | /** 105 | Handle the switch between greyscale mode 106 | */ 107 | - (void)mouseUp:(NSEvent *)theEvent { 108 | useGreyscale = !useGreyscale; 109 | [self setNeedsDisplay:YES]; 110 | } 111 | 112 | - (void)mouseDown:(NSEvent *)theEvent { 113 | //Cancel out the super class 114 | } 115 | 116 | - (void)mouseDragged:(NSEvent *)theEvent { 117 | //Cancel out the super class 118 | } 119 | 120 | - (void)keyDown:(NSEvent *)theEvent { 121 | //Cancel out the super class; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Minim UI Components/M3DigitalClockView.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3DigitalClockView.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 22/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | /** 35 | @class CLASS_HERE 36 | DESCRIPTION_HERE 37 | @since Available in M3AppKit 1.0 and later 38 | */ 39 | @interface M3DigitalClockView : NSView { 40 | NSTimeInterval time; 41 | NSTimer *timer; 42 | NSInteger hourTime; 43 | NSInteger minutesTime; 44 | NSInteger secondsTime; 45 | } 46 | 47 | /** 48 | @property PROPERTY_NAME 49 | ABSTRACT_HERE 50 | @since Available in M3AppKit 1.0 and later 51 | */ 52 | @property (assign) NSTimeInterval time; 53 | 54 | 55 | /** 56 | ABSTRACT_HERE 57 | DISCUSSION_HERE 58 | @param PARAM_HERE 59 | @param PARAM_HERE 60 | @result RESULT_HERE 61 | @since Available in M3AppKit 1.0 and later 62 | */ 63 | - (IBAction)start:(id)sender; 64 | 65 | /** 66 | ABSTRACT_HERE 67 | DISCUSSION_HERE 68 | @param PARAM_HERE 69 | @param PARAM_HERE 70 | @result RESULT_HERE 71 | @since Available in M3AppKit 1.0 and later 72 | */ 73 | - (IBAction)stop:(id)sender; 74 | 75 | /** 76 | ABSTRACT_HERE 77 | DISCUSSION_HERE 78 | @param PARAM_HERE 79 | @param PARAM_HERE 80 | @result RESULT_HERE 81 | @since Available in M3AppKit 1.0 and later 82 | */ 83 | - (IBAction)reset:(id)sender; 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Minim UI Components/M3DigitalClockView.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3DigitalClockView.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 22/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3DigitalClockView.h" 33 | #import "NSBezierPath+MCAdditions.h" 34 | #import "NSShadow+M3Extensions.h" 35 | 36 | static NSGradient *backGradient; 37 | static NSGradient *glossGradient; 38 | static NSShadow *insetShadow; 39 | 40 | 41 | @interface M3DigitalClockView () 42 | 43 | - (NSBezierPath *)bezierPathInRect:(NSRect)rect forInteger:(NSUInteger)integer; 44 | - (void)drawDividerInRect:(NSRect)rect size:(CGFloat)size; 45 | 46 | @end 47 | 48 | 49 | 50 | 51 | @implementation M3DigitalClockView 52 | 53 | @synthesize time; 54 | 55 | /** 56 | Set up gradients 57 | */ 58 | + (void)initialize { 59 | backGradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedRed:0.108 green:0.142 blue:0.228 alpha:1.000], 0.0, 60 | [NSColor colorWithCalibratedRed:0.004 green:0.031 blue:0.097 alpha:1.000], 0.5, 61 | [NSColor colorWithCalibratedRed:0.076 green:0.110 blue:0.208 alpha:1.000], 1.0, nil]; 62 | glossGradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedWhite:1.0 alpha:0.1], 0.49, 63 | [NSColor clearColor], 0.5, nil]; 64 | insetShadow = [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] offset:NSMakeSize(0, -1) blurRadius:0] retain]; 65 | } 66 | 67 | - (id)initWithFrame:(NSRect)frame { 68 | if ((self = [super initWithFrame:frame])) { 69 | time = 0; 70 | } 71 | return self; 72 | } 73 | 74 | /** 75 | Update the time 76 | */ 77 | - (void)setTime:(NSTimeInterval)newTime { 78 | time = newTime; 79 | [self setNeedsDisplay:YES]; 80 | NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification); 81 | } 82 | 83 | /** 84 | Start counting once per second 85 | */ 86 | - (IBAction)start:(id)sender { 87 | timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(increment:) userInfo:nil repeats:YES]; 88 | } 89 | 90 | /** 91 | Update the time 92 | */ 93 | - (void)increment:(NSTimer *)timer { 94 | time++; 95 | [self setNeedsDisplay:YES]; 96 | NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification); 97 | } 98 | 99 | /** 100 | Stop timing 101 | */ 102 | - (IBAction)stop:(id)sender { 103 | [timer invalidate]; 104 | timer = nil; 105 | } 106 | 107 | /** 108 | Go back to 0 109 | */ 110 | - (IBAction)reset:(id)sender { 111 | time = 0; 112 | [self setNeedsDisplay:YES]; 113 | } 114 | 115 | 116 | #pragma mark - 117 | #pragma mark Drawing 118 | 119 | /** 120 | Calculate each digit from the time interval 121 | */ 122 | - (NSArray *)digitsArray { 123 | hourTime = ((int)time/3600); 124 | NSInteger dig2 = hourTime % 10; 125 | NSInteger dig1 = ((hourTime % 100) - dig2)/10; 126 | 127 | minutesTime = (((int)time - hourTime * 3600)/60); 128 | NSInteger dig4 = minutesTime % 10; 129 | NSInteger dig3 = ((minutesTime % 100) - dig4)/10; 130 | 131 | secondsTime = (int)time - (minutesTime * 60) - (hourTime * 3600); 132 | NSInteger dig6 = secondsTime % 10; 133 | NSInteger dig5 = (secondsTime - dig6)/10; 134 | 135 | return [NSArray arrayWithObjects:[NSNumber numberWithInteger:dig1], [NSNumber numberWithInteger:dig2], 136 | [NSNumber numberWithInteger:dig3], [NSNumber numberWithInteger:dig4], 137 | [NSNumber numberWithInteger:dig5], [NSNumber numberWithInteger:dig6], nil]; 138 | } 139 | 140 | /** 141 | Draw the clock 142 | */ 143 | - (void)drawRect:(NSRect)dirtyRect { 144 | //Draw the black border with the inner shadow 145 | dirtyRect = NSInsetRect([self bounds], 1, 1); 146 | if (dirtyRect.size.width > 0 && dirtyRect.size.height > 0) { 147 | [[NSColor blackColor] set]; 148 | [insetShadow set]; 149 | [[NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:5 yRadius:5] fill]; 150 | } 151 | 152 | //Draw the background gradient 153 | dirtyRect = NSInsetRect(dirtyRect, 1, 1); 154 | if (dirtyRect.size.width > 0 && dirtyRect.size.height > 0) { 155 | NSBezierPath *clockShapePath = [NSBezierPath bezierPathWithRoundedRect:dirtyRect xRadius:5 yRadius:5]; 156 | [backGradient drawInBezierPath:clockShapePath angle:-90]; 157 | 158 | 159 | 160 | 161 | //Draw digits 162 | [[NSColor colorWithCalibratedRed:0.078 green:0.645 blue:1.000 alpha:1.000] set]; 163 | 164 | [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedRed:0.078 green:0.645 blue:1.000 alpha:0.7] 165 | offset:NSZeroSize 166 | blurRadius:7.0] set]; 167 | 168 | CGFloat digitHeight = dirtyRect.size.height * 0.6; 169 | CGFloat digitWidth = digitHeight / 1.75; 170 | CGFloat totalWidth = (digitWidth * 11.5); 171 | CGFloat padding = (dirtyRect.size.width - totalWidth) / 2; 172 | 173 | 174 | if (padding < 10) { 175 | padding = 10; 176 | } 177 | 178 | NSRect rect = NSMakeRect(padding, dirtyRect.size.height * 0.2, digitWidth, digitHeight); 179 | 180 | 181 | 182 | NSArray *digits = [self digitsArray]; 183 | for (NSInteger i = 0; i < 6; i++) { 184 | NSBezierPath *path = [self bezierPathInRect:rect forInteger:[[digits objectAtIndex:i] integerValue]]; 185 | [path setLineWidth:digitWidth/4]; 186 | [path stroke]; 187 | rect.origin.x += digitWidth * 1.5; 188 | if (i == 1 || i == 3) { 189 | [self drawDividerInRect:rect size:digitWidth/4]; 190 | rect.origin.x += digitWidth * 1.5; 191 | } 192 | } 193 | 194 | //Draw the gloss 195 | [glossGradient drawInBezierPath:clockShapePath angle:-90]; 196 | 197 | //Add the inner stroke 198 | NSShadow *shadow = [NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.85] offset:NSZeroSize blurRadius:1.0]; 199 | [clockShapePath fillWithInnerShadow:shadow]; 200 | } 201 | 202 | } 203 | 204 | /** 205 | Get the bezier path for the supplied digit 206 | */ 207 | - (NSBezierPath *)bezierPathInRect:(NSRect)rect forInteger:(NSUInteger)integer { 208 | NSBezierPath *path = nil; 209 | NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); 210 | NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); 211 | NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect)); 212 | NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); 213 | NSPoint middleLeft = NSMakePoint(NSMinX(rect), NSMidY(rect)); 214 | NSPoint middleRight = NSMakePoint(NSMaxX(rect), NSMidY(rect)); 215 | 216 | if (integer == 0) { 217 | path = [NSBezierPath bezierPath]; 218 | [path moveToPoint:topLeft]; 219 | [path lineToPoint:bottomLeft]; 220 | [path lineToPoint:bottomRight]; 221 | [path lineToPoint:topRight]; 222 | [path lineToPoint:topLeft]; 223 | } else if (integer == 1) { 224 | path = [NSBezierPath bezierPath]; 225 | [path moveToPoint:bottomRight]; 226 | [path lineToPoint:topRight]; 227 | } else if (integer == 2) { 228 | path = [NSBezierPath bezierPath]; 229 | [path moveToPoint:topLeft]; 230 | [path lineToPoint:topRight]; 231 | [path lineToPoint:middleRight]; 232 | [path lineToPoint:middleLeft]; 233 | [path lineToPoint:bottomLeft]; 234 | [path lineToPoint:bottomRight]; 235 | } else if (integer == 3) { 236 | path = [NSBezierPath bezierPath]; 237 | [path moveToPoint:topLeft]; 238 | [path lineToPoint:topRight]; 239 | [path lineToPoint:bottomRight]; 240 | [path lineToPoint:bottomLeft]; 241 | [path moveToPoint:middleLeft]; 242 | [path lineToPoint:middleRight]; 243 | } else if (integer == 4) { 244 | path = [NSBezierPath bezierPath]; 245 | [path moveToPoint:topLeft]; 246 | [path lineToPoint:middleLeft]; 247 | [path lineToPoint:middleRight]; 248 | [path lineToPoint:topRight]; 249 | [path lineToPoint:bottomRight]; 250 | } else if (integer == 5) { 251 | path = [NSBezierPath bezierPath]; 252 | [path moveToPoint:topRight]; 253 | [path lineToPoint:topLeft]; 254 | [path lineToPoint:middleLeft]; 255 | [path lineToPoint:middleRight]; 256 | [path lineToPoint:bottomRight]; 257 | [path lineToPoint:bottomLeft]; 258 | } else if (integer == 6) { 259 | path = [NSBezierPath bezierPath]; 260 | [path moveToPoint:topLeft]; 261 | [path lineToPoint:bottomLeft]; 262 | [path lineToPoint:bottomRight]; 263 | [path lineToPoint:middleRight]; 264 | [path lineToPoint:middleLeft]; 265 | } else if (integer == 7) { 266 | path = [NSBezierPath bezierPath]; 267 | [path moveToPoint:bottomRight]; 268 | [path lineToPoint:topRight]; 269 | [path lineToPoint:topLeft]; 270 | } else if (integer == 8) { 271 | path = [NSBezierPath bezierPath]; 272 | [path moveToPoint:topLeft]; 273 | [path lineToPoint:bottomLeft]; 274 | [path lineToPoint:bottomRight]; 275 | [path lineToPoint:topRight]; 276 | [path lineToPoint:topLeft]; 277 | [path moveToPoint:middleLeft]; 278 | [path lineToPoint:middleRight]; 279 | } else if (integer == 9) { 280 | path = [NSBezierPath bezierPath]; 281 | [path moveToPoint:bottomRight]; 282 | [path lineToPoint:topRight]; 283 | [path lineToPoint:topLeft]; 284 | [path lineToPoint:middleLeft]; 285 | [path lineToPoint:middleRight]; 286 | } 287 | [path setLineCapStyle:NSRoundLineCapStyle]; 288 | [path setLineJoinStyle:NSRoundLineJoinStyle]; 289 | return path; 290 | } 291 | 292 | /** 293 | Draw the time unit divider 294 | */ 295 | - (void)drawDividerInRect:(NSRect)rect size:(CGFloat)size { 296 | NSRect topRect = NSMakeRect(NSMidX(rect)-(size/2), NSMidY(rect)+(size/2), size, size); 297 | [[NSBezierPath bezierPathWithRoundedRect:topRect xRadius:2 yRadius:2] fill]; 298 | 299 | NSRect bottomRect = NSMakeRect(NSMidX(rect)-(size/2), NSMidY(rect)-(size*1.5), size, size); 300 | [[NSBezierPath bezierPathWithRoundedRect:bottomRect xRadius:2 yRadius:2] fill]; 301 | } 302 | 303 | 304 | #pragma mark - 305 | #pragma mark Accessibility 306 | 307 | - (BOOL)accessibilityIsIgnored { 308 | return NO; 309 | } 310 | 311 | - (NSArray *)accessibilityAttributeNames { 312 | static NSArray *attributes = nil; 313 | if (attributes == nil) { 314 | attributes = [[super accessibilityAttributeNames] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:NSAccessibilityDescriptionAttribute, NSAccessibilityValueAttribute, nil]]; 315 | } 316 | return attributes; 317 | } 318 | 319 | 320 | - (id)accessibilityAttributeValue:(NSString *)attribute { 321 | if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 322 | return NSAccessibilityStaticTextRole; 323 | } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 324 | return NSLocalizedString(@"timer", @"Accessibility role description for digital clock in recording panel"); 325 | } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 326 | return NSLocalizedString(@"recording", @"Accessibility description for digital clock in recording panel"); 327 | } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 328 | [self digitsArray]; 329 | if (hourTime > 0) { 330 | return [NSString stringWithFormat:NSLocalizedString(@"%d hours, %d minutes, %d seconds", @"Accessibility value format for digital clock in recording panel"), hourTime, minutesTime, secondsTime]; 331 | } else if (minutesTime > 0) { 332 | return [NSString stringWithFormat:NSLocalizedString(@"%d minutes, %d seconds", @"Accessibility value format for digital clock in recording panel"), minutesTime, secondsTime]; 333 | } else { 334 | return [NSString stringWithFormat:NSLocalizedString(@"%d seconds", @"Accessibility value format for digital clock in recording panel"), secondsTime]; 335 | } 336 | } 337 | return [super accessibilityAttributeValue:attribute]; 338 | } 339 | 340 | - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { 341 | if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 342 | return NO; 343 | } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 344 | return NO; 345 | } 346 | return [super accessibilityIsAttributeSettable:attribute]; 347 | } 348 | 349 | 350 | @end 351 | -------------------------------------------------------------------------------- /Minim UI Components/M3GlossyBar.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3GlossyBar.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 24/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @protocol M3GlossyBarDelegate; 35 | /** 36 | @class CLASS_HERE 37 | DESCRIPTION_HERE 38 | @since Available in M3AppKit 1.0 and later 39 | */ 40 | @interface M3GlossyBar : NSView { 41 | CGFloat level; 42 | id delegate; 43 | } 44 | 45 | /** 46 | @property PROPERTY_NAME 47 | ABSTRACT_HERE 48 | @since Available in M3AppKit 1.0 and later 49 | */ 50 | @property (assign, nonatomic) CGFloat level; 51 | 52 | /** 53 | @property PROPERTY_NAME 54 | ABSTRACT_HERE 55 | @since Available in M3AppKit 1.0 and later 56 | */ 57 | @property (assign) id delegate; 58 | 59 | 60 | /** 61 | ABSTRACT_HERE 62 | DISCUSSION_HERE 63 | @param PARAM_HERE 64 | @param PARAM_HERE 65 | @result RESULT_HERE 66 | @since Available in M3AppKit 1.0 and later 67 | */ 68 | - (void)drawLevelInPath:(NSBezierPath *)path; 69 | 70 | @end -------------------------------------------------------------------------------- /Minim UI Components/M3GlossyBar.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3GlossyBar.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 24/09/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3GlossyBar.h" 33 | 34 | #import "NSBezierPath+MCAdditions.h" 35 | #import "NSShadow+M3Extensions.h" 36 | #import "M3GlossyBarDelegate.h" 37 | 38 | static NSGradient *mainGradient; 39 | static NSGradient *glossGradient; 40 | static NSShadow *innerShadow; 41 | static NSShadow *insetShadow; 42 | 43 | @implementation M3GlossyBar 44 | 45 | @synthesize level; 46 | @synthesize delegate; 47 | 48 | /** 49 | Set up gradients and shadows 50 | */ 51 | + (void)initialize { 52 | mainGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:0.208 green:0.212 blue:0.222 alpha:1.000] 53 | endingColor:[NSColor colorWithCalibratedRed:0.065 green:0.074 blue:0.082 alpha:1.000]]; 54 | glossGradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedWhite:1.0 alpha:0.1], 0.49, 55 | [NSColor clearColor], 0.5, nil]; 56 | insetShadow = [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.2] offset:NSMakeSize(0, -1) blurRadius:0] retain]; 57 | innerShadow = [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.4] offset:NSMakeSize(0, -2) blurRadius:2] retain]; 58 | } 59 | 60 | 61 | - (id)initWithFrame:(NSRect)frame { 62 | if ((self = [super initWithFrame:frame])) { 63 | level = 0.0; 64 | } 65 | return self; 66 | } 67 | 68 | - (id)initWithCoder:(NSCoder *)aDecoder { 69 | if ((self = [super initWithCoder:aDecoder])) { 70 | level = [aDecoder decodeFloatForKey:@"glossyBarLevel"]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)encodeWithCoder:(NSCoder *)aCoder { 76 | [super encodeWithCoder:aCoder]; 77 | [aCoder encodeFloat:level forKey:@"glossyBarLevel"]; 78 | } 79 | 80 | - (BOOL)acceptsFirstResponder { 81 | return YES; 82 | } 83 | 84 | /** 85 | Set the level and refresh 86 | */ 87 | - (void)setLevel:(CGFloat)newLevel { 88 | level = newLevel; 89 | [self setNeedsDisplay:YES]; 90 | } 91 | 92 | /** 93 | Draw the glossy bar 94 | */ 95 | - (void)drawRect:(NSRect)dirtyRect { 96 | NSRect drawingRect = NSInsetRect([self bounds], 1, 1); 97 | if (drawingRect.size.width > 0 && drawingRect.size.height > 0) { 98 | //Draw background and shadow 99 | NSBezierPath *outlinePath = [NSBezierPath bezierPathWithRoundedRect:drawingRect xRadius:drawingRect.size.height/2 yRadius:drawingRect.size.height/2]; 100 | 101 | if ([[self window] firstResponder] == self) { 102 | [NSGraphicsContext saveGraphicsState]; 103 | NSSetFocusRingStyle(NSFocusRingOnly); 104 | [outlinePath fill]; 105 | [NSGraphicsContext restoreGraphicsState]; 106 | } 107 | 108 | [insetShadow set]; 109 | [[NSColor blackColor] set]; 110 | [outlinePath fill]; 111 | 112 | //Draw the background gradient 113 | NSRect innerRect = NSInsetRect(drawingRect, 1, 1); 114 | NSBezierPath *innerPath = [NSBezierPath bezierPathWithRoundedRect:innerRect xRadius:innerRect.size.height/2 yRadius:innerRect.size.height/2]; 115 | [mainGradient drawInBezierPath:innerPath angle:-90]; 116 | 117 | //Draw level 118 | [self drawLevelInPath:innerPath]; 119 | 120 | //Draw the gloss and inner shadow 121 | [glossGradient drawInBezierPath:innerPath angle:-90]; 122 | [innerPath fillWithInnerShadow:innerShadow]; 123 | } 124 | } 125 | 126 | /** 127 | Draw the progress 128 | */ 129 | - (void)drawLevelInPath:(NSBezierPath *)path { 130 | CGFloat lowerLevel = level-0.001; 131 | if (lowerLevel < 0) { 132 | lowerLevel = 0; 133 | } 134 | 135 | if (level != lowerLevel) { 136 | NSGradient *fillGrad = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedRed:0.169 green:0.324 blue:0.765 alpha:1.000], lowerLevel, 137 | [NSColor clearColor], level, nil]; 138 | [fillGrad drawInBezierPath:path angle:0]; 139 | [fillGrad release]; 140 | } 141 | } 142 | 143 | #pragma mark - 144 | #pragma mark Mouse Events 145 | 146 | /** 147 | Handle clicks and drags to update the level 148 | */ 149 | - (void)mouseDown:(NSEvent *)theEvent { 150 | NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 151 | NSSize frameSize = [self frame].size; 152 | [self setLevel:location.x / frameSize.width]; 153 | 154 | if ([[self delegate] respondsToSelector:@selector(glossyBarDidUpdateLevel:)]) { 155 | [[self delegate] glossyBarDidUpdateLevel:self]; 156 | } 157 | } 158 | 159 | - (void)mouseDragged:(NSEvent *)theEvent { 160 | NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 161 | NSSize frameSize = [self frame].size; 162 | CGFloat newLevel = location.x / frameSize.width; 163 | if (newLevel < 0) { 164 | newLevel = 0; 165 | } else if (newLevel > 1) { 166 | newLevel = 1; 167 | } 168 | [self setLevel:newLevel]; 169 | 170 | if ([[self delegate] respondsToSelector:@selector(glossyBarDidUpdateLevel:)]) { 171 | [[self delegate] glossyBarDidUpdateLevel:self]; 172 | } 173 | } 174 | 175 | - (void)keyDown:(NSEvent *)theEvent { 176 | if (([theEvent keyCode] == 123 || [theEvent keyCode] == 124)) { 177 | if ([theEvent modifierFlags] & NSAlternateKeyMask) { 178 | [self setLevel:[theEvent keyCode] == 123 ? 0 : 1]; 179 | } else { 180 | [self setLevel:[self level] + ([theEvent keyCode] == 123 ? -0.01 : 0.01)]; 181 | } 182 | if ([[self delegate] respondsToSelector:@selector(glossyBarDidUpdateLevel:)]) { 183 | [[self delegate] glossyBarDidUpdateLevel:self]; 184 | } 185 | } else { 186 | [super keyDown:theEvent]; 187 | } 188 | } 189 | 190 | 191 | #pragma mark - 192 | #pragma mark Accessibility 193 | 194 | - (BOOL)accessibilityIsIgnored { 195 | return NO; 196 | } 197 | 198 | - (NSArray *)accessibilityAttributeNames { 199 | static NSArray *attributes = nil; 200 | if (attributes == nil) { 201 | attributes = [[super accessibilityAttributeNames] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:NSAccessibilityValueAttribute, nil]]; 202 | } 203 | return attributes; 204 | } 205 | 206 | 207 | - (id)accessibilityAttributeValue:(NSString *)attribute { 208 | if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { 209 | return NSAccessibilitySliderRole; 210 | } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { 211 | return NSAccessibilityRoleDescription(NSAccessibilityProgressIndicatorRole, nil); 212 | } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 213 | return [NSString stringWithFormat:@"%ld %%", (NSInteger)([self level] * 100)]; 214 | } 215 | return [super accessibilityAttributeValue:attribute]; 216 | } 217 | 218 | - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { 219 | if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { 220 | return NO; 221 | } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 222 | return NO; 223 | } 224 | return [super accessibilityIsAttributeSettable:attribute]; 225 | } 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /Minim UI Components/M3GlossyBarDelegate.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3GlossyBarDelegate.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3GlossyBar; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3GlossyBarDelegate 42 | 43 | @optional 44 | /** 45 | ABSTRACT_HERE 46 | DISCUSSION_HERE 47 | @param PARAM_HERE 48 | @param PARAM_HERE 49 | @result RESULT_HERE 50 | @since Available in M3AppKit 1.0 and later 51 | */ 52 | - (void)glossyBarDidUpdateLevel:(M3GlossyBar *)bar; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Minim UI Components/M3InfoPanel.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InfoPanel.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 16/01/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @protocol M3InfoPanelDelegate; 35 | @class M3InfoPanelViewController; 36 | 37 | /** 38 | @class CLASS_HERE 39 | DESCRIPTION_HERE 40 | @since Available in M3AppKit 1.0 and later 41 | */ 42 | @interface M3InfoPanel : NSView { 43 | id delegate; 44 | NSMutableArray *panels; 45 | id representedObject; 46 | BOOL displaysNavigationButtons; 47 | BOOL displaysCloseButton; 48 | BOOL hasPreviousObject; 49 | BOOL hasNextObject; 50 | NSUInteger selectedIndex; 51 | 52 | 53 | NSSegmentedControl *tabControl; 54 | NSButton *closeButton; 55 | NSButton *nextButton; 56 | NSButton *previousButton; 57 | NSView *panelContainer; 58 | } 59 | 60 | /** 61 | @property PROPERTY_NAME 62 | ABSTRACT_HERE 63 | @since Available in M3AppKit 1.0 and later 64 | */ 65 | @property (assign) id delegate; 66 | 67 | /** 68 | @property PROPERTY_NAME 69 | ABSTRACT_HERE 70 | @since Available in M3AppKit 1.0 and later 71 | */ 72 | @property (retain) id representedObject; 73 | 74 | /** 75 | @property PROPERTY_NAME 76 | ABSTRACT_HERE 77 | @since Available in M3AppKit 1.0 and later 78 | */ 79 | @property (assign) BOOL displaysNavigationButtons; 80 | 81 | /** 82 | @property PROPERTY_NAME 83 | ABSTRACT_HERE 84 | @since Available in M3AppKit 1.0 and later 85 | */ 86 | @property (assign) BOOL displaysCloseButton; 87 | 88 | /** 89 | @property PROPERTY_NAME 90 | ABSTRACT_HERE 91 | @since Available in M3AppKit 1.0 and later 92 | */ 93 | @property (assign) BOOL hasPreviousObject; 94 | 95 | /** 96 | @property PROPERTY_NAME 97 | ABSTRACT_HERE 98 | @since Available in M3AppKit 1.0 and later 99 | */ 100 | @property (assign) BOOL hasNextObject; 101 | 102 | 103 | /** 104 | ABSTRACT_HERE 105 | DISCUSSION_HERE 106 | @param PARAM_HERE 107 | @param PARAM_HERE 108 | @result RESULT_HERE 109 | @since Available in M3AppKit 1.0 and later 110 | */ 111 | - (NSArray *)panels; 112 | 113 | /** 114 | ABSTRACT_HERE 115 | DISCUSSION_HERE 116 | @param PARAM_HERE 117 | @param PARAM_HERE 118 | @result RESULT_HERE 119 | @since Available in M3AppKit 1.0 and later 120 | */ 121 | - (void)addPanel:(M3InfoPanelViewController *)panel; 122 | 123 | /** 124 | ABSTRACT_HERE 125 | DISCUSSION_HERE 126 | @param PARAM_HERE 127 | @param PARAM_HERE 128 | @result RESULT_HERE 129 | @since Available in M3AppKit 1.0 and later 130 | */ 131 | - (void)insertPanel:(M3InfoPanelViewController *)aPanel atIndex:(NSUInteger)aIndex; 132 | 133 | /** 134 | ABSTRACT_HERE 135 | DISCUSSION_HERE 136 | @param PARAM_HERE 137 | @param PARAM_HERE 138 | @result RESULT_HERE 139 | @since Available in M3AppKit 1.0 and later 140 | */ 141 | - (void)replacePanelAtIndex:(NSUInteger)aIndex withPanel:(M3InfoPanelViewController *)aPanel; 142 | 143 | /** 144 | ABSTRACT_HERE 145 | DISCUSSION_HERE 146 | @param PARAM_HERE 147 | @param PARAM_HERE 148 | @result RESULT_HERE 149 | @since Available in M3AppKit 1.0 and later 150 | */ 151 | - (void)removePanelAtIndex:(NSUInteger)aIndex; 152 | 153 | /** 154 | ABSTRACT_HERE 155 | DISCUSSION_HERE 156 | @param PARAM_HERE 157 | @param PARAM_HERE 158 | @result RESULT_HERE 159 | @since Available in M3AppKit 1.0 and later 160 | */ 161 | - (void)refreshPanels; 162 | 163 | /** 164 | ABSTRACT_HERE 165 | DISCUSSION_HERE 166 | @param PARAM_HERE 167 | @param PARAM_HERE 168 | @result RESULT_HERE 169 | @since Available in M3AppKit 1.0 and later 170 | */ 171 | - (void)switchToPanelAtIndex:(NSInteger)index; 172 | 173 | @end 174 | 175 | -------------------------------------------------------------------------------- /Minim UI Components/M3InfoPanelDelegate.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InfoPanelDelegate.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 16/01/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3InfoPanel; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3InfoPanelDelegate 42 | 43 | @optional 44 | 45 | /** 46 | ABSTRACT_HERE 47 | DISCUSSION_HERE 48 | @param PARAM_HERE 49 | @param PARAM_HERE 50 | @result RESULT_HERE 51 | @since Available in M3AppKit 1.0 and later 52 | */ 53 | - (void)infoPanelWillMoveToNextObject:(M3InfoPanel *)panel; 54 | 55 | /** 56 | ABSTRACT_HERE 57 | DISCUSSION_HERE 58 | @param PARAM_HERE 59 | @param PARAM_HERE 60 | @result RESULT_HERE 61 | @since Available in M3AppKit 1.0 and later 62 | */ 63 | - (void)infoPanelWillMoveToPreviousObject:(M3InfoPanel *)panel; 64 | 65 | /** 66 | ABSTRACT_HERE 67 | DISCUSSION_HERE 68 | @param PARAM_HERE 69 | @param PARAM_HERE 70 | @result RESULT_HERE 71 | @since Available in M3AppKit 1.0 and later 72 | */ 73 | - (void)infoPanelWillClose:(M3InfoPanel *)panel; 74 | 75 | /** 76 | ABSTRACT_HERE 77 | DISCUSSION_HERE 78 | @param PARAM_HERE 79 | @param PARAM_HERE 80 | @result RESULT_HERE 81 | @since Available in M3AppKit 1.0 and later 82 | */ 83 | - (void)infoPanelSelectionDidChange:(M3InfoPanel *)panel; 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Minim UI Components/M3InfoPanelViewController.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InfoPanelViewController.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 18/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3InfoPanel; 35 | 36 | /** 37 | @class CLASS_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @interface M3InfoPanelViewController : NSViewController { 42 | IBOutlet NSView *firstView; 43 | IBOutlet NSView *lastView; 44 | IBOutlet NSView *buttonBarView; 45 | M3InfoPanel *parent; 46 | } 47 | 48 | /** 49 | @property PROPERTY_NAME 50 | ABSTRACT_HERE 51 | @since Available in M3AppKit 1.0 and later 52 | */ 53 | @property (assign) M3InfoPanel *parent; 54 | 55 | /** 56 | @property PROPERTY_NAME 57 | ABSTRACT_HERE 58 | @since Available in M3AppKit 1.0 and later 59 | */ 60 | @property (retain) NSView *firstView; 61 | 62 | /** 63 | @property PROPERTY_NAME 64 | ABSTRACT_HERE 65 | @since Available in M3AppKit 1.0 and later 66 | */ 67 | @property (retain) NSView *lastView; 68 | 69 | /** 70 | @property PROPERTY_NAME 71 | ABSTRACT_HERE 72 | @since Available in M3AppKit 1.0 and later 73 | */ 74 | @property (retain) NSView *buttonBarView; 75 | 76 | 77 | /** 78 | ABSTRACT_HERE 79 | DISCUSSION_HERE 80 | @param PARAM_HERE 81 | @param PARAM_HERE 82 | @result RESULT_HERE 83 | @since Available in M3AppKit 1.0 and later 84 | */ 85 | - (void)reloadPanel; 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Minim UI Components/M3InfoPanelViewController.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3InfoPanelViewController.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 18/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3InfoPanelViewController.h" 33 | 34 | 35 | @implementation M3InfoPanelViewController 36 | 37 | - (id)initWithCoder:(NSCoder *)aDecoder { 38 | if ((self = [super initWithCoder:aDecoder])) { 39 | [self setParent:[aDecoder decodeObjectForKey:@"parent"]]; 40 | [self setFirstView:[aDecoder decodeObjectForKey:@"firstView"]]; 41 | [self setLastView:[aDecoder decodeObjectForKey:@"lastView"]]; 42 | [self setButtonBarView:[aDecoder decodeObjectForKey:@"buttonBarView"]]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)encodeWithCoder:(NSCoder *)aCoder { 48 | [super encodeWithCoder:aCoder]; 49 | [aCoder encodeConditionalObject:parent forKey:@"parent"]; 50 | [aCoder encodeObject:firstView forKey:@"firstView"]; 51 | [aCoder encodeObject:lastView forKey:@"lastView"]; 52 | [aCoder encodeObject:buttonBarView forKey:@"buttonBarView"]; 53 | } 54 | 55 | - (void)dealloc { 56 | parent = nil; 57 | [firstView release]; 58 | [lastView release]; 59 | [buttonBarView release]; 60 | [super dealloc]; 61 | } 62 | 63 | - (void)loadView { 64 | if ([self nibName]) { 65 | [super loadView]; 66 | } 67 | } 68 | 69 | @synthesize parent; 70 | @synthesize firstView; 71 | @synthesize lastView; 72 | @synthesize buttonBarView; 73 | 74 | - (void)reloadPanel {} 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Minim UI Components/M3PittedRubberView.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3PittedRubberView.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 16/10/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | /** 35 | @class CLASS_HERE 36 | DESCRIPTION_HERE 37 | @since Available in M3AppKit 1.0 and later 38 | */ 39 | @interface M3PittedRubberView : NSView { 40 | NSImage *dots; 41 | } 42 | 43 | 44 | /** 45 | ABSTRACT_HERE 46 | DISCUSSION_HERE 47 | @param PARAM_HERE 48 | @param PARAM_HERE 49 | @result RESULT_HERE 50 | @since Available in M3AppKit 1.0 and later 51 | */ 52 | - (void)drawDots; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Minim UI Components/M3PittedRubberView.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3PittedRubberView.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 16/10/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3PittedRubberView.h" 33 | 34 | #import "NSBezierPath+MCAdditions.h" 35 | #import "NSShadow+M3Extensions.h" 36 | 37 | static NSColor *backColour; 38 | static NSColor *circleColour; 39 | static NSShadow *innerShadow; 40 | static NSShadow *dropShadow; 41 | 42 | @implementation M3PittedRubberView 43 | 44 | + (void)initialize { 45 | backColour = [[NSColor colorWithCalibratedRed:0.109 green:0.112 blue:0.115 alpha:1.000] retain]; 46 | circleColour = [[NSColor colorWithCalibratedRed:0.100 green:0.103 blue:0.103 alpha:1.000] retain]; 47 | innerShadow = [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.2] offset:NSMakeSize(0, -1) blurRadius:0] retain]; 48 | dropShadow = [[NSShadow m3_shadowWithColor:[NSColor colorWithCalibratedWhite:0.7 alpha:0.1] offset:NSMakeSize(0, -1) blurRadius:0] retain]; 49 | } 50 | 51 | - (void)drawRect:(NSRect)dirtyRect { 52 | [backColour set]; 53 | [NSBezierPath fillRect:[self bounds]]; 54 | [self drawDots]; 55 | } 56 | 57 | - (void)drawDots { 58 | if ([self bounds].size.height > [dots size].height || [self bounds].size.width > [dots size].width) { 59 | dots = nil; 60 | } 61 | 62 | NSInteger imageSize = 96; 63 | 64 | if (!dots) { 65 | dots = [[NSImage alloc] initWithSize:NSMakeSize(imageSize, imageSize)]; 66 | [dots lockFocus]; 67 | NSBezierPath *path = [NSBezierPath bezierPath]; 68 | [circleColour set]; 69 | [dropShadow set]; 70 | NSInteger currentY = 1; 71 | while (currentY < [dots size].height) { 72 | NSInteger currentX = (currentY - 1) % 12 == 0 ? 0 : 6; 73 | while (currentX < [dots size].width) { 74 | [path appendBezierPath:[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(currentX, currentY, 4, 4)]]; 75 | currentX += 12; 76 | } 77 | currentY += 6; 78 | } 79 | [path fill]; 80 | [path fillWithInnerShadow:innerShadow]; 81 | [dots unlockFocus]; 82 | } 83 | 84 | NSInteger currentY = 0; 85 | while (currentY < [self bounds].size.height) { 86 | NSInteger currentX = 0; 87 | while (currentX < [self bounds].size.width) { 88 | [dots drawInRect:NSMakeRect(currentX, currentY, imageSize, imageSize) fromRect:NSMakeRect(0, 0, imageSize, imageSize) operation:NSCompositeSourceOver fraction:1]; 89 | currentX += imageSize; 90 | } 91 | currentY += imageSize; 92 | } 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /Minim UI Components/M3SegmentedTabsCell.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3SegmentedTabsCell.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 30/07/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | /** 35 | @class CLASS_HERE 36 | DESCRIPTION_HERE 37 | @since Available in M3AppKit 1.0 and later 38 | */ 39 | @interface M3SegmentedTabsCell : NSSegmentedCell {} 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Minim UI Components/M3SegmentedTabsCell.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3SegmentedTabsCell.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 30/07/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3SegmentedTabsCell.h" 33 | 34 | 35 | @implementation M3SegmentedTabsCell 36 | 37 | /** 38 | Generates the rect in which to draw each segment 39 | */ 40 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { 41 | CGFloat currentX = 3; 42 | for (NSInteger i = 0; i < [self segmentCount]; i++) { 43 | CGFloat segmentWidth = [self widthForSegment:i]; 44 | CGFloat height = cellFrame.size.height; 45 | CGFloat y = 0; 46 | CGFloat x = currentX; 47 | [self drawSegment:i inFrame:NSMakeRect(x, y, segmentWidth, height) withView:controlView]; 48 | currentX += segmentWidth + 1; 49 | } 50 | } 51 | 52 | /** 53 | Draws each segment 54 | */ 55 | - (void)drawSegment:(NSInteger)segment inFrame:(NSRect)frame withView:(NSView *)controlView { 56 | frame.size.width -= 3; 57 | frame.origin.x += 1; 58 | 59 | NSColor *topColor = nil; 60 | NSColor *bottomColor = nil; 61 | NSColor *textColor = nil; 62 | NSColor *shadowColor = nil; 63 | NSSize shadowOffset = NSZeroSize; 64 | CGFloat heightChange = 1; 65 | 66 | //Handle colours 67 | if ([self selectedSegment] == segment) { 68 | topColor = [NSColor colorWithCalibratedRed:0.826 green:0.839 blue:0.860 alpha:1.000]; 69 | bottomColor = [NSColor colorWithCalibratedRed:0.744 green:0.770 blue:0.794 alpha:1.000]; 70 | textColor = [NSColor blackColor]; 71 | shadowColor = [NSColor whiteColor]; 72 | shadowOffset = NSMakeSize(0, -1); 73 | } else { 74 | topColor = [NSColor colorWithCalibratedRed:0.363 green:0.385 blue:0.409 alpha:1.000]; 75 | bottomColor = [NSColor colorWithCalibratedRed:0.316 green:0.342 blue:0.366 alpha:1.000]; 76 | textColor = [NSColor whiteColor]; 77 | shadowColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.7]; 78 | shadowOffset = NSMakeSize(0, 1); 79 | heightChange = 2; 80 | } 81 | 82 | //Creates the tab path 83 | NSBezierPath *path = [NSBezierPath bezierPath]; 84 | CGFloat left = frame.origin.x; 85 | CGFloat right = frame.origin.x + frame.size.width; 86 | 87 | 88 | [path moveToPoint:NSMakePoint(left, frame.size.height-heightChange)]; 89 | [path lineToPoint:NSMakePoint(left, 8)]; 90 | [path curveToPoint:NSMakePoint(left+8, 0) controlPoint1:NSMakePoint(left, 3) controlPoint2:NSMakePoint(left+3, 0)]; 91 | [path lineToPoint:NSMakePoint(right-8, 0)]; 92 | [path curveToPoint:NSMakePoint(right, 8) controlPoint1:NSMakePoint(right-3, 0) controlPoint2:NSMakePoint(right, 3)]; 93 | [path lineToPoint:NSMakePoint(right, frame.size.height-heightChange)]; 94 | 95 | //Draws the gradient in the path 96 | NSGradient *gradient = [[NSGradient alloc] initWithColorsAndLocations:topColor, 0.0, bottomColor, 0.3, nil]; 97 | [gradient drawInBezierPath:path angle:90.0]; 98 | [gradient release]; 99 | 100 | //Attributes 101 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 102 | [attributes setObject:[NSFont systemFontOfSize:12.0] forKey:NSFontAttributeName]; 103 | 104 | NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; 105 | [style setAlignment:NSCenterTextAlignment]; 106 | [attributes setObject:style forKey:NSParagraphStyleAttributeName]; 107 | [style release]; 108 | 109 | [attributes setObject:textColor forKey:NSForegroundColorAttributeName]; 110 | 111 | NSShadow *shadow = [[NSShadow alloc] init]; 112 | [shadow setShadowColor:shadowColor]; 113 | [shadow setShadowOffset:shadowOffset]; 114 | [attributes setObject:shadow forKey:NSShadowAttributeName]; 115 | [shadow release]; 116 | 117 | //Works out the rect to draw the label in and draws it 118 | NSRect rect = [[self labelForSegment:segment] boundingRectWithSize:frame.size options:0 attributes:attributes]; 119 | 120 | frame.origin.y = (frame.size.height / 2) - (rect.size.height / 2); 121 | [[self labelForSegment:segment] drawInRect:frame withAttributes:attributes]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is a catchall for various loose classes from M Cubed apps that don't really fit in a framework. They don't contain tests and have patchy documentation. The code has also not been cleaned up. As such you should be a bit more careful if you use them in your apps. 2 | 3 | All source code in this repository is covered by the MIT licence (found in LICENSE.txt). -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3OutlineView.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3OutlineView.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 28/07/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | #ifdef M3APPKIT_IB_BUILD 35 | #import "M3IndexSetEnumerator.h" 36 | #else 37 | #import 38 | #endif 39 | 40 | 41 | @protocol M3OutlineViewDelegate, M3OutlineViewDataSource; 42 | 43 | /** 44 | @class CLASS_HERE 45 | DESCRIPTION_HERE 46 | @since Available in M3AppKit 1.0 and later 47 | */ 48 | @interface M3OutlineView : NSOutlineView { 49 | NSIndexSet *previousSelection; 50 | NSIndexSet *currentSelection; 51 | } 52 | 53 | /** 54 | @property PROPERTY_NAME 55 | ABSTRACT_HERE 56 | @since Available in M3AppKit 1.0 and later 57 | */ 58 | @property (assign) id delegate; 59 | 60 | /** 61 | @property PROPERTY_NAME 62 | ABSTRACT_HERE 63 | @since Available in M3AppKit 1.0 and later 64 | */ 65 | @property (assign) id dataSource; 66 | 67 | /** 68 | @property PROPERTY_NAME 69 | ABSTRACT_HERE 70 | @since Available in M3AppKit 1.0 and later 71 | */ 72 | @property (readonly, copy) NSIndexSet *previousSelection; 73 | 74 | @end 75 | 76 | 77 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3OutlineView.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3OutlineView.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 28/07/2009. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3OutlineView.h" 33 | 34 | #import "M3OutlineViewDelegate.h" 35 | #import "M3OutlineViewDataSource.h" 36 | #import "M3IndexSetEnumerator.h" 37 | 38 | @interface M3OutlineView () 39 | 40 | @property (readwrite, copy) NSIndexSet *previousSelection; 41 | @property (retain) NSIndexSet *currentSelection; 42 | 43 | - (NSIndexSet *)adjustRowIndexes:(NSIndexSet *)indexset forChildrenOfItem:(id)item up:(BOOL)up; 44 | - (NSArray *)childrenForItem:(id)item; 45 | 46 | @end 47 | 48 | 49 | @implementation M3OutlineView 50 | 51 | @synthesize previousSelection; 52 | @synthesize currentSelection; 53 | 54 | - (id)initWithCoder:(NSCoder *)aDecoder { 55 | if ((self = [super initWithCoder:aDecoder])) { 56 | previousSelection = [[NSIndexSet alloc] init]; 57 | currentSelection = [[NSIndexSet alloc] init]; 58 | [[NSNotificationCenter defaultCenter] addObserver:self 59 | selector:@selector(updatePreviousSelection:) 60 | name:NSOutlineViewSelectionDidChangeNotification 61 | object:self]; 62 | } 63 | return self; 64 | } 65 | 66 | -(id)initWithFrame:(NSRect)frame { 67 | if ((self = [super initWithFrame:frame])) { 68 | previousSelection = [[NSIndexSet alloc] init]; 69 | currentSelection = [[NSIndexSet alloc] init]; 70 | [[NSNotificationCenter defaultCenter] addObserver:self 71 | selector:@selector(updatePreviousSelection:) 72 | name:NSOutlineViewSelectionDidChangeNotification 73 | object:self]; 74 | } 75 | return self; 76 | } 77 | 78 | - (void)dealloc { 79 | [previousSelection release]; 80 | [currentSelection release]; 81 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 82 | [super dealloc]; 83 | } 84 | 85 | 86 | - (id )delegate { 87 | return (id )[super delegate]; 88 | } 89 | 90 | - (void)setDelegate:(id )aDelegate { 91 | [super setDelegate:aDelegate]; 92 | } 93 | 94 | 95 | - (id )dataSource { 96 | return (id )[super delegate]; 97 | } 98 | 99 | - (void)setDataSource:(id )aDataSource { 100 | [super setDataSource:aDataSource]; 101 | } 102 | 103 | 104 | 105 | 106 | 107 | - (void)updatePreviousSelection:(NSNotification *)note { 108 | [self setPreviousSelection:currentSelection]; 109 | [self setCurrentSelection:[self selectedRowIndexes]]; 110 | } 111 | 112 | - (void)keyDown:(NSEvent *)theEvent { 113 | //Handle keyboard re-arrange 114 | if (([theEvent keyCode] == 125 || [theEvent keyCode] == 126) && ([theEvent modifierFlags] & NSAlternateKeyMask && [theEvent modifierFlags] & NSShiftKeyMask)) { 115 | //No point doing all this work if we don't even respond to the selector, is there? 116 | if ([[self dataSource] respondsToSelector:@selector(outlineView:moveChildrenOfItem:atIndexes:toIndexes:)]) { 117 | NSMutableArray *items = [NSMutableArray arrayWithObject:@"nil"]; 118 | NSMutableArray *selectedItems = [NSMutableArray array]; 119 | NSMutableArray *indexes = [NSMutableArray arrayWithObject:[NSMutableIndexSet indexSet]]; 120 | 121 | M3IndexSetEnumerator *indexEnum = [M3IndexSetEnumerator enumeratorWithIndexSet:[self selectedRowIndexes]]; 122 | NSUInteger index; 123 | 124 | //Loop the indexes 125 | while ((index = [indexEnum nextIndex]) != NSNotFound) { 126 | 127 | id item = [self parentForItem:[self itemAtRow:index]]; //Get the parent for the current item 128 | [selectedItems addObject:[self itemAtRow:index]]; //Add the current item to an array for calculating selection later 129 | NSArray *children = [self childrenForItem:item]; //Get the children for the item 130 | index = [children indexOfObject:[self itemAtRow:index]]; //So we can get the index of the item within the children 131 | 132 | //If item is nil then we're at the root so add the index to the nil group 133 | if (!item) { 134 | [[indexes objectAtIndex:0] addIndex:index]; 135 | } else { 136 | //If this is the first child of the parent then we need to initalise the index set 137 | if ([items indexOfObject:item] == NSNotFound) { 138 | [items addObject:item]; 139 | [indexes addObject:[NSMutableIndexSet indexSet]]; 140 | } 141 | [[indexes objectAtIndex:[items indexOfObject:item]] addIndex:index]; 142 | } 143 | } 144 | 145 | 146 | //Loop through all the parent items 147 | for (id item in items) { 148 | //Get the current indexes 149 | NSIndexSet *currentRowIndexes = [indexes objectAtIndex:[items indexOfObject:item]]; 150 | if ([currentRowIndexes count]) { 151 | //We have to store the string "nil" 152 | if ([item isEqual:@"nil"]) { 153 | item = nil; 154 | } 155 | //Get what we think the new rows should be 156 | NSIndexSet *suggestedRowIndexes = [self adjustRowIndexes:currentRowIndexes forChildrenOfItem:item up:[theEvent keyCode] == 126]; 157 | BOOL move = YES; 158 | //Check if we should move for this item 159 | if ([[self dataSource] respondsToSelector:@selector(outlineView:validateMoveChildrenOfItem:atIndexes:toIndexes:)]) { 160 | move = [[self dataSource] outlineView:self validateMoveChildrenOfItem:item atIndexes:currentRowIndexes toIndexes:suggestedRowIndexes]; 161 | } 162 | //If we do them tell the data source to move them (if it doesn't nasty things will happen! mwhahahaha) 163 | if (move) { 164 | [[self dataSource] outlineView:self moveChildrenOfItem:item atIndexes:currentRowIndexes toIndexes:suggestedRowIndexes]; 165 | } 166 | } 167 | } 168 | 169 | //Build an index set of rows to be selected 170 | NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; 171 | for (id item in selectedItems) { 172 | [indexSet addIndex:[self rowForItem:item]]; 173 | } 174 | 175 | //If there are any to be selected then select them, otherwise beep to let the user know this action isn't allowed 176 | if ([indexSet count]) { 177 | [self selectRowIndexes:indexSet byExtendingSelection:NO]; 178 | } else { 179 | NSBeep(); 180 | } 181 | } 182 | return; 183 | } 184 | 185 | if ([[self delegate] respondsToSelector:@selector(outlineView:didReceiveKeyPressWithCode:)]) { 186 | [[self delegate] outlineView:self didReceiveKeyPressWithCode:[theEvent keyCode]]; 187 | } 188 | 189 | BOOL performKeyDown = YES; 190 | if ([[self delegate] respondsToSelector:@selector(outlineView:shouldIgnoreKeyPressWithCode:)]) { 191 | performKeyDown = ![[self delegate] outlineView:self shouldIgnoreKeyPressWithCode:[theEvent keyCode]]; 192 | } 193 | 194 | if (performKeyDown) { 195 | [super keyDown:theEvent]; 196 | } 197 | } 198 | 199 | - (NSIndexSet *)adjustRowIndexes:(NSIndexSet *)indexset forChildrenOfItem:(id)item up:(BOOL)up { 200 | NSMutableIndexSet *adjustedIndexSet = [[NSMutableIndexSet alloc] init]; 201 | NSUInteger numberOfRows = [[self dataSource] outlineView:self numberOfChildrenOfItem:item]; 202 | //If we're moving down 203 | if (!up) { 204 | M3IndexSetEnumerator *enumerator = [M3IndexSetEnumerator reverseEnumeratorWithIndexSet:indexset]; 205 | NSUInteger index; 206 | //Loop through the indexes and update 207 | while ((index = [enumerator nextIndex]) != NSNotFound) { 208 | NSUInteger newIndex = index + 1; 209 | //If we are at the highest index or the next index already exists then don't move 210 | if (newIndex >= numberOfRows || [adjustedIndexSet containsIndex:newIndex]) { 211 | newIndex = index; 212 | } 213 | [adjustedIndexSet addIndex:newIndex]; 214 | } 215 | //If we're moving up 216 | } else { 217 | M3IndexSetEnumerator *enumerator = [M3IndexSetEnumerator enumeratorWithIndexSet:indexset]; 218 | NSUInteger index; 219 | //Loop through the indexes and update 220 | while ((index = [enumerator nextIndex]) != NSNotFound) { 221 | NSInteger newIndex = index - 1; 222 | //If we are at the lowest index or the next index already exists then don't move 223 | if (newIndex < 0 || [adjustedIndexSet containsIndex:newIndex]) { 224 | newIndex = index; 225 | } 226 | [adjustedIndexSet addIndex:newIndex]; 227 | } 228 | } 229 | return [adjustedIndexSet autorelease]; 230 | } 231 | 232 | - (NSArray *)childrenForItem:(id)item { 233 | NSMutableArray *items = [NSMutableArray array]; 234 | NSUInteger numberOfRows = [[self dataSource] outlineView:self numberOfChildrenOfItem:item]; 235 | NSUInteger i = 0; 236 | for (i = 0; i < numberOfRows; i++) { 237 | [items addObject:[[self dataSource] outlineView:self child:i ofItem:item]]; 238 | } 239 | return [[items copy] autorelease]; 240 | } 241 | 242 | - (NSMenu *)menuForEvent:(NSEvent *)theEvent { 243 | if ([theEvent type] == NSRightMouseDown) { 244 | NSIndexSet *selectedIndexes = [self selectedRowIndexes]; 245 | NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 246 | NSInteger row = [self rowAtPoint:mousePoint]; 247 | BOOL shouldSelectRow = YES; 248 | if ([[self delegate] respondsToSelector:@selector(outlineView:shouldSelectItem:)] && ![selectedIndexes containsIndex:row]) { 249 | shouldSelectRow = [[self delegate] outlineView:self shouldSelectItem:[self itemAtRow:row]]; 250 | } 251 | if (row >= 0 && shouldSelectRow) { 252 | if (![selectedIndexes containsIndex:row]) { 253 | [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; 254 | } 255 | if ([[self delegate] respondsToSelector:@selector(contextMenuForOutlineView:)]) { 256 | NSMenu *menu = [[self delegate] contextMenuForOutlineView:self]; 257 | if (menu) { 258 | return menu; 259 | } 260 | } 261 | return [self menu]; 262 | } else { 263 | // you can disable this if you don't want clicking on an empty space to deselect all rows 264 | [self deselectAll:self]; 265 | } 266 | } 267 | return nil; 268 | } 269 | 270 | @end -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3OutlineViewDataSource.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3OutlineViewDataSource.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3OutlineView; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3OutlineViewDataSource 42 | 43 | @optional 44 | /** 45 | Asks the data source whether the move should be allowed to go ahead 46 | @param outlineView The M3OutlineView sending the message 47 | @param currentIndexes The index set of the currently selected rows 48 | @prarm suggestedIndexes The index set of where the outline view has moved these indexes 49 | If this method isn't implemented, the the outline view always assumes that all moves are valid 50 | @return YES if the move should go ahead, NO if it should be cancelled 51 | @since Available in M3AppKit 1.0 and later 52 | */ 53 | - (BOOL)outlineView:(M3OutlineView *)anOutlineView validateMoveChildrenOfItem:(id)item atIndexes:(NSIndexSet *)currentIndexes toIndexes:(NSIndexSet *)suggestedIndexes; 54 | 55 | /** 56 | Tells the data source to move the rows to new indexes 57 | @param outlineView The M3OutlineView sending the message 58 | @param currentIndexes The index set of the currently selected rows 59 | @prarm newIndexes The index set of where the data source should move these rows 60 | @since Available in M3AppKit 1.0 and later 61 | */ 62 | - (void)outlineView:(M3OutlineView *)anOutlineView moveChildrenOfItem:(id)item atIndexes:(NSIndexSet *)currentIndexes toIndexes:(NSIndexSet *)newIndexes; 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3OutlineViewDelegate.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3OutlineViewDelegate.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3OutlineView; 35 | 36 | 37 | /** 38 | @protocol PROTOCOL_HERE 39 | DESCRIPTION_HERE 40 | @since Available in M3AppKit 1.0 and later 41 | */ 42 | @protocol M3OutlineViewDelegate 43 | 44 | @optional 45 | /** 46 | Asks the delegate for a menu to use when the right mouse button is clicked 47 | @param outlineView The M3OutlineView sending the message 48 | @result An NSMenu object to use as a context menu when the right mouse button is clicked 49 | @since Available in M3AppKit 1.0 and later 50 | */ 51 | - (NSMenu *)contextMenuForOutlineView:(M3OutlineView *)outlineView; 52 | 53 | /** 54 | Informs the delegate that a key was pressed on the outline 55 | @param outlineView The M3OutlineView sending the message 56 | @param code The virtual key code of the key pressed 57 | @since Available in M3AppKit 1.0 and later 58 | */ 59 | - (void)outlineView:(M3OutlineView *)anOutlineView didReceiveKeyPressWithCode:(unsigned short)code; 60 | 61 | /** 62 | Asks the delegate whether a pressed key should be ignored (often to be handled by the delegate) 63 | @param outlineView The M3OutlineView sending the message 64 | @param code The virtual key code of the key pressed 65 | @return YES if the outline view should ignore the keypress or NO if it should handle the key press itself 66 | @since Available in M3AppKit 1.0 and later 67 | */ 68 | - (BOOL)outlineView:(M3OutlineView *)anOutlineView shouldIgnoreKeyPressWithCode:(unsigned short)code; 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3SectionedTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // M3SectionedTableView.h 3 | // Lighthouse Keeper 4 | // 5 | // Created by Martin Pilkington on 17/06/2011. 6 | // Copyright 2011 M Cubed Software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "M3TableView.h" 11 | 12 | @protocol M3SectionedTableViewDelegate, M3SectionedTableViewDataSource; 13 | 14 | @interface M3SectionedTableView : M3TableView { 15 | 16 | } 17 | 18 | @property (assign) IBOutlet idsectionedDataSource; 19 | @property (assign) IBOutlet idsectionedDelegate; 20 | 21 | - (NSInteger)numberOfSections; 22 | - (NSInteger)numberOfRowsInSection:(NSUInteger)aSection; 23 | 24 | - (void)scrollRowAtIndexPathToVisible:(NSIndexPath *)aPath; 25 | 26 | - (void)reloadDataForRowsAtIndexPaths:(NSArray *)aRowIndexPaths columnIndexes:(NSIndexSet *)aColumnIndexes; 27 | 28 | - (NSIndexPath *)indexPathForClickedRow; 29 | 30 | //- (BOOL)canDragRowsAtIndexPaths:(NSArray *)aRowIndexPaths atPoint:(NSPoint)aMouseDownPoint; 31 | //- (NSImage *)dragImageForRowsAtIndexPaths:(NSArray *)aDragRows tableColumns:(NSArray *)aTableColumns event:(NSEvent *)aDragEvent offset:(NSPointPointer)aDragImageOffset; 32 | //- (void)setDropRowAtIndexPath:(NSIndexPath *)aRow dropOperation:(NSTableViewDropOperation)aDropOperation; 33 | 34 | - (void)selectRowsAtIndexPaths:(NSArray *)aIndexPaths byExtendingSelection:(BOOL)aExtend; 35 | - (NSArray *)indexPathsForSelectedRows; 36 | - (void)deselectRowAtIndexPath:(NSIndexPath *)aRow; 37 | - (NSIndexPath *)indexPathForSelectedRow; 38 | //- (BOOL)isRowAtIndexPathSelected:(NSIndexPath *)aRow; 39 | 40 | //- (NSRect)rectForRowAtIndexPath:(NSIndexPath *)aRow; 41 | - (NSArray *)indexPathsForRowsInRect:(NSRect)aRect; 42 | //- (NSIndexPath *)indexPathForRowAtPoint:(NSPoint)aPoint; 43 | 44 | //- (NSRect)frameOfCellAtColumn:(NSInteger)aColumn rowAtIndexPath:(NSIndexPath *)aRow; 45 | 46 | //- (void)editColumn:(NSInteger)aColumn rowAtIndexPath:(NSIndexPath *)aRow withEvent:(NSEvent *)aEvent select:(BOOL)aSelect; 47 | 48 | - (id)viewAtColumn:(NSInteger)aColumn rowAtIndexPath:(NSIndexPath *)aRow makeIfNecessary:(BOOL)aMakeIfNecessary; 49 | - (id)rowViewAtIndexPath:(NSIndexPath *)aRow makeIfNecessary:(BOOL)aMakeIfNecessary; 50 | 51 | - (NSIndexPath *)indexPathForView:(NSView *)view; 52 | 53 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 54 | 55 | - (void)insertRowsAtIndexPaths:(NSArray *)aIndexPaths withAnimation:(NSTableViewAnimationOptions)animationOptions; 56 | - (void)removeRowsAtIndexPaths:(NSArray *)aIndexPaths withAnimation:(NSTableViewAnimationOptions)animationOptions; 57 | - (void)moveRowAtIndexPath:(NSIndexPath *)aOldIndexPath toIndexPath:(NSIndexPath *)aNewIndexPath; 58 | 59 | #endif 60 | 61 | @end 62 | 63 | 64 | 65 | 66 | 67 | 68 | @protocol M3SectionedTableViewDelegate 69 | 70 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 71 | @optional 72 | - (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRowAtIndexPath:(NSIndexPath *)aRow; 73 | 74 | - (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRowAtIndexPath:(NSIndexPath *)aRow; 75 | - (void)tableView:(NSTableView *)tableView didRemoveRowView:(NSTableRowView *)rowView forRowAtIndexPath:(NSIndexPath *)aRow; 76 | 77 | #endif 78 | 79 | //- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row; 80 | 81 | //- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation; 82 | 83 | - (BOOL)tableView:(NSTableView *)tableView shouldSelectRowAtIndexPath:(NSIndexPath *)aRow; 84 | 85 | //- (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes; 86 | 87 | - (CGFloat)tableView:(NSTableView *)tableView heightOfRowAtIndexPath:(NSIndexPath *)aRow; 88 | - (CGFloat)tableView:(NSTableView *)tableView heightOfHeaderForSection:(NSUInteger)aSection; 89 | 90 | //- (NSString *)tableView:(NSTableView *)tableView typeSelectStringForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row; 91 | //- (NSInteger)tableView:(NSTableView *)tableView nextTypeSelectMatchFromRow:(NSInteger)startRow toRow:(NSInteger)endRow forString:(NSString *)searchString; 92 | 93 | //- (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row; 94 | 95 | @end 96 | 97 | 98 | 99 | 100 | 101 | @protocol M3SectionedTableViewDataSource 102 | 103 | @required 104 | - (NSInteger)numberOfSectionsInTableView:(NSTableView *)aTableView; 105 | - (NSInteger)tableView:(NSTableView *)aTableView numberOfRowsInSection:(NSInteger)aSection; 106 | - (NSView *)tableView:(NSTableView *)aTableView viewForTableColumn:(NSTableColumn *)aTableColumn rowAtIndexPath:(NSIndexPath *)aRow; 107 | - (NSView *)tableView:(NSTableView *)aTableView headerViewForTableColumn:(NSTableColumn *)aTableColumn section:(NSUInteger)aSection; 108 | 109 | @optional 110 | - (BOOL)tableView:(NSTableView *)aTableView shouldDisplayHeaderRowForSection:(NSUInteger)aSection; 111 | 112 | //- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors; 113 | 114 | //- (id )tableView:(NSTableView *)tableView pasteboardWriterForRow:(NSInteger)row; 115 | 116 | //- (void)tableView:(NSTableView *)tableView draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint forRowIndexes:(NSIndexSet *)rowIndexes; 117 | //- (void)tableView:(NSTableView *)tableView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation; 118 | //- (void)tableView:(NSTableView *)tableView updateDraggingItemsForDrag:(id )draggingInfo; 119 | 120 | 121 | //- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; 122 | - (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id )info proposedIndexPath:(NSIndexPath *)aIndexPath proposedDropOperation:(NSTableViewDropOperation)dropOperation; 123 | - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id )info indexPath:(NSIndexPath *)aIndexPath dropOperation:(NSTableViewDropOperation)dropOperation; 124 | 125 | //- (NSArray *)tableView:(NSTableView *)tableView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)indexSet; 126 | 127 | @end 128 | 129 | 130 | 131 | 132 | 133 | @interface NSIndexPath (M3SectionedTableViewExtensions) 134 | 135 | + (NSIndexPath *)indexPathForRow:(NSUInteger)row inSection:(NSUInteger)section; 136 | 137 | @property (readonly) NSUInteger row; 138 | @property (readonly) NSUInteger section; 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3TableView.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TableView.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 07/05/2007. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | #ifdef M3APPKIT_IB_BUILD 35 | #import "M3IndexSetEnumerator.h" 36 | #else 37 | #import 38 | #endif 39 | 40 | 41 | @protocol M3TableViewDelegate, M3TableViewDataSource; 42 | 43 | /** 44 | @class M3TableView 45 | Adds delegate methods for dealing with key presses and context menus to NSTableView, as well as the option to get the previous selection index set 46 | @since Available in M3AppKit 1.0 and later 47 | */ 48 | @interface M3TableView : NSTableView { 49 | NSIndexSet *previousSelection; 50 | NSIndexSet *currentSelection; 51 | NSIndexSet *stickySelectionIndexes; 52 | BOOL allowsStickySelection; 53 | NSInteger selectedRow; 54 | } 55 | 56 | /** 57 | @property PROPERTY_NAME 58 | ABSTRACT_HERE 59 | @since Available in M3AppKit 1.0 and later 60 | */ 61 | @property (assign) id delegate; 62 | 63 | /** 64 | @property PROPERTY_NAME 65 | ABSTRACT_HERE 66 | @since Available in M3AppKit 1.0 and later 67 | */ 68 | @property (assign) id dataSource; 69 | 70 | /** 71 | @property PROPERTY_NAME 72 | ABSTRACT_HERE 73 | @since Available in M3AppKit 1.0 and later 74 | */ 75 | @property (readonly, copy) NSIndexSet *previousSelection; 76 | 77 | /** 78 | @property PROPERTY_NAME 79 | ABSTRACT_HERE 80 | @since Available in M3AppKit 1.0 and later 81 | */ 82 | @property (copy) NSIndexSet *stickySelectionIndexes; 83 | 84 | /** 85 | @property PROPERTY_NAME 86 | ABSTRACT_HERE 87 | @since Available in M3AppKit 1.0 and later 88 | */ 89 | @property (assign) BOOL allowsStickySelection; 90 | 91 | /** 92 | @property PROPERTY_NAME 93 | ABSTRACT_HERE 94 | @since Available in M3AppKit 1.0 and later 95 | */ 96 | @property (readonly) NSInteger selectedRow; 97 | 98 | 99 | /** 100 | Adds the supplied rows to the list of sticky rows 101 | @param rowIndexes The rows to make sticky 102 | @since Available in M3AppKit 1.0 and later 103 | */ 104 | - (void)addStickyRowIndexes:(NSIndexSet *)rowIndexes; 105 | 106 | /** 107 | Removes the supplied rows from the list of sticky rows 108 | @param rowIndexes The rows to unstick 109 | @since Available in M3AppKit 1.0 and later 110 | */ 111 | - (void)removeStickyRowIndexes:(NSIndexSet *)rowIndexes; 112 | 113 | /** 114 | Deselects all rows that aren't sticky 115 | @param sender The sender of the call 116 | @since Available in M3AppKit 1.0 and later 117 | */ 118 | 119 | - (IBAction)deselectAllNonStickyRows:(id)sender; 120 | 121 | /** 122 | Clears all the sticky rows 123 | @param sender The sender of the call 124 | This method does not deselect the rows, to deselect all rows then use -(void)deselectAll:(id)sender 125 | @since Available in M3AppKit 1.0 and later 126 | */ 127 | - (IBAction)clearAllStickyRows:(id)sender; 128 | 129 | /** 130 | Returns a boolean value to indicate whether the row at the supplied index is sticky 131 | @param rowIndex The index of the row to test 132 | @return YES if the supplied row is sticky, otherwise NO 133 | @since Available in M3AppKit 1.0 and later 134 | */ 135 | - (BOOL)isRowSticky:(NSInteger)rowIndex; 136 | 137 | @end -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3TableView.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TableView.m 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 07/05/2007. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3TableView.h" 33 | 34 | #import "M3TableViewDelegate.h" 35 | #import "M3TableViewDataSource.h" 36 | #import "M3IndexSetEnumerator.h" 37 | 38 | @interface M3TableView () 39 | 40 | @property (readwrite, copy) NSIndexSet *previousSelection; 41 | @property (retain) NSIndexSet *currentSelection; 42 | @property (readwrite, assign) NSInteger selectedRow; 43 | 44 | - (NSIndexSet *)adjustRowIndexes:(NSIndexSet *)indexset up:(BOOL)up; 45 | - (void)shiftRows:(NSIndexSet *)rows up:(BOOL)up; 46 | - (void)collectRows:(NSIndexSet *)rows atIndex:(NSInteger)newIndex; 47 | - (void)makeRowsSticky:(NSIndexSet *)indexes; 48 | 49 | @end 50 | 51 | 52 | @implementation M3TableView 53 | 54 | @synthesize previousSelection; 55 | @synthesize currentSelection; 56 | @synthesize allowsStickySelection; 57 | @synthesize stickySelectionIndexes; 58 | @synthesize selectedRow; 59 | 60 | #pragma mark - 61 | #pragma mark Initialisation 62 | 63 | /** 64 | Set everything up 65 | */ 66 | - (id)initWithCoder:(NSCoder *)aDecoder { 67 | if ((self = [super initWithCoder:aDecoder])) { 68 | previousSelection = [[NSIndexSet alloc] init]; 69 | currentSelection = [[NSIndexSet alloc] init]; 70 | stickySelectionIndexes = [[NSIndexSet alloc] init]; 71 | [[NSNotificationCenter defaultCenter] addObserver:self 72 | selector:@selector(updatePreviousSelection:) 73 | name:NSTableViewSelectionDidChangeNotification 74 | object:self]; 75 | } 76 | return self; 77 | } 78 | 79 | -(id)initWithFrame:(NSRect)frame { 80 | if ((self = [super initWithFrame:frame])) { 81 | previousSelection = [[NSIndexSet alloc] init]; 82 | currentSelection = [[NSIndexSet alloc] init]; 83 | stickySelectionIndexes = [[NSIndexSet alloc] init]; 84 | [[NSNotificationCenter defaultCenter] addObserver:self 85 | selector:@selector(updatePreviousSelection:) 86 | name:NSTableViewSelectionDidChangeNotification 87 | object:self]; 88 | } 89 | return self; 90 | } 91 | 92 | /** 93 | Bring everything down 94 | */ 95 | - (void)dealloc { 96 | [previousSelection release]; 97 | [currentSelection release]; 98 | [stickySelectionIndexes release]; 99 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 100 | [super dealloc]; 101 | } 102 | 103 | 104 | 105 | - (id )delegate { 106 | return (id )[super delegate]; 107 | } 108 | 109 | - (void)setDelegate:(id )aDelegate { 110 | [super setDelegate:aDelegate]; 111 | } 112 | 113 | 114 | - (id )dataSource { 115 | return (id )[super dataSource]; 116 | } 117 | 118 | - (void)setDataSource:(id )aDataSource { 119 | [super setDataSource:aDataSource]; 120 | } 121 | 122 | 123 | 124 | #pragma mark - 125 | #pragma mark Selection 126 | 127 | /** 128 | Remove all stick indexes 129 | */ 130 | - (IBAction)deselectAll:(id)sender { 131 | [self setStickySelectionIndexes:[NSIndexSet indexSet]]; 132 | [self selectRowIndexes:nil byExtendingSelection:NO]; 133 | } 134 | 135 | /** 136 | Remove the row from a sticky index 137 | */ 138 | - (void)deselectRow:(NSInteger)rowIndex { 139 | [self removeStickyRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex]]; 140 | [super deselectRow:rowIndex]; 141 | } 142 | 143 | /** 144 | Selects rows, preserving the selection of sticky rows 145 | */ 146 | - (void)selectRowIndexes:(NSIndexSet *)indexes byExtendingSelection:(BOOL)extend { 147 | [self setSelectedRow:[indexes firstIndex]]; 148 | NSMutableIndexSet *index = [indexes mutableCopy]; 149 | if (allowsStickySelection) { 150 | [index addIndexes:[self stickySelectionIndexes]]; 151 | } 152 | [super selectRowIndexes:index byExtendingSelection:extend]; 153 | [index release]; 154 | } 155 | 156 | /** 157 | Select row, preserving the selection of sticky rows 158 | */ 159 | 160 | - (void)selectRow:(NSInteger)rowIndex byExtendingSelection:(BOOL)extend { 161 | [self setSelectedRow:rowIndex]; 162 | NSMutableIndexSet *index = [NSMutableIndexSet indexSetWithIndex:rowIndex]; 163 | if (allowsStickySelection) { 164 | [index addIndexes:[self stickySelectionIndexes]]; 165 | } 166 | [super selectRowIndexes:index byExtendingSelection:extend]; 167 | } 168 | 169 | /** 170 | Update the current selection (which we need to handle manually now) for a mouse down 171 | */ 172 | - (void)mouseDown:(NSEvent *)theEvent { 173 | if ([[self window] isKeyWindow]) { 174 | NSPoint point = [self convertPointFromBase:[theEvent locationInWindow]]; 175 | [self setSelectedRow:[self rowAtPoint:point]]; 176 | } 177 | [super mouseDown:theEvent]; 178 | } 179 | 180 | /** 181 | Updates the previous selection index set 182 | */ 183 | - (void)updatePreviousSelection:(NSNotification *)note { 184 | [self setPreviousSelection:currentSelection]; 185 | [self setCurrentSelection:[self selectedRowIndexes]]; 186 | } 187 | 188 | 189 | 190 | #pragma mark - 191 | #pragma mark New keyboard events 192 | 193 | - (void)keyDown:(NSEvent *)theEvent { 194 | //Handle keyboard re-arrange 195 | if ([theEvent keyCode] == 125 || [theEvent keyCode] == 126) { 196 | //Move rows 197 | if ([theEvent modifierFlags] & NSAlternateKeyMask && [theEvent modifierFlags] & NSShiftKeyMask) { 198 | [self shiftRows:[self selectedRowIndexes] up:[theEvent keyCode] == 126]; 199 | return; 200 | //Collect rows 201 | } else if ([theEvent modifierFlags] & NSControlKeyMask && [theEvent modifierFlags] & NSShiftKeyMask) { 202 | NSIndexSet *selected = [self selectedRowIndexes]; 203 | [self collectRows:selected atIndex:[theEvent keyCode] == 126 ? [selected firstIndex] : ([selected lastIndex] + 1)]; 204 | return; 205 | } 206 | } 207 | 208 | //Sticky rows 209 | if ([theEvent keyCode] == 49 && [theEvent modifierFlags] & NSAlternateKeyMask && [theEvent modifierFlags] & NSShiftKeyMask && allowsStickySelection) { 210 | [self makeRowsSticky:[self selectedRowIndexes]]; 211 | return; 212 | } 213 | 214 | //Did get keypress 215 | if ([[self delegate] respondsToSelector:@selector(tableView:didReceiveKeyPressWithCode:)]) { 216 | [[self delegate] tableView:self didReceiveKeyPressWithCode:[theEvent keyCode]]; 217 | } 218 | 219 | //Should ignore it 220 | BOOL performKeyDown = YES; 221 | if ([[self delegate] respondsToSelector:@selector(tableView:shouldIgnoreKeyPressWithCode:)]) { 222 | performKeyDown = ![[self delegate] tableView:self shouldIgnoreKeyPressWithCode:[theEvent keyCode]]; 223 | } 224 | 225 | //If we should accept it then do the standard action 226 | if (performKeyDown) { 227 | [super keyDown:theEvent]; 228 | } 229 | } 230 | 231 | #pragma mark - 232 | #pragma mark Shift Rows 233 | 234 | /** 235 | Shifts the supplied rows up or down 236 | */ 237 | - (void)shiftRows:(NSIndexSet *)rows up:(BOOL)up { 238 | NSIndexSet *suggestedRowIndexes = [self adjustRowIndexes:rows up:up]; 239 | BOOL move = YES; 240 | //Check if delegate lets us move 241 | if ([[self delegate] respondsToSelector:@selector(tableView:shouldMoveFromIndexes:toIndexes:)]) { 242 | move = [[self delegate] tableView:self shouldMoveFromIndexes:rows toIndexes:suggestedRowIndexes]; 243 | } 244 | //If we can move and the data source works then move them and sort out the new selection 245 | if (move && [[self dataSource] respondsToSelector:@selector(tableView:moveRowsAtIndexes:toIndexes:)]) { 246 | [[self dataSource] tableView:self moveRowsAtIndexes:rows toIndexes:suggestedRowIndexes]; 247 | [self deselectAll:self]; 248 | [self selectRowIndexes:suggestedRowIndexes byExtendingSelection:NO]; 249 | } 250 | if (!move) { 251 | NSBeep(); 252 | } 253 | } 254 | 255 | /** 256 | Calculates the new indexes for the shift 257 | */ 258 | - (NSIndexSet *)adjustRowIndexes:(NSIndexSet *)indexset up:(BOOL)up { 259 | NSMutableIndexSet *adjustedIndexSet = [[NSMutableIndexSet alloc] init]; 260 | NSUInteger numberOfRows = [[self dataSource] numberOfRowsInTableView:self]; 261 | //Move rows down 262 | if (!up) { 263 | M3IndexSetEnumerator *enumerator = [M3IndexSetEnumerator reverseEnumeratorWithIndexSet:indexset]; 264 | NSUInteger index; 265 | while ((index = [enumerator nextIndex]) != NSNotFound) { 266 | NSUInteger newIndex = index + 1; 267 | //If we're at the bottom then don't shift 268 | if (newIndex >= numberOfRows || [adjustedIndexSet containsIndex:newIndex]) { 269 | newIndex = index; 270 | } 271 | [adjustedIndexSet addIndex:newIndex]; 272 | } 273 | //Move rows up 274 | } else { 275 | M3IndexSetEnumerator *enumerator = [M3IndexSetEnumerator enumeratorWithIndexSet:indexset]; 276 | NSUInteger index; 277 | while ((index = [enumerator nextIndex]) != NSNotFound) { 278 | NSInteger newIndex = index - 1; 279 | //If we're at the top then don't shift 280 | if (newIndex < 0 || [adjustedIndexSet containsIndex:newIndex]) { 281 | newIndex = index; 282 | } 283 | [adjustedIndexSet addIndex:newIndex]; 284 | } 285 | } 286 | return [adjustedIndexSet autorelease]; 287 | } 288 | 289 | 290 | #pragma mark - 291 | #pragma mark Collect Rows 292 | 293 | /** 294 | Collects the supplied rows at newIndex 295 | */ 296 | - (void)collectRows:(NSIndexSet *)rows atIndex:(NSInteger)newIndex { 297 | BOOL collect = YES; 298 | //Check if the delegate says we shouldn't work 299 | if ([[self delegate] respondsToSelector:@selector(tableView:shouldCollectRowsAtIndexes:atNewIndex:)]) { 300 | collect = [[self delegate] tableView:self shouldCollectRowsAtIndexes:rows atNewIndex:newIndex]; 301 | } 302 | //If we can collect and the data source implements it then collect and sort out the new welection 303 | if (collect && [[self dataSource] respondsToSelector:@selector(tableView:collectRowsAtIndexes:atNewIndex:)]) { 304 | [[self dataSource] tableView:self collectRowsAtIndexes:rows atNewIndex:newIndex]; 305 | [self deselectAll:self]; 306 | [self selectRowIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(newIndex - (newIndex == [rows lastIndex] + 1 ? [rows count] : 0), [rows count])] byExtendingSelection:NO]; 307 | } 308 | if (!collect) { 309 | NSBeep(); 310 | } 311 | } 312 | 313 | 314 | #pragma mark - 315 | #pragma mark Sticky Rows 316 | 317 | /** 318 | Makes the supplied rows sticky 319 | */ 320 | - (void)makeRowsSticky:(NSIndexSet *)indexes { 321 | if ([self isRowSticky:[self selectedRow]]) { 322 | [self removeStickyRowIndexes:[NSIndexSet indexSetWithIndex:[self selectedRow]]]; 323 | } else { 324 | [self addStickyRowIndexes:[NSIndexSet indexSetWithIndex:[self selectedRow]]]; 325 | } 326 | } 327 | 328 | 329 | /** 330 | Marks the supplied rows as sticky 331 | */ 332 | - (void)addStickyRowIndexes:(NSIndexSet *)rowIndexes { 333 | if (allowsStickySelection) { 334 | NSMutableIndexSet *index = [[self stickySelectionIndexes] mutableCopy]; 335 | [index addIndexes:rowIndexes]; 336 | [self setStickySelectionIndexes:index]; 337 | [index release]; 338 | } 339 | } 340 | 341 | /** 342 | Unmarks the supplied rows as sticky 343 | */ 344 | - (void)removeStickyRowIndexes:(NSIndexSet *)rowIndexes { 345 | if (allowsStickySelection) { 346 | NSMutableIndexSet *index = [[self stickySelectionIndexes] mutableCopy]; 347 | [index removeIndexes:rowIndexes]; 348 | [self setStickySelectionIndexes:index]; 349 | [index release]; 350 | } 351 | } 352 | 353 | /** 354 | Checks if the supplied row is sticky 355 | */ 356 | - (BOOL)isRowSticky:(NSInteger)rowIndex { 357 | return [[self stickySelectionIndexes] containsIndex:rowIndex]; 358 | } 359 | 360 | /** 361 | Deselects all rows that aren't sticky 362 | */ 363 | - (IBAction)deselectAllNonStickyRows:(id)sender { 364 | NSIndexSet *indexSet = [[self stickySelectionIndexes] retain]; 365 | [super deselectAll:sender]; 366 | [self setStickySelectionIndexes:indexSet]; 367 | [indexSet release]; 368 | [self selectRowIndexes:indexSet byExtendingSelection:NO]; 369 | } 370 | 371 | /** 372 | Gets rid of all sticky rows 373 | */ 374 | - (IBAction)clearAllStickyRows:(id)sender { 375 | [self setStickySelectionIndexes:[NSIndexSet indexSet]]; 376 | } 377 | 378 | 379 | 380 | #pragma mark - 381 | #pragma mark Context Menu 382 | 383 | /** 384 | Asks the delegate to provide a context menu for the table, selecting the appropriate row 385 | */ 386 | - (NSMenu *)menuForEvent:(NSEvent *)theEvent { 387 | if ([theEvent type] == NSRightMouseDown) { 388 | NSIndexSet *selectedIndexes = [self selectedRowIndexes]; 389 | NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 390 | NSInteger row = [self rowAtPoint:mousePoint]; 391 | BOOL shouldSelectRow = YES; 392 | if ([[self delegate] respondsToSelector:@selector(tableView:shouldSelectRow:)] && ![selectedIndexes containsIndex:row]) { 393 | shouldSelectRow = [[self delegate] tableView:self shouldSelectRow:row]; 394 | } 395 | if (row >= 0 && shouldSelectRow) { 396 | if (![selectedIndexes containsIndex:row]) { 397 | [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; 398 | } 399 | if ([[self delegate] respondsToSelector:@selector(contextMenuForTableView:)]) { 400 | NSMenu *menu = [[self delegate] contextMenuForTableView:self]; 401 | if (menu) { 402 | return menu; 403 | } 404 | } 405 | return [self menu]; 406 | } else { 407 | [self deselectAll:self]; 408 | } 409 | } 410 | return nil; 411 | } 412 | 413 | @end 414 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3TableViewDataSource.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TableViewDataSource.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3TableView; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3TableViewDataSource 42 | 43 | @optional 44 | /** 45 | Tells the data source to move the rows to new indexes 46 | @param tableView The M3TableView sending the message 47 | @param currentIndexes The index set of the currently selected rows 48 | @prarm newIndexes The index set of where the data source should move these rows 49 | @since Available in M3AppKit 1.0 and later 50 | */ 51 | - (void)tableView:(M3TableView *)tableView moveRowsAtIndexes:(NSIndexSet *)currentIndexes toIndexes:(NSIndexSet *)newIndexes; 52 | 53 | /** 54 | Tells the data source to collect the rows at the new index 55 | @param tableView The M3TableView sending the message 56 | @param currentIndexes The index set of the currently selected rows 57 | @prarm newIndex The index where the rows should be inserted 58 | @since Available in M3AppKit 1.0 and later 59 | */ 60 | - (void)tableView:(M3TableView *)tableView collectRowsAtIndexes:(NSIndexSet *)selectedIndexes atNewIndex:(NSInteger)newIndex; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Table & OutlineView enhancements/M3TableViewDelegate.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TableViewDelegate.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3TableView; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3TableViewDelegate 42 | 43 | @optional 44 | /** 45 | Asks the delegate for a menu to use when the right mouse button is clicked 46 | @param tableView The M3TableView sending the message 47 | @result An NSMenu object to use as a context menu when the right mouse button is clicked 48 | @since Available in M3AppKit 1.0 and later 49 | */ 50 | - (NSMenu *)contextMenuForTableView:(M3TableView *)tableView; 51 | 52 | /** 53 | Informs the delegate that a key was pressed on the table 54 | @param tableView The M3TableView sending the message 55 | @param code The virtual key code of the key pressed 56 | @since Available in M3AppKit 1.0 and later 57 | */ 58 | - (void)tableView:(M3TableView *)tableView didReceiveKeyPressWithCode:(unsigned short)code; 59 | 60 | /** 61 | Asks the delegate whether a pressed key should be ignored (often to be handled by the delegate) 62 | @param tableView The M3TableView sending the message 63 | @param code The virtual key code of the key pressed 64 | @return YES if the table view should ignore the keypress or NO if it should handle the key press itself 65 | @since Available in M3AppKit 1.0 and later 66 | */ 67 | - (BOOL)tableView:(M3TableView *)tableView shouldIgnoreKeyPressWithCode:(unsigned short)code; 68 | 69 | /** 70 | Asks the delegate whether the move should be allowed to go ahead 71 | @param tableView The M3TableView sending the message 72 | @param currentIndexes The index set of the currently selected rows 73 | @prarm suggestedIndexes The index set of where the table view has moved these indexes 74 | If this method isn't implemented, the the table view always assumes that all moves are valid 75 | @return YES if the move should go ahead, NO if it should be cancelled 76 | @since Available in M3AppKit 1.0 and later 77 | */ 78 | - (BOOL)tableView:(M3TableView *)tableView shouldMoveFromIndexes:(NSIndexSet *)currentIndexes toIndexes:(NSIndexSet *)suggestedIndexes; 79 | 80 | /** 81 | Asks the delegate whether the row collect should be allowed to go ahead 82 | @param tableView The M3TableView sending the message 83 | @param currentIndexes The index set of the currently selected rows 84 | @prarm newIndex The index where the rows should be inserted 85 | If this method isn't implemented, the the table view always assumes that all row collections are valid 86 | @return YES if the collect should go ahead, NO if it should be cancelled 87 | @since Available in M3AppKit 1.0 and later 88 | */ 89 | - (BOOL)tableView:(M3TableView *)tableView shouldCollectRowsAtIndexes:(NSIndexSet *)currentIndexes atNewIndex:(NSInteger)newIndex; 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /TextViewListExample/README.md: -------------------------------------------------------------------------------- 1 | # TextViewListExample 2 | 3 | This sample project shows how to implement a support for text lists in NSTextView on the Mac, as implemented in [Coppice](https://coppiceapp.com). 4 | 5 | For a more detailed explanation visit the [associated blog post](https://coppiceapp.com/blog/tech_talk_adding_lists_to_coppice) 6 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 993DCDB226179F3E00554C62 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993DCDB126179F3E00554C62 /* AppDelegate.swift */; }; 11 | 993DCDB426179F3F00554C62 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 993DCDB326179F3F00554C62 /* Assets.xcassets */; }; 12 | 993DCDB726179F3F00554C62 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 993DCDB526179F3F00554C62 /* MainMenu.xib */; }; 13 | 993DCDC126179F5C00554C62 /* ListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993DCDC026179F5C00554C62 /* ListController.swift */; }; 14 | 993DCDC426179FA000554C62 /* ListCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993DCDC326179FA000554C62 /* ListCalculator.swift */; }; 15 | 993DCDC726179FCB00554C62 /* NSTextView+M3Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993DCDC626179FCB00554C62 /* NSTextView+M3Extensions.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 993DCDAE26179F3E00554C62 /* TextViewListExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextViewListExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 993DCDB126179F3E00554C62 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 993DCDB326179F3F00554C62 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 22 | 993DCDB626179F3F00554C62 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 23 | 993DCDB826179F3F00554C62 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 993DCDB926179F3F00554C62 /* TextViewListExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TextViewListExample.entitlements; sourceTree = ""; }; 25 | 993DCDC026179F5C00554C62 /* ListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListController.swift; sourceTree = ""; }; 26 | 993DCDC326179FA000554C62 /* ListCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCalculator.swift; sourceTree = ""; }; 27 | 993DCDC626179FCB00554C62 /* NSTextView+M3Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextView+M3Extensions.swift"; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 993DCDAB26179F3E00554C62 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 993DCDA526179F3E00554C62 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 993DCDB026179F3E00554C62 /* TextViewListExample */, 45 | 993DCDAF26179F3E00554C62 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 993DCDAF26179F3E00554C62 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 993DCDAE26179F3E00554C62 /* TextViewListExample.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 993DCDB026179F3E00554C62 /* TextViewListExample */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 993DCDB126179F3E00554C62 /* AppDelegate.swift */, 61 | 993DCDC026179F5C00554C62 /* ListController.swift */, 62 | 993DCDC326179FA000554C62 /* ListCalculator.swift */, 63 | 993DCDC626179FCB00554C62 /* NSTextView+M3Extensions.swift */, 64 | 993DCDB326179F3F00554C62 /* Assets.xcassets */, 65 | 993DCDB526179F3F00554C62 /* MainMenu.xib */, 66 | 993DCDB826179F3F00554C62 /* Info.plist */, 67 | 993DCDB926179F3F00554C62 /* TextViewListExample.entitlements */, 68 | ); 69 | path = TextViewListExample; 70 | sourceTree = ""; 71 | }; 72 | /* End PBXGroup section */ 73 | 74 | /* Begin PBXNativeTarget section */ 75 | 993DCDAD26179F3E00554C62 /* TextViewListExample */ = { 76 | isa = PBXNativeTarget; 77 | buildConfigurationList = 993DCDBC26179F3F00554C62 /* Build configuration list for PBXNativeTarget "TextViewListExample" */; 78 | buildPhases = ( 79 | 993DCDAA26179F3E00554C62 /* Sources */, 80 | 993DCDAB26179F3E00554C62 /* Frameworks */, 81 | 993DCDAC26179F3E00554C62 /* Resources */, 82 | ); 83 | buildRules = ( 84 | ); 85 | dependencies = ( 86 | ); 87 | name = TextViewListExample; 88 | productName = TextViewListExample; 89 | productReference = 993DCDAE26179F3E00554C62 /* TextViewListExample.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | 993DCDA626179F3E00554C62 /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | LastSwiftUpdateCheck = 1240; 99 | LastUpgradeCheck = 1240; 100 | TargetAttributes = { 101 | 993DCDAD26179F3E00554C62 = { 102 | CreatedOnToolsVersion = 12.4; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = 993DCDA926179F3E00554C62 /* Build configuration list for PBXProject "TextViewListExample" */; 107 | compatibilityVersion = "Xcode 9.3"; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 993DCDA526179F3E00554C62; 115 | productRefGroup = 993DCDAF26179F3E00554C62 /* Products */; 116 | projectDirPath = ""; 117 | projectRoot = ""; 118 | targets = ( 119 | 993DCDAD26179F3E00554C62 /* TextViewListExample */, 120 | ); 121 | }; 122 | /* End PBXProject section */ 123 | 124 | /* Begin PBXResourcesBuildPhase section */ 125 | 993DCDAC26179F3E00554C62 /* Resources */ = { 126 | isa = PBXResourcesBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | 993DCDB426179F3F00554C62 /* Assets.xcassets in Resources */, 130 | 993DCDB726179F3F00554C62 /* MainMenu.xib in Resources */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXResourcesBuildPhase section */ 135 | 136 | /* Begin PBXSourcesBuildPhase section */ 137 | 993DCDAA26179F3E00554C62 /* Sources */ = { 138 | isa = PBXSourcesBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | 993DCDC726179FCB00554C62 /* NSTextView+M3Extensions.swift in Sources */, 142 | 993DCDB226179F3E00554C62 /* AppDelegate.swift in Sources */, 143 | 993DCDC426179FA000554C62 /* ListCalculator.swift in Sources */, 144 | 993DCDC126179F5C00554C62 /* ListController.swift in Sources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin PBXVariantGroup section */ 151 | 993DCDB526179F3F00554C62 /* MainMenu.xib */ = { 152 | isa = PBXVariantGroup; 153 | children = ( 154 | 993DCDB626179F3F00554C62 /* Base */, 155 | ); 156 | name = MainMenu.xib; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXVariantGroup section */ 160 | 161 | /* Begin XCBuildConfiguration section */ 162 | 993DCDBA26179F3F00554C62 /* Debug */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | CLANG_ANALYZER_NONNULL = YES; 167 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 168 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 169 | CLANG_CXX_LIBRARY = "libc++"; 170 | CLANG_ENABLE_MODULES = YES; 171 | CLANG_ENABLE_OBJC_ARC = YES; 172 | CLANG_ENABLE_OBJC_WEAK = YES; 173 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 174 | CLANG_WARN_BOOL_CONVERSION = YES; 175 | CLANG_WARN_COMMA = YES; 176 | CLANG_WARN_CONSTANT_CONVERSION = YES; 177 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 178 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 179 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 180 | CLANG_WARN_EMPTY_BODY = YES; 181 | CLANG_WARN_ENUM_CONVERSION = YES; 182 | CLANG_WARN_INFINITE_RECURSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 186 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 188 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 189 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 190 | CLANG_WARN_STRICT_PROTOTYPES = YES; 191 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 192 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 193 | CLANG_WARN_UNREACHABLE_CODE = YES; 194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 195 | COPY_PHASE_STRIP = NO; 196 | DEBUG_INFORMATION_FORMAT = dwarf; 197 | ENABLE_STRICT_OBJC_MSGSEND = YES; 198 | ENABLE_TESTABILITY = YES; 199 | GCC_C_LANGUAGE_STANDARD = gnu11; 200 | GCC_DYNAMIC_NO_PIC = NO; 201 | GCC_NO_COMMON_BLOCKS = YES; 202 | GCC_OPTIMIZATION_LEVEL = 0; 203 | GCC_PREPROCESSOR_DEFINITIONS = ( 204 | "DEBUG=1", 205 | "$(inherited)", 206 | ); 207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 211 | GCC_WARN_UNUSED_FUNCTION = YES; 212 | GCC_WARN_UNUSED_VARIABLE = YES; 213 | MACOSX_DEPLOYMENT_TARGET = 11.1; 214 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 215 | MTL_FAST_MATH = YES; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = macosx; 218 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 220 | }; 221 | name = Debug; 222 | }; 223 | 993DCDBB26179F3F00554C62 /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_ANALYZER_NONNULL = YES; 228 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 230 | CLANG_CXX_LIBRARY = "libc++"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_ENABLE_OBJC_WEAK = YES; 234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_COMMA = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 251 | CLANG_WARN_STRICT_PROTOTYPES = YES; 252 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | COPY_PHASE_STRIP = NO; 257 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 258 | ENABLE_NS_ASSERTIONS = NO; 259 | ENABLE_STRICT_OBJC_MSGSEND = YES; 260 | GCC_C_LANGUAGE_STANDARD = gnu11; 261 | GCC_NO_COMMON_BLOCKS = YES; 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | MACOSX_DEPLOYMENT_TARGET = 11.1; 269 | MTL_ENABLE_DEBUG_INFO = NO; 270 | MTL_FAST_MATH = YES; 271 | SDKROOT = macosx; 272 | SWIFT_COMPILATION_MODE = wholemodule; 273 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 274 | }; 275 | name = Release; 276 | }; 277 | 993DCDBD26179F3F00554C62 /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 281 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 282 | CODE_SIGN_ENTITLEMENTS = TextViewListExample/TextViewListExample.entitlements; 283 | CODE_SIGN_STYLE = Automatic; 284 | COMBINE_HIDPI_IMAGES = YES; 285 | DEVELOPMENT_TEAM = Z699XFM6P2; 286 | ENABLE_HARDENED_RUNTIME = YES; 287 | INFOPLIST_FILE = TextViewListExample/Info.plist; 288 | LD_RUNPATH_SEARCH_PATHS = ( 289 | "$(inherited)", 290 | "@executable_path/../Frameworks", 291 | ); 292 | PRODUCT_BUNDLE_IDENTIFIER = com.mcubedsw.TextViewListExample; 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | SWIFT_VERSION = 5.0; 295 | }; 296 | name = Debug; 297 | }; 298 | 993DCDBE26179F3F00554C62 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 302 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 303 | CODE_SIGN_ENTITLEMENTS = TextViewListExample/TextViewListExample.entitlements; 304 | CODE_SIGN_STYLE = Automatic; 305 | COMBINE_HIDPI_IMAGES = YES; 306 | DEVELOPMENT_TEAM = Z699XFM6P2; 307 | ENABLE_HARDENED_RUNTIME = YES; 308 | INFOPLIST_FILE = TextViewListExample/Info.plist; 309 | LD_RUNPATH_SEARCH_PATHS = ( 310 | "$(inherited)", 311 | "@executable_path/../Frameworks", 312 | ); 313 | PRODUCT_BUNDLE_IDENTIFIER = com.mcubedsw.TextViewListExample; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SWIFT_VERSION = 5.0; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | 993DCDA926179F3E00554C62 /* Build configuration list for PBXProject "TextViewListExample" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | 993DCDBA26179F3F00554C62 /* Debug */, 326 | 993DCDBB26179F3F00554C62 /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | 993DCDBC26179F3F00554C62 /* Build configuration list for PBXNativeTarget "TextViewListExample" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 993DCDBD26179F3F00554C62 /* Debug */, 335 | 993DCDBE26179F3F00554C62 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = 993DCDA626179F3E00554C62 /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TextViewListExample 4 | // 5 | // Created by Martin Pilkington on 02/04/2021. 6 | // 7 | // Please read the LICENCE.txt for licensing information 8 | // 9 | 10 | import Cocoa 11 | 12 | @main 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | @IBOutlet var window: NSWindow! 16 | @IBOutlet var textView: NSTextView! 17 | 18 | lazy var listController: ListController = { 19 | return ListController(textView: self.textView) 20 | }() 21 | 22 | @IBAction func addBulletedList(_ sender: Any) { 23 | self.listController.updateSelection(withListType: NSTextList(markerFormat: .disc, options: 0)) 24 | } 25 | 26 | @IBAction func addNumberedList(_ sender: Any) { 27 | self.listController.updateSelection(withListType: NSTextList(markerFormat: .decimal, options: 0)) 28 | } 29 | 30 | @IBAction func removeList(_ sender: Any) { 31 | self.listController.updateSelection(withListType: nil) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainNibFile 26 | MainMenu 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/ListCalculator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListCalculator.swift 3 | // TextViewListExample 4 | // 5 | // Created by Martin Pilkington on 02/04/2021. 6 | // 7 | // Please read the LICENCE.txt for licensing information 8 | // 9 | 10 | import Cocoa 11 | 12 | class ListCalculator { 13 | private var level: Int? = nil 14 | private var editingRange: NSRange? = nil 15 | 16 | 17 | /// Calculate the total range of lists to edit in a textView, and (in the case of nested lists) the level of list we'll change 18 | /// - Parameter textView: The text view to calculate on 19 | /// - Returns: A tuple with the range (or nil if invalid), and the level of list (which should match the index to edit in all NSParagraphStyle.textLists in the range) 20 | func calculateListRangeAndLevel(in textView: NSTextView) -> (NSRange?, Int) { 21 | guard let textStorage = textView.textStorage else { 22 | return (nil, 0) 23 | } 24 | 25 | let selectedRanges = textView.selectedRanges.compactMap { $0.rangeValue }.filter { ($0.lowerBound <= textStorage.length) && ($0.upperBound <= textStorage.length) } 26 | //For an empty text view, the editing range is (0,0) 27 | if textStorage.length == 0, selectedRanges.count == 1, selectedRanges[0] == NSRange(location: 0, length: 0) { 28 | return (NSRange(location: 0, length: 0), 0) 29 | } 30 | 31 | for range in selectedRanges { 32 | guard range.length > 0 else { 33 | self.handleZeroLengthRange(range, in: textStorage, defaultStyle: textView.defaultParagraphStyle) 34 | continue 35 | } 36 | 37 | textStorage.enumerateAttribute(.paragraphStyle, in: range, options: []) { (attribute, effectiveRange, _) in 38 | guard let paragraphStyle = attribute as? NSParagraphStyle else { 39 | return 40 | } 41 | self.updateLevelAndRange(using: paragraphStyle, effectiveRange: effectiveRange, in: textStorage) 42 | } 43 | } 44 | return (self.editingRange, self.level ?? 0) 45 | } 46 | 47 | 48 | /// Zero length ranges require special treatment, as we can't use NSAttributedString.enumerateAttribute() 49 | private func handleZeroLengthRange(_ range: NSRange, in textStorage: NSTextStorage, defaultStyle: NSParagraphStyle?) { 50 | var effectiveRange = NSRange(location: NSNotFound, length: 0) 51 | 52 | var attribute: Any? = defaultStyle 53 | if (textStorage.length > 0) { 54 | var actualRange = range 55 | //If the cursor is at the end of the text view, we have to shift forward when fetching the attribute to avoid an out of bounds exception 56 | if (actualRange.location == textStorage.length) { 57 | actualRange.location = max(actualRange.location - 1, 0) 58 | } 59 | attribute = textStorage.attribute(.paragraphStyle, at: actualRange.location, effectiveRange: &effectiveRange) 60 | //If there are currently no text lists, then the paragraph style will actually cover multiple paragraphs. In this case we only want the current paragraph 61 | if let attribute = attribute as? NSParagraphStyle { 62 | if attribute.textLists.count == 0 { 63 | effectiveRange = (textStorage.string as NSString).paragraphRange(for: range) 64 | } 65 | } 66 | } 67 | //Perform the update calculation 68 | if let paragraphStyle = attribute as? NSParagraphStyle { 69 | self.updateLevelAndRange(using: paragraphStyle, effectiveRange: effectiveRange, in: textStorage) 70 | } 71 | } 72 | 73 | 74 | /// Actually update the level and range 75 | private func updateLevelAndRange(using paragraphStyle: NSParagraphStyle, effectiveRange: NSRange, in textStorage: NSTextStorage) { 76 | var newRange = effectiveRange 77 | //The level is the index of NSParagraphStyle.textLists we need to edit. This is always the list with the lowest indentation in the selection 78 | //This should be zero indexed so it maps to the .textLists array 79 | if let currentLevel = self.level { 80 | self.level = min(currentLevel, max(paragraphStyle.textLists.count - 1, 0)) 81 | } else { 82 | self.level = max(paragraphStyle.textLists.count - 1, 0) 83 | } 84 | 85 | //If we're in a list then we need to get the full range of the list, which may be outside the selection range 86 | if let list = paragraphStyle.textLists.last { 87 | let listRange = textStorage.range(of: list, at: effectiveRange.location) 88 | if (listRange.location != NSNotFound) { 89 | newRange = listRange 90 | } 91 | } else { 92 | //If we're outside of a list then we just want the current paragraph 93 | newRange = (textStorage.string as NSString).paragraphRange(for: effectiveRange) 94 | } 95 | 96 | //Merge the ranges 97 | guard let editRange = self.editingRange else { 98 | self.editingRange = newRange 99 | return 100 | } 101 | self.editingRange = editRange.union(newRange) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/ListController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListController.swift 3 | // TextViewListExample 4 | // 5 | // Created by Martin Pilkington on 02/04/2021. 6 | // 7 | // Please read the LICENCE.txt for licensing information 8 | // 9 | 10 | import Cocoa 11 | 12 | /// This exists as a separate class just for convenience of transferring from Coppice's code, but could be put into an NSTextView subclass if preferred 13 | class ListController: NSObject { 14 | let textView: NSTextView 15 | init(textView: NSTextView) { 16 | self.textView = textView 17 | } 18 | 19 | func updateSelection(withListType listType: NSTextList?) { 20 | let (range, level) = ListCalculator().calculateListRangeAndLevel(in: self.textView) 21 | guard let editingRange = range else { 22 | return 23 | } 24 | 25 | var selectedLocation = self.textView.selectedRange().location 26 | 27 | self.textView.modifyText(in: [editingRange]) { (textStorage) in 28 | //The end of the text view is a special case, where we just append 29 | guard editingRange.location < textStorage.length else { 30 | if let list = listType { 31 | self.add(list, toEndOf: textStorage) 32 | } 33 | return 34 | } 35 | 36 | //We want an copy of the old string as we need it to calculate the ranges of the old list markers to replace 37 | let oldString = textStorage.copy() as! NSAttributedString 38 | var replacements: [(NSRange, String, NSParagraphStyle)] = [] 39 | textStorage.enumerateAttribute(.paragraphStyle, in: editingRange, options: []) { (attribute, effectiveRange, _) in 40 | guard 41 | let oldParagraphStyle = attribute as? NSParagraphStyle, 42 | let newParagraphStyle = oldParagraphStyle.mutableCopy() as? NSMutableParagraphStyle 43 | else { 44 | return 45 | } 46 | 47 | var textLists = newParagraphStyle.textLists 48 | //If we're setting a list then we want to replace the list at the desired level 49 | if let listType = listType { 50 | if (textLists.count > level) { 51 | textLists[level] = listType 52 | } else { 53 | textLists = [listType] 54 | } 55 | } else { 56 | //If we have no list then we're removing all lists 57 | textLists = [] 58 | } 59 | newParagraphStyle.textLists = textLists 60 | 61 | //Update the paragraph style on the text storage 62 | textStorage.removeAttribute(.paragraphStyle, range: effectiveRange) 63 | textStorage.addAttribute(.paragraphStyle, value: newParagraphStyle, range: effectiveRange) 64 | 65 | //Enumerate the lines of the old attribute string to find our replacement ranges 66 | (oldString.string as NSString).enumerateSubstrings(in: effectiveRange, options: .byLines) { (substring, substringRange, effectiveRange, _) in 67 | var existingRange = NSRange(location: substringRange.location, length: 0) 68 | //If we had an old list then we want to calculate the marker so we can get its range for replacement 69 | if let oldList = oldParagraphStyle.textLists.last { 70 | var itemNumber = oldString.itemNumber(in: oldList, at: substringRange.location) 71 | //We need to manually handle the startingItemNumber as itemNumber(in:at:) doesn't (despite being giving the list) 72 | if (oldList.startingItemNumber > 1) { 73 | itemNumber = oldList.startingItemNumber + (itemNumber - 1) 74 | } 75 | //We just need the length of the marker as the location is always the start of the line 76 | //We also add 2 as we always have a tab before and after 77 | existingRange.length = oldList.marker(forItemNumber: itemNumber).count + 2 78 | } 79 | 80 | //Add the range and text to replace. We don't actually replace here as we don't want to mess up enumerateAttributes() 81 | if let list = textLists.last { 82 | replacements.append((existingRange, "\t\(list.marker(forItemNumber: textStorage.itemNumber(in: list, at: substringRange.location)))\t", newParagraphStyle)) 83 | } else { 84 | replacements.append((existingRange, "", newParagraphStyle)) 85 | } 86 | } 87 | } 88 | 89 | //Going from back to front (so the ranges remain valid) apply all the list replacements 90 | for (range, string, paragraphStyle) in replacements.reversed() { 91 | textStorage.replaceCharacters(in: range, with: string) 92 | //If we're adding a list then we need to make absolutely sure what is added has the paragraph style 93 | //This is especially true for the earliest range we're adding as it may use the attributes of the text before 94 | if (range.length == 0) { 95 | let addedRange = NSRange(location: range.location, length: string.count) 96 | textStorage.removeAttribute(.paragraphStyle, range: addedRange) 97 | textStorage.addAttribute(.paragraphStyle, value: paragraphStyle, range: addedRange) 98 | } 99 | //We also want to update the selectionLocation so the cursor goes back to the start of the location, which may have shifted due to other list items changing above 100 | if (range.location < selectedLocation) { 101 | selectedLocation += (string.count - range.length) 102 | } 103 | } 104 | } 105 | 106 | self.textView.selectedRanges = [NSValue(range: NSRange(location: selectedLocation, length: 0))] 107 | } 108 | 109 | private func add(_ list: NSTextList, toEndOf textStorage: NSTextStorage) { 110 | let string = "\t\(list.marker(forItemNumber: 0))\t" 111 | var attributes = self.textView.typingAttributes 112 | let paragraphStyle = (attributes[.paragraphStyle] as? NSParagraphStyle) ?? textView.defaultParagraphStyle ?? NSParagraphStyle() 113 | if let mutableStyle = paragraphStyle.mutableCopy() as? NSMutableParagraphStyle { 114 | mutableStyle.textLists = [list] 115 | attributes[.paragraphStyle] = mutableStyle.copy() 116 | } 117 | 118 | textStorage.append(NSAttributedString(string: string, attributes: attributes)) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/NSTextView+M3Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextView+M3Extensions.swift 3 | // TextViewListExample 4 | // 5 | // Created by Martin Pilkington on 02/04/2021. 6 | // 7 | // Please read the LICENCE.txt for licensing information 8 | // 9 | 10 | import AppKit 11 | 12 | extension NSTextView { 13 | func modifyText(in ranges: [NSRange], _ block: (NSTextStorage) -> Void) { 14 | guard let textStorage = self.textStorage else { 15 | return 16 | } 17 | 18 | let rangesAsValues = ranges.map { NSValue(range: $0) } 19 | guard self.shouldChangeText(inRanges: rangesAsValues, replacementStrings: nil) else { 20 | return 21 | } 22 | 23 | textStorage.beginEditing() 24 | block(textStorage) 25 | textStorage.endEditing() 26 | self.didChangeText() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TextViewListExample/TextViewListExample/TextViewListExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Token Cloud/M3TokenCloud.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TokenCloud.h 3 | 4 | Created by Martin Pilkington on 08/01/2009. 5 | 6 | Copyright (c) 2006-2010 M Cubed Software 7 | Parts of M3TokenButtonCell adapted from BWTokenAttachmentCell created by Brandon Walkin (www.brandonwalkin.com) 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | #import 32 | 33 | @class M3TokenController; 34 | 35 | /** 36 | @class CLASS_HERE 37 | DESCRIPTION_HERE 38 | @since Available in M3AppKit 1.0 and later 39 | */ 40 | @interface M3TokenCloud : NSView { 41 | NSMutableSet *tokens; 42 | NSMutableDictionary *tokenButtons; 43 | NSRect prevRect; 44 | IBOutlet M3TokenController *controller; 45 | BOOL setup; 46 | } 47 | 48 | /** 49 | @property PROPERTY_NAME 50 | ABSTRACT_HERE 51 | @since Available in M3AppKit 1.0 and later 52 | */ 53 | @property (retain) M3TokenController *controller; 54 | 55 | /** 56 | @property PROPERTY_NAME 57 | ABSTRACT_HERE 58 | @since Available in M3AppKit 1.0 and later 59 | */ 60 | @property (retain) NSSet *tokens; 61 | 62 | 63 | /** 64 | ABSTRACT_HERE 65 | DISCUSSION_HERE 66 | @param PARAM_HERE 67 | @param PARAM_HERE 68 | @result RESULT_HERE 69 | @since Available in M3AppKit 1.0 and later 70 | */ 71 | - (BOOL)addTokenWithString:(NSString *)token; 72 | 73 | /** 74 | ABSTRACT_HERE 75 | DISCUSSION_HERE 76 | @param PARAM_HERE 77 | @param PARAM_HERE 78 | @result RESULT_HERE 79 | @since Available in M3AppKit 1.0 and later 80 | */ 81 | - (void)removeTokenWithString:(NSString *)token; 82 | 83 | /** 84 | ABSTRACT_HERE 85 | DISCUSSION_HERE 86 | @param PARAM_HERE 87 | @param PARAM_HERE 88 | @result RESULT_HERE 89 | @since Available in M3AppKit 1.0 and later 90 | */ 91 | - (void)tokensToHighlight:(NSArray *)tokensArray; 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Token Cloud/M3TokenCloud.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TokenCloud.m 3 | 4 | Created by Martin Pilkington on 08/01/2009. 5 | 6 | Copyright (c) 2006-2009 M Cubed Software 7 | Parts of M3TokenButtonCell adapted from BWTokenAttachmentCell created by Brandon Walkin (www.brandonwalkin.com) 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import "M3TokenCloud.h" 33 | 34 | @interface M3TokenCloud (ControllerMethods) 35 | 36 | - (void)tokenCloud:(M3TokenCloud *)cloud didClickToken:(NSString *)str enabled:(BOOL)flag; 37 | - (void)updateFrameForHeight:(CGFloat)aHeight; 38 | - (void)setup; 39 | - (void)recalculateButtonLocations; 40 | 41 | @end 42 | 43 | @interface M3TokenButton : NSButton 44 | 45 | - (NSRect)boundingRect; 46 | 47 | @end 48 | 49 | 50 | 51 | @interface M3TokenButtonCell : NSButtonCell 52 | 53 | @end 54 | 55 | 56 | 57 | 58 | 59 | 60 | @implementation M3TokenCloud { 61 | CGFloat intrinsicHeight; 62 | } 63 | 64 | @synthesize controller; 65 | 66 | - (id)initWithFrame:(NSRect)frame { 67 | self = [super initWithFrame:frame]; 68 | if (self) { 69 | [self setup]; 70 | } 71 | return self; 72 | } 73 | 74 | - (id)initWithCoder:(NSCoder *)aDecoder { 75 | if ((self = [super initWithCoder:aDecoder])) { 76 | [self setup]; 77 | } 78 | return self; 79 | } 80 | 81 | 82 | - (void)setup { 83 | tokens = [[NSMutableSet alloc] init]; 84 | tokenButtons = [[NSMutableDictionary alloc] init]; 85 | prevRect = NSZeroRect; 86 | } 87 | 88 | - (NSSize)intrinsicContentSize { 89 | return NSMakeSize(NSViewNoInstrinsicMetric, intrinsicHeight); 90 | } 91 | 92 | 93 | - (NSSet *)tokens { 94 | return [[tokens copy] autorelease]; 95 | } 96 | 97 | - (void)setTokens:(NSSet *)set { 98 | //Find the tokens that aren't in the new set and remove them from the view 99 | [tokens minusSet:set]; 100 | for (NSString *title in [tokens allObjects]) { 101 | [[tokenButtons objectForKey:title] removeFromSuperview]; 102 | [tokenButtons removeObjectForKey:title]; 103 | } 104 | 105 | [tokens release]; 106 | tokens = [set mutableCopy]; 107 | 108 | //Create new buttons 109 | for (NSString *title in [tokens allObjects]) { 110 | if (![tokenButtons objectForKey:title] && [title length]) { 111 | M3TokenButton *button = [[[M3TokenButton alloc] initWithFrame:NSMakeRect(0, 0, 50, 20)] autorelease]; 112 | [button setTitle:title]; 113 | [button setTarget:self]; 114 | [button setAction:@selector(buttonClicked:)]; 115 | [tokenButtons setObject:button forKey:title]; 116 | } 117 | } 118 | [self recalculateButtonLocations]; 119 | } 120 | 121 | 122 | - (void)drawRect:(NSRect)rect { 123 | [self recalculateButtonLocations]; 124 | } 125 | 126 | - (void)recalculateButtonLocations { 127 | //Get all our buttons sorted alphabetically by their title 128 | NSArray *buttons = [[tokenButtons allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease]]]; 129 | int index = 0; 130 | 131 | NSMutableArray *rowsArray = [NSMutableArray array]; 132 | //Loop through buttons assigning them to rows 133 | while (index < [buttons count]) { 134 | float x = 0; 135 | float rowWidth = 0; 136 | NSMutableArray *row = [NSMutableArray array]; 137 | while (x < [self bounds].size.width) { 138 | M3TokenButton *button = [buttons objectAtIndex:index]; 139 | x += [button boundingRect].size.width + 4; 140 | rowWidth += [button boundingRect].size.width; 141 | //If this button puts us over the width of the view then break and move to the next line, if the button's bounding rect is greater than the size of the view then draw anyway and trim. 142 | if (x > [self bounds].size.width && [button boundingRect].size.width + 4 < [self bounds].size.width) { 143 | rowWidth -= [button boundingRect].size.width; 144 | break; 145 | } 146 | [row addObject:button]; 147 | index++; 148 | if (index >= [buttons count]) 149 | break; 150 | } 151 | [rowsArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:row, @"row", [NSNumber numberWithFloat:rowWidth], @"rowWidth", nil]]; 152 | } 153 | 154 | //Now position the bloody things 155 | float y = [self bounds].size.height - 20; 156 | for (NSDictionary *row in rowsArray) { 157 | float x = 0; 158 | 159 | float rowWidth = [[row objectForKey:@"rowWidth"] floatValue]; 160 | NSArray *rowArray = [row objectForKey:@"row"]; 161 | float spacing = ([self bounds].size.width - rowWidth) / ([rowArray count] - 1); 162 | 163 | for (M3TokenButton *button in rowArray) { 164 | [button setFrameOrigin:NSMakePoint(x, y)]; 165 | if ([rowsArray count] > 1) { 166 | x += [button boundingRect].size.width + spacing; 167 | } else { 168 | x += [button boundingRect].size.width + 4; 169 | } 170 | if (![button superview]) 171 | [self addSubview:button]; 172 | } 173 | y -= 24; 174 | } 175 | //Send the new preferred height to the 176 | [self updateFrameForHeight:24 * [rowsArray count]]; 177 | } 178 | 179 | 180 | - (void)updateFrameForHeight:(CGFloat)aHeight { 181 | intrinsicHeight = aHeight; 182 | [self invalidateIntrinsicContentSize]; 183 | } 184 | 185 | - (BOOL)addTokenWithString:(NSString *)token { 186 | BOOL returnValue = NO; 187 | if (![token length]) { 188 | return NO; 189 | } 190 | 191 | if (![tokens containsObject:token]) { 192 | M3TokenButton *button = [[[M3TokenButton alloc] initWithFrame:NSMakeRect(0, 0, 50, 20)] autorelease]; 193 | [button setTitle:token]; 194 | [button setTarget:self]; 195 | [button setAction:@selector(buttonClicked:)]; 196 | [tokenButtons setObject:button forKey:token]; 197 | [self recalculateButtonLocations]; 198 | returnValue = YES; 199 | } 200 | 201 | [tokens addObject:token]; 202 | return returnValue; 203 | } 204 | 205 | - (void)removeTokenWithString:(NSString *)token { 206 | if ([tokens containsObject:token]) { 207 | [[tokenButtons objectForKey:token] removeFromSuperview]; 208 | [tokenButtons removeObjectForKey:token]; 209 | [self recalculateButtonLocations]; 210 | } 211 | [tokens removeObject:token]; 212 | } 213 | 214 | - (void)tokensToHighlight:(NSArray *)tokensArray { 215 | for (NSButton *button in [tokenButtons allValues]) { 216 | [button setState:[tokensArray containsObject:[button title]]]; 217 | } 218 | } 219 | 220 | - (void)buttonClicked:(id)sender { 221 | if ([[self controller] respondsToSelector:@selector(tokenCloud:didClickToken:enabled:)]) { 222 | [[self controller] tokenCloud:self didClickToken:[sender title] enabled:[sender state]]; 223 | } 224 | } 225 | 226 | @end 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | @implementation M3TokenButton 236 | 237 | + (Class)cellClass { 238 | return [M3TokenButtonCell class]; 239 | } 240 | 241 | - (void)drawRect:(NSRect)rect { 242 | [self setFrame:[self boundingRect]]; //Can't remember why I need to do this, but I do 243 | [super drawRect:[self boundingRect]]; 244 | } 245 | 246 | - (NSRect)boundingRect { 247 | NSRect boundingRect = [[self attributedTitle] boundingRectWithSize:NSMakeSize(1000, [self frame].size.height) options:0]; 248 | boundingRect.size.width += 20; 249 | boundingRect.size.height = [self frame].size.height; 250 | boundingRect.origin = [self frame].origin; 251 | return boundingRect; 252 | } 253 | 254 | @end 255 | 256 | 257 | 258 | static NSGradient *blueStrokeGradient; 259 | static NSGradient *blueInsetGradient; 260 | static NSGradient *blueGradient; 261 | static NSGradient *highlightedBlueStrokeGradient; 262 | static NSGradient *highlightedBlueInsetGradient; 263 | static NSGradient *highlightedBlueGradient; 264 | static NSGradient *hoverBlueGradient; 265 | static NSGradient *hoverBlueInsetGradient; 266 | 267 | 268 | 269 | /* 270 | Yeah, most of this is borrowed from BWToolkit. Brandon Walkin++ 271 | */ 272 | @implementation M3TokenButtonCell 273 | 274 | /* 275 | Change this to play with the colours of the tokens 276 | */ 277 | + (void)initialize 278 | { 279 | 280 | NSColor *blueTopColor = [NSColor colorWithCalibratedRed:217.0/255.0 green:228.0/255.0 blue:254.0/255.0 alpha:1]; 281 | NSColor *blueBottomColor = [NSColor colorWithCalibratedRed:195.0/255.0 green:212.0/255.0 blue:250.0/255.0 alpha:1]; 282 | blueGradient = [[NSGradient alloc] initWithStartingColor:blueTopColor endingColor:blueBottomColor]; 283 | 284 | NSColor *blueStrokeTopColor = [NSColor colorWithCalibratedRed:164.0/255.0 green:184.0/255.0 blue:230.0/255.0 alpha:1]; 285 | NSColor *blueStrokeBottomColor = [NSColor colorWithCalibratedRed:122.0/255.0 green:128.0/255.0 blue:199.0/255.0 alpha:1]; 286 | blueStrokeGradient = [[NSGradient alloc] initWithStartingColor:blueStrokeTopColor endingColor:blueStrokeBottomColor]; 287 | 288 | NSColor *blueInsetTopColor = [NSColor colorWithCalibratedRed:226.0/255.0 green:234.0/255.0 blue:254.0/255.0 alpha:1]; 289 | NSColor *blueInsetBottomColor = [NSColor colorWithCalibratedRed:206.0/255.0 green:221.0/255.0 blue:250.0/255.0 alpha:1]; 290 | blueInsetGradient = [[NSGradient alloc] initWithStartingColor:blueInsetTopColor endingColor:blueInsetBottomColor]; 291 | 292 | NSColor *highlightedBlueTopColor = [NSColor colorWithCalibratedRed:80.0/255.0 green:127.0/255.0 blue:251.0/255.0 alpha:1]; 293 | NSColor *highlightedBlueBottomColor = [NSColor colorWithCalibratedRed:65.0/255.0 green:107.0/255.0 blue:236.0/255.0 alpha:1]; 294 | highlightedBlueGradient = [[NSGradient alloc] initWithStartingColor:highlightedBlueTopColor endingColor:highlightedBlueBottomColor]; 295 | 296 | NSColor *highlightedBlueStrokeTopColor = [NSColor colorWithCalibratedRed:51.0/255.0 green:95.0/255.0 blue:248.0/255.0 alpha:1]; 297 | NSColor *highlightedBlueStrokeBottomColor = [NSColor colorWithCalibratedRed:42.0/255.0 green:47.0/255.0 blue:233.0/255.0 alpha:1]; 298 | highlightedBlueStrokeGradient = [[NSGradient alloc] initWithStartingColor:highlightedBlueStrokeTopColor endingColor:highlightedBlueStrokeBottomColor]; 299 | 300 | NSColor *highlightedBlueInsetTopColor = [NSColor colorWithCalibratedRed:92.0/255.0 green:137.0/255.0 blue:251.0/255.0 alpha:1]; 301 | NSColor *highlightedBlueInsetBottomColor = [NSColor colorWithCalibratedRed:76.0/255.0 green:116.0/255.0 blue:236.0/255.0 alpha:1]; 302 | highlightedBlueInsetGradient = [[NSGradient alloc] initWithStartingColor:highlightedBlueInsetTopColor endingColor:highlightedBlueInsetBottomColor]; 303 | 304 | NSColor *hoverBlueTopColor = [NSColor colorWithCalibratedRed:195.0/255.0 green:207.0/255.0 blue:243.0/255.0 alpha:1]; 305 | NSColor *hoverBlueBottomColor = [NSColor colorWithCalibratedRed:176.0/255.0 green:193.0/255.0 blue:239.0/255.0 alpha:1]; 306 | hoverBlueGradient = [[NSGradient alloc] initWithStartingColor:hoverBlueTopColor endingColor:hoverBlueBottomColor]; 307 | 308 | NSColor *hoverBlueInsetTopColor = [NSColor colorWithCalibratedRed:204.0/255.0 green:2137.0/255.0 blue:243.0/255.0 alpha:1]; 309 | NSColor *hoverBlueInsetBottomColor = [NSColor colorWithCalibratedRed:186.0/255.0 green:201.0/255.0 blue:239.0/255.0 alpha:1]; 310 | hoverBlueInsetGradient = [[NSGradient alloc] initWithStartingColor:hoverBlueInsetTopColor endingColor:hoverBlueInsetBottomColor]; 311 | } 312 | 313 | 314 | - (BOOL)allowsMixedState { 315 | return NO; 316 | } 317 | 318 | 319 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { 320 | float scaleFactor = [[NSScreen mainScreen] userSpaceScaleFactor]; 321 | 322 | NSRect drawingRect = [self drawingRectForBounds:cellFrame]; 323 | NSRect insetRect = NSInsetRect(drawingRect, 1 / scaleFactor, 1 / scaleFactor); 324 | NSRect insetRect2 = NSInsetRect(insetRect, 1 / scaleFactor, 1 / scaleFactor); 325 | 326 | if (scaleFactor < 0.99 || scaleFactor > 1.01) 327 | { 328 | drawingRect = [controlView centerScanRect:drawingRect]; 329 | insetRect = [controlView centerScanRect:insetRect]; 330 | insetRect2 = [controlView centerScanRect:insetRect2]; 331 | } 332 | 333 | NSBezierPath *drawingPath = [NSBezierPath bezierPathWithRoundedRect:drawingRect xRadius:0.5*drawingRect.size.height yRadius:0.5*drawingRect.size.height]; 334 | NSBezierPath *insetPath = [NSBezierPath bezierPathWithRoundedRect:insetRect xRadius:0.5*insetRect.size.height yRadius:0.5*insetRect.size.height]; 335 | NSBezierPath *insetPath2 = [NSBezierPath bezierPathWithRoundedRect:insetRect2 xRadius:0.5*insetRect2.size.height yRadius:0.5*insetRect2.size.height]; 336 | 337 | NSMutableAttributedString *str = [[self attributedTitle] mutableCopy]; 338 | NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; 339 | [style setAlignment:NSCenterTextAlignment]; 340 | [str setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSColor blackColor], NSForegroundColorAttributeName, [NSFont systemFontOfSize:12], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil] range:NSMakeRange(0, [str length])]; 341 | if ([self isHighlighted]) { 342 | [blueStrokeGradient drawInBezierPath:drawingPath angle:90]; 343 | [hoverBlueInsetGradient drawInBezierPath:insetPath angle:90]; 344 | [hoverBlueGradient drawInBezierPath:insetPath2 angle:90]; 345 | } else if ([self state] == NSOffState) { 346 | [blueStrokeGradient drawInBezierPath:drawingPath angle:90]; 347 | [blueInsetGradient drawInBezierPath:insetPath angle:90]; 348 | [blueGradient drawInBezierPath:insetPath2 angle:90]; 349 | } else { 350 | [str setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSColor whiteColor], NSForegroundColorAttributeName, [NSFont systemFontOfSize:12], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil] range:NSMakeRange(0, [str length])]; 351 | 352 | [highlightedBlueStrokeGradient drawInBezierPath:drawingPath angle:90]; 353 | [highlightedBlueInsetGradient drawInBezierPath:insetPath angle:90]; 354 | [highlightedBlueGradient drawInBezierPath:insetPath2 angle:90]; 355 | } 356 | [style release]; 357 | 358 | NSRect textRect = drawingRect; 359 | 360 | textRect.size.height = 16; 361 | textRect.origin.y = (drawingRect.size.height - 14) / 2; 362 | 363 | [str drawInRect:textRect]; 364 | [str release]; 365 | } 366 | 367 | - (NSRect)drawingRectForBounds:(NSRect)bounds { 368 | return NSMakeRect(1, 1, bounds.size.width - 3, bounds.size.height - 3); 369 | } 370 | 371 | @end 372 | 373 | 374 | -------------------------------------------------------------------------------- /Token Cloud/M3TokenController.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TokenController.h 3 | 4 | Created by Martin Pilkington on 08/01/2009. 5 | 6 | Copyright (c) 2006-2010 M Cubed Software 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | *****************************************************************/ 30 | 31 | #import 32 | 33 | @class M3TokenCloud; 34 | @protocol M3TokenControllerDelegate; 35 | 36 | /** 37 | @class CLASS_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @interface M3TokenController : NSObject { 42 | id delegate; 43 | IBOutlet NSTokenField *tokenField; 44 | IBOutlet M3TokenCloud *tokenCloud; 45 | } 46 | 47 | /** 48 | @property PROPERTY_NAME 49 | ABSTRACT_HERE 50 | @since Available in M3AppKit 1.0 and later 51 | */ 52 | @property (retain) IBOutlet id delegate; 53 | 54 | /** 55 | @property PROPERTY_NAME 56 | ABSTRACT_HERE 57 | @since Available in M3AppKit 1.0 and later 58 | */ 59 | @property (retain) NSTokenField *tokenField; 60 | 61 | /** 62 | @property PROPERTY_NAME 63 | ABSTRACT_HERE 64 | @since Available in M3AppKit 1.0 and later 65 | */ 66 | @property (retain) M3TokenCloud *tokenCloud; 67 | 68 | 69 | /** 70 | ABSTRACT_HERE 71 | DISCUSSION_HERE 72 | @param PARAM_HERE 73 | @param PARAM_HERE 74 | @result RESULT_HERE 75 | @since Available in M3AppKit 1.0 and later 76 | */ 77 | - (void)reloadTokens; 78 | 79 | @end -------------------------------------------------------------------------------- /Token Cloud/M3TokenController.m: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TokenController.m 3 | 4 | Created by Martin Pilkington on 08/01/2009. 5 | 6 | Copyright (c) 2006-2010 M Cubed Software 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | *****************************************************************/ 30 | 31 | #import "M3TokenController.h" 32 | 33 | #import "M3TokenCloud.h" 34 | #import "M3TokenControllerDelegate.h" 35 | 36 | @interface M3TokenController () 37 | 38 | - (void)_updatingBindingWithValue:(NSString *)aValue; 39 | 40 | @end 41 | 42 | 43 | @implementation M3TokenController 44 | 45 | @synthesize delegate; 46 | 47 | - (id)initWithCoder:(NSCoder *)aDecoder { 48 | if ((self = [super init])) { 49 | [self setTokenField:[aDecoder decodeObjectForKey:@"tokenField"]]; 50 | [self setTokenCloud:[aDecoder decodeObjectForKey:@"tokenCloud"]]; 51 | } 52 | return self; 53 | } 54 | 55 | 56 | - (void)encodeWithCoder:(NSCoder *)aCoder { 57 | [aCoder encodeConditionalObject:tokenField forKey:@"tokenField"]; 58 | [aCoder encodeConditionalObject:tokenCloud forKey:@"tokenCloud"]; 59 | } 60 | 61 | - (NSTokenField *)tokenField { 62 | return tokenField; 63 | } 64 | 65 | 66 | - (void)setTokenField:(NSTokenField *)field { 67 | if (field != tokenField) { 68 | //Remove the notifications from the old token field 69 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSControlTextDidChangeNotification object:tokenField]; 70 | [tokenField release]; 71 | tokenField = [field retain]; 72 | [tokenField setDelegate:self]; 73 | //And then add them to the new token field 74 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tokenFieldChanged:) name:NSControlTextDidChangeNotification object:tokenField]; 75 | } 76 | } 77 | 78 | 79 | - (M3TokenCloud *)tokenCloud { 80 | return tokenCloud; 81 | } 82 | 83 | - (void)setTokenCloud:(M3TokenCloud *)cloud { 84 | if (cloud != tokenCloud) { 85 | [tokenCloud release]; 86 | tokenCloud = [cloud retain]; 87 | [tokenCloud setController:self]; 88 | } 89 | } 90 | 91 | 92 | 93 | /* 94 | Add the token to the token cloud. If it wasn't a duplicate then tell the delegate to add a new token 95 | */ 96 | - (NSArray *)tokenField:(NSTokenField *)tokField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index { 97 | for (NSString *token in tokens) { 98 | if ([tokenCloud addTokenWithString:token]) { 99 | if ([[self delegate] respondsToSelector:@selector(tokenController:didAddNewToken:)]) { 100 | [[self delegate] tokenController:self didAddNewToken:token]; 101 | } 102 | } 103 | } 104 | return tokens; 105 | } 106 | 107 | - (void)controlTextDidEndEditing:(NSNotification *)aNotification { 108 | [self tokenField:tokenField shouldAddObjects:[[tokenField stringValue] componentsSeparatedByCharactersInSet:[tokenField tokenizingCharacterSet]] atIndex:0]; 109 | [tokenCloud tokensToHighlight:[[tokenField stringValue] componentsSeparatedByCharactersInSet:[tokenField tokenizingCharacterSet]]]; 110 | } 111 | 112 | /* 113 | Update the tokens that need highlighting in the token cloud 114 | */ 115 | - (void)tokenFieldChanged:(NSNotification *)note { 116 | [tokenCloud tokensToHighlight:[[tokenField stringValue] componentsSeparatedByCharactersInSet:[tokenField tokenizingCharacterSet]]]; 117 | } 118 | 119 | /* 120 | If the token field exists then set the string value 121 | */ 122 | - (void)tokenCloud:(M3TokenCloud *)cloud didClickToken:(NSString *)str enabled:(BOOL)flag { 123 | if (tokenField) { 124 | NSMutableArray *tokens = [[[tokenField stringValue] componentsSeparatedByCharactersInSet:[tokenField tokenizingCharacterSet]] mutableCopy]; 125 | if ([tokens containsObject:str] && !flag) { 126 | [tokens removeObject:str]; 127 | } else if (![tokens containsObject:str] && flag) { 128 | [tokens addObject:str]; 129 | } 130 | //Yeah, I only support comma as the separator for now. I'll fix this at some point 131 | NSString *tokenString = [tokens componentsJoinedByString:@","]; 132 | [tokenField setStringValue:tokenString]; 133 | 134 | [self _updatingBindingWithValue:tokenString]; 135 | 136 | [tokens release]; 137 | } 138 | } 139 | 140 | - (void)_updatingBindingWithValue:(NSString *)aValue { 141 | NSDictionary *bindingInfo = [tokenField infoForBinding:@"value"]; 142 | NSString *keyPath = [bindingInfo objectForKey:NSObservedKeyPathKey]; 143 | id object = [bindingInfo objectForKey:NSObservedObjectKey]; 144 | 145 | NSValueTransformer *valueTransformer = [[bindingInfo objectForKey:NSOptionsKey] objectForKey:NSValueTransformerBindingOption]; 146 | 147 | if (valueTransformer) { 148 | [object setValue:[valueTransformer transformedValue:aValue] forKeyPath:keyPath]; 149 | } else { 150 | [object setValue:aValue forKeyPath:keyPath]; 151 | } 152 | } 153 | 154 | - (void)reloadTokens { 155 | if ([[self delegate] respondsToSelector:@selector(tagsForTokenController:)]) { 156 | NSSet *tokens = [[self delegate] tagsForTokenController:self]; 157 | if (!tokens) { 158 | tokens = [NSMutableSet set]; 159 | } 160 | [tokenCloud setTokens:tokens]; 161 | [self controlTextDidEndEditing:nil]; 162 | [tokenCloud tokensToHighlight:[[tokenField stringValue] componentsSeparatedByCharactersInSet:[tokenField tokenizingCharacterSet]]]; 163 | } 164 | } 165 | 166 | /****************************** 167 | Deal with tags auto complete 168 | ******************************/ 169 | - (NSArray *)tokenField:(NSTokenField *)aTokenField completionsForSubstring:(NSString *)aSubstring indexOfToken:(NSInteger)TokenIndex indexOfSelectedItem:(NSInteger *)aSelectedIndex { 170 | NSPredicate *filterPred = [NSPredicate predicateWithFormat:@"description BEGINSWITH[cd] %@", aSubstring]; 171 | return [[[tokenCloud tokens] filteredSetUsingPredicate:filterPred] allObjects]; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /Token Cloud/M3TokenControllerDelegate.h: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | M3TokenControllerDelegate.h 3 | M3Extensions 4 | 5 | Created by Martin Pilkington on 20/06/2010. 6 | 7 | Copyright (c) 2006-2010 M Cubed Software 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | *****************************************************************/ 31 | 32 | #import 33 | 34 | @class M3TokenController; 35 | 36 | /** 37 | @protocol PROTOCOL_HERE 38 | DESCRIPTION_HERE 39 | @since Available in M3AppKit 1.0 and later 40 | */ 41 | @protocol M3TokenControllerDelegate 42 | 43 | @optional 44 | /** 45 | ABSTRACT_HERE 46 | DISCUSSION_HERE 47 | @param PARAM_HERE 48 | @param PARAM_HERE 49 | @result RESULT_HERE 50 | @since Available in M3AppKit 1.0 and later 51 | */ 52 | - (NSSet *)tagsForTokenController:(M3TokenController *)controller; 53 | 54 | /** 55 | ABSTRACT_HERE 56 | DISCUSSION_HERE 57 | @param PARAM_HERE 58 | @param PARAM_HERE 59 | @result RESULT_HERE 60 | @since Available in M3AppKit 1.0 and later 61 | */ 62 | - (void)tokenController:(M3TokenController *)controller didAddNewToken:(NSString *)token; 63 | 64 | @end 65 | --------------------------------------------------------------------------------