├── Sample ├── macOSRTESample │ ├── macOSRichTextEditor.framework │ │ ├── Versions │ │ │ ├── Current │ │ │ └── A │ │ │ │ ├── Modules │ │ │ │ └── module.modulemap │ │ │ │ ├── macOSRichTextEditor │ │ │ │ ├── PrivateHeaders │ │ │ │ └── WZProtocolInterceptor.h │ │ │ │ ├── Headers │ │ │ │ ├── macOSRichTextEditor.h │ │ │ │ ├── NSAttributedString+RichTextEditor.h │ │ │ │ ├── NSFont+RichTextEditor.h │ │ │ │ └── RichTextEditor.h │ │ │ │ └── Resources │ │ │ │ └── Info.plist │ │ ├── Headers │ │ ├── Modules │ │ ├── Resources │ │ ├── PrivateHeaders │ │ └── macOSRichTextEditor │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── bold.imageset │ │ │ ├── format-bold.png │ │ │ ├── format-bold-48.png │ │ │ └── Contents.json │ │ ├── italic.imageset │ │ │ ├── format-italic-24.png │ │ │ ├── format-italic-48.png │ │ │ └── Contents.json │ │ ├── underline.imageset │ │ │ ├── format-underline-24.png │ │ │ ├── format-underline-48.png │ │ │ └── Contents.json │ │ ├── bulleted-list.imageset │ │ │ ├── format-list-bulleted-24.png │ │ │ ├── format-list-bulleted-48.png │ │ │ └── Contents.json │ │ ├── indent-decrease.imageset │ │ │ ├── format-indent-decrease-24.png │ │ │ ├── format-indent-decrease-48.png │ │ │ └── Contents.json │ │ ├── indent-increase.imageset │ │ │ ├── format-indent-increase-24.png │ │ │ ├── format-indent-increase-48.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── ViewController.h │ ├── main.m │ ├── AppDelegate.h │ ├── macOSRTESample.entitlements │ ├── AppDelegate.m │ ├── Info.plist │ └── ViewController.m ├── macOSRTESampleTests │ ├── Info.plist │ └── macOSRTESampleTests.m ├── macOSRTESampleUITests │ ├── Info.plist │ └── macOSRTESampleUITests.m └── macOSRTESample.xcodeproj │ └── project.pbxproj ├── Library ├── macOSRichTextEditor.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj ├── macOSRichTextEditor │ ├── Source │ │ ├── WZProtocolInterceptor.h │ │ ├── Categories │ │ │ ├── NSAttributedString+RichTextEditor.h │ │ │ ├── NSFont+RichTextEditor.h │ │ │ ├── NSFont+RichTextEditor.m │ │ │ └── NSAttributedString+RichTextEditor.m │ │ ├── WZProtocolInterceptor.m │ │ ├── RichTextEditor.h │ │ └── RichTextEditor.m │ ├── macOSRichTextEditor.h │ └── Info.plist └── macOSRichTextEditorTests │ ├── Info.plist │ └── macOSRichTextEditorTests.m ├── macOSRichTextEditor.xcworkspace └── contents.xcworkspacedata ├── .gitignore ├── README.md └── License.txt /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/PrivateHeaders: -------------------------------------------------------------------------------- 1 | Versions/Current/PrivateHeaders -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/macOSRichTextEditor: -------------------------------------------------------------------------------- 1 | Versions/Current/macOSRichTextEditor -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bold.imageset/format-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/bold.imageset/format-bold.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bold.imageset/format-bold-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/bold.imageset/format-bold-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/italic.imageset/format-italic-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/italic.imageset/format-italic-24.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/italic.imageset/format-italic-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/italic.imageset/format-italic-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/underline.imageset/format-underline-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/underline.imageset/format-underline-24.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/underline.imageset/format-underline-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/underline.imageset/format-underline-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module macOSRichTextEditor { 2 | umbrella header "macOSRichTextEditor.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/macOSRichTextEditor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/macOSRichTextEditor -------------------------------------------------------------------------------- /Library/macOSRichTextEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bulleted-list.imageset/format-list-bulleted-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/bulleted-list.imageset/format-list-bulleted-24.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bulleted-list.imageset/format-list-bulleted-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/bulleted-list.imageset/format-list-bulleted-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-decrease.imageset/format-indent-decrease-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/indent-decrease.imageset/format-indent-decrease-24.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-decrease.imageset/format-indent-decrease-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/indent-decrease.imageset/format-indent-decrease-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-increase.imageset/format-indent-increase-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/indent-increase.imageset/format-indent-increase-24.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-increase.imageset/format-indent-increase-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deadpikle/macOS-Rich-Text-Editor/HEAD/Sample/macOSRTESample/Assets.xcassets/indent-increase.imageset/format-indent-increase-48.png -------------------------------------------------------------------------------- /Sample/macOSRTESample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // macOSRTESample 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : NSViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // macOSRTESample 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // macOSRTESample 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRTESample.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 | -------------------------------------------------------------------------------- /macOSRichTextEditor.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bold.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-bold.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-bold-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/italic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-italic-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-italic-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/underline.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-underline-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-underline-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/bulleted-list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-list-bulleted-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-list-bulleted-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-decrease.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-indent-decrease-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-indent-decrease-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/indent-increase.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "format-indent-increase-24.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "format-indent-increase-48.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | 3 | # Created by https://www.gitignore.io/api/xcode 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata/ 18 | 19 | ## Other 20 | *.moved-aside 21 | *.xccheckout 22 | *.xcscmblueprint 23 | 24 | ### Xcode Patch ### 25 | *.xcodeproj/* 26 | !*.xcodeproj/project.pbxproj 27 | !*.xcodeproj/xcshareddata/ 28 | !*.xcworkspace/contents.xcworkspacedata 29 | /*.gcno 30 | 31 | 32 | # End of https://www.gitignore.io/api/xcode 33 | macOSRichTextEditor.xcworkspace/xcshareddata/ 34 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // macOSRTESample 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 18 | // Insert code here to initialize your application 19 | } 20 | 21 | 22 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 23 | // Insert code here to tear down your application 24 | } 25 | 26 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { 27 | return YES; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Sample/macOSRTESampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sample/macOSRTESampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/WZProtocolInterceptor.h: -------------------------------------------------------------------------------- 1 | // 2 | // WZProtocolInterceptor.h 3 | // 4 | // Created by WeZZard on 10/13/13. 5 | // Copyright © 2013 WeZZard. All rights reserved. 6 | // http://stackoverflow.com/a/18777565/3938401 7 | 8 | #import 9 | 10 | @interface WZProtocolInterceptor : NSObject 11 | 12 | @property (nonatomic, readonly, copy) NSArray * interceptedProtocols; 13 | @property (unsafe_unretained) id receiver; 14 | @property (unsafe_unretained) id middleMan; 15 | 16 | - (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol; 17 | - (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ... NS_REQUIRES_NIL_TERMINATION; 18 | - (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/macOSRichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // macOSRichTextEditor.h 3 | // macOSRichTextEditor 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for macOSRichTextEditor. 12 | FOUNDATION_EXPORT double macOSRichTextEditorVersionNumber; 13 | 14 | //! Project version string for macOSRichTextEditor. 15 | FOUNDATION_EXPORT const unsigned char macOSRichTextEditorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #include 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/PrivateHeaders/WZProtocolInterceptor.h: -------------------------------------------------------------------------------- 1 | // 2 | // WZProtocolInterceptor.h 3 | // 4 | // Created by WeZZard on 10/13/13. 5 | // Copyright © 2013 WeZZard. All rights reserved. 6 | // http://stackoverflow.com/a/18777565/3938401 7 | 8 | #import 9 | 10 | @interface WZProtocolInterceptor : NSObject 11 | 12 | @property (nonatomic, readonly, copy) NSArray * interceptedProtocols; 13 | @property (unsafe_unretained) id receiver; 14 | @property (unsafe_unretained) id middleMan; 15 | 16 | - (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol; 17 | - (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ... NS_REQUIRES_NIL_TERMINATION; 18 | - (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Headers/macOSRichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // macOSRichTextEditor.h 3 | // macOSRichTextEditor 4 | // 5 | // Created by School of Computing Macbook on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for macOSRichTextEditor. 12 | FOUNDATION_EXPORT double macOSRichTextEditorVersionNumber; 13 | 14 | //! Project version string for macOSRichTextEditor. 15 | FOUNDATION_EXPORT const unsigned char macOSRichTextEditorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #include 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2018 Pikle Productions. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sample/macOSRTESampleTests/macOSRTESampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // macOSRTESampleTests.m 3 | // macOSRTESampleTests 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface macOSRTESampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation macOSRTESampleTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditorTests/macOSRichTextEditorTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // macOSRichTextEditorTests.m 3 | // macOSRichTextEditorTests 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface macOSRichTextEditorTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation macOSRichTextEditorTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/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 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Pikle Productions. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Sample/macOSRTESampleUITests/macOSRTESampleUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // macOSRTESampleUITests.m 3 | // macOSRTESampleUITests 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface macOSRTESampleUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation macOSRTESampleUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 17D102 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | macOSRichTextEditor 11 | CFBundleIdentifier 12 | com.pikleproductions.macOSRichTextEditor 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | macOSRichTextEditor 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 9C40b 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 17C76 35 | DTSDKName 36 | macosx10.13 37 | DTXcode 38 | 0920 39 | DTXcodeBuild 40 | 9C40b 41 | NSHumanReadableCopyright 42 | Copyright © 2018 Pikle Productions. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | #import 32 | #import "NSFont+RichTextEditor.h" 33 | 34 | @interface NSAttributedString (RichTextEditor) 35 | 36 | - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range; 37 | - (NSArray *)rangeOfParagraphsFromTextRange:(NSRange)textRange; 38 | - (NSString *)htmlString; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Headers/NSAttributedString+RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | #import 32 | #import "NSFont+RichTextEditor.h" 33 | 34 | @interface NSAttributedString (RichTextEditor) 35 | 36 | - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range; 37 | - (NSArray *)rangeOfParagraphsFromTextRange:(NSRange)textRange; 38 | - (NSString *)htmlString; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/Categories/NSFont+RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | #import 32 | #import 33 | 34 | @interface NSFont (RichTextEditor) 35 | 36 | + (NSString *)postscriptNameFromFullName:(NSString *)fullName; 37 | + (NSFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isBold italicTrait:(BOOL)isItalic; 38 | - (NSFont *)fontWithBoldTrait:(BOOL)bold italicTrait:(BOOL)italic andSize:(CGFloat)size; 39 | - (NSFont *)fontWithBoldTrait:(BOOL)bold andItalicTrait:(BOOL)italic; 40 | - (BOOL)isBold; 41 | - (BOOL)isItalic; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Headers/NSFont+RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | #import 32 | #import 33 | 34 | @interface NSFont (RichTextEditor) 35 | 36 | + (NSString *)postscriptNameFromFullName:(NSString *)fullName; 37 | + (NSFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isBold italicTrait:(BOOL)isItalic; 38 | - (NSFont *)fontWithBoldTrait:(BOOL)bold italicTrait:(BOOL)italic andSize:(CGFloat)size; 39 | - (NSFont *)fontWithBoldTrait:(BOOL)bold andItalicTrait:(BOOL)italic; 40 | - (BOOL)isBold; 41 | - (BOOL)isItalic; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/WZProtocolInterceptor.m: -------------------------------------------------------------------------------- 1 | // 2 | // WZProtocolInterceptor.m 3 | // 4 | // Created by WeZZard on 10/13/13. 5 | // Copyright © 2013 WeZZard. All rights reserved. 6 | // http://stackoverflow.com/a/18777565/3938401 7 | 8 | #import "WZProtocolInterceptor.h" 9 | #import 10 | 11 | static inline BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol); 12 | 13 | @implementation WZProtocolInterceptor 14 | - (id)forwardingTargetForSelector:(SEL)aSelector { 15 | if (self.middleMan && [self.middleMan respondsToSelector:aSelector] && 16 | [self isSelectorContainedInInterceptedProtocols:aSelector]) { 17 | return self.middleMan; 18 | } 19 | 20 | if ([self.receiver respondsToSelector:aSelector]) { 21 | return self.receiver; 22 | } 23 | 24 | return [super forwardingTargetForSelector:aSelector]; 25 | } 26 | 27 | - (BOOL)respondsToSelector:(SEL)aSelector { 28 | if (self.middleMan && [self.middleMan respondsToSelector:aSelector] && 29 | [self isSelectorContainedInInterceptedProtocols:aSelector]) { 30 | return YES; 31 | } 32 | 33 | if (self.receiver && [self.receiver respondsToSelector:aSelector]) { 34 | return YES; 35 | } 36 | 37 | return [super respondsToSelector:aSelector]; 38 | } 39 | 40 | - (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol { 41 | self = [super init]; 42 | if (self) { 43 | _interceptedProtocols = @[interceptedProtocol]; 44 | } 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ...; { 49 | self = [super init]; 50 | if (self) { 51 | NSMutableArray * mutableProtocols = [NSMutableArray array]; 52 | Protocol * eachInterceptedProtocol; 53 | va_list argumentList; 54 | if (firstInterceptedProtocol) { 55 | [mutableProtocols addObject:firstInterceptedProtocol]; 56 | va_start(argumentList, firstInterceptedProtocol); 57 | while ((eachInterceptedProtocol = va_arg(argumentList, id))) { 58 | [mutableProtocols addObject:eachInterceptedProtocol]; 59 | } 60 | va_end(argumentList); 61 | } 62 | _interceptedProtocols = [mutableProtocols copy]; 63 | } 64 | return self; 65 | } 66 | 67 | - (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols { 68 | self = [super init]; 69 | if (self) { 70 | _interceptedProtocols = [arrayOfInterceptedProtocols copy]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)dealloc { 76 | _interceptedProtocols = nil; 77 | } 78 | 79 | - (BOOL)isSelectorContainedInInterceptedProtocols:(SEL)aSelector{ 80 | __block BOOL isSelectorContainedInInterceptedProtocols = NO; 81 | [self.interceptedProtocols enumerateObjectsUsingBlock:^(Protocol * protocol, NSUInteger idx, BOOL *stop) { 82 | isSelectorContainedInInterceptedProtocols = selector_belongsToProtocol(aSelector, protocol); 83 | * stop = isSelectorContainedInInterceptedProtocols; 84 | }]; 85 | return isSelectorContainedInInterceptedProtocols; 86 | } 87 | 88 | @end 89 | 90 | BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol) { 91 | // Reference: https://gist.github.com/numist/3838169 92 | for (int optionbits = 0; optionbits < (1 << 2); optionbits++) { 93 | BOOL required = optionbits & 1; 94 | BOOL instance = !(optionbits & (1 << 1)); 95 | 96 | struct objc_method_description hasMethod = protocol_getMethodDescription(protocol, selector, required, instance); 97 | if (hasMethod.name || hasMethod.types) { 98 | return YES; 99 | } 100 | } 101 | 102 | return NO; 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | macOS Rich Text Editor 2 | ================== 3 | 4 | The macOS Rich Text Editor library allows for rich text editing via a native `NSTextView`. You will need to implement much of the UI yourself (buttons, handling selection changes via the delegate protocol, etc.). The RTE just handles the bold/italic/bulleted lists/etc. formatting for you. The sample should give you some guidance on how this could be accomplished. 5 | 6 | To use this library, you only need eight files: 7 | 8 | - RichTextEditor.h/m 9 | - NSFont+RichTextEditor.h/m 10 | - NSAttributedString+RichTextEditor.h/m 11 | - WZProtocolInterceptor.h/m 12 | 13 | You can copy these files directly into your project, or you can choose to build and use the `.framework` output. Remember to open the `.xcworkspace` file when exploring this project. 14 | 15 | This library is based upon Deadpikle's [iOS Rich Text Editor](https://github.com/Deadpikle/iOS-Rich-Text-Editor), which was edited from the [original iOS rich text editor](https://github.com/aryaxt/iOS-Rich-Text-Editor) by [aryaxt](https://github.com/aryaxt). 16 | 17 | ### Features: 18 | 19 | - Bold 20 | - Italic 21 | - Underline 22 | - Font 23 | - Font size 24 | - Text background color 25 | - Text foreground color 26 | - Text alignment 27 | - Paragraph indent/outdent 28 | - Bulleted lists 29 | 30 | ### Compatibility 31 | 32 | The rich text editor is compatible with macOS 10.10+. It might work on older versions, but this has not been tested. 33 | 34 | #### Keyboard Shortcuts 35 | 36 | | Shortcut | Action | 37 | | ------------- | ------------- | 38 | | ⌘ + B | Toggle bold | 39 | | ⌘ + I | Toggle italic | 40 | | ⌘ + U | Toggle underline | 41 | | ⌘ + ⇧ + > | Increase font size | 42 | | ⌘ + ⇧ + < | Decrease font size | 43 | | ⌘ + ⇧ + L | Toggle bulleted list | 44 | | ⌘ + ⇧ + N | If in bulleted list, leave bulleted list | 45 | | ⌘ + ⇧ + T | Decrease indent | 46 | | ⌘ + T | Increase indent | 47 | 48 | By default, all keyboard shortcuts are enabled. If you want to selectively enable some keyboard shortcuts, implement the `RichTextEditorDataSource` method `- (RichTextEditorShortcut)enabledKeyboardShortcuts`. If you want to do this, don't forget to set the `rteDataSource`! 49 | 50 | #### Scaling Text [TODO: move to Wiki] 51 | 52 | If you want to scale text, you can use code similar to the following (based on http://stackoverflow.com/a/14113905/3938401): 53 | ``` 54 | // http://stackoverflow.com/a/14113905/3938401 55 | @interface ... 56 | @property CGFloat scaleFactor; 57 | @end 58 | 59 | @implementation ... 60 | 61 | -(void)viewDidLoad { 62 | self.scaleFactor = 1.0f; 63 | ... 64 | } 65 | 66 | - (void)setScaleFactor:(CGFloat)newScaleFactor adjustPopup:(BOOL)flag { 67 | CGFloat oldScaleFactor = self.scaleFactor; 68 | if (self.scaleFactor != newScaleFactor) { 69 | NSSize curDocFrameSize, newDocBoundsSize; 70 | NSView *clipView = [self.notesTextView superview]; 71 | self.scaleFactor = newScaleFactor; 72 | // Get the frame. The frame must stay the same. 73 | curDocFrameSize = [clipView frame].size; 74 | // The new bounds will be frame divided by scale factor 75 | newDocBoundsSize.width = curDocFrameSize.width / self.scaleFactor; 76 | newDocBoundsSize.height = curDocFrameSize.height / self.scaleFactor; 77 | } 78 | self.scaleFactor = newScaleFactor; 79 | [self scaleChanged:oldScaleFactor newScale:newScaleFactor]; 80 | } 81 | 82 | - (void)scaleChanged:(CGFloat)oldScale newScale:(CGFloat)newScale { 83 | CGFloat scaler = newScale / oldScale; 84 | [self.notesTextView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)]; 85 | // For some reason, even after ensuring the layout and displaying, the wrapping doesn't update until text is messed 86 | // with. This workaround "fixes" that. Since we need it anyway, I removed the ensureLayoutForTextContainer: 87 | // (from the SO post) and the documentation-implied [self.notesTextView display] calls. 88 | [[self.notesTextView textStorage] appendAttributedString:[[NSAttributedString alloc] initWithString:@""]]; 89 | } 90 | 91 | @end 92 | ``` 93 | 94 | 95 | Credits 96 | ------------------------- 97 | 98 | Original Rich Text Editor code by [aryaxt](https://github.com/aryaxt) at the [iOS Rich Text Editor repo](https://github.com/aryaxt/iOS-Rich-Text-Editor). `WZProtocolInterceptor` is from [this SO post](http://stackoverflow.com/a/18777565/3938401). 99 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/Categories/NSFont+RichTextEditor.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+RichTextEditor.m 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | #import "NSFont+RichTextEditor.h" 32 | 33 | @implementation NSFont (RichTextEditor) 34 | 35 | + (NSString *)postscriptNameFromFullName:(NSString *)fullName { 36 | // avoid error with "All system UI font access should be through proper APIs..." 37 | // by bailing early if the user gets here with a system font 38 | if ([fullName containsString:@"SFNS"]) { 39 | return fullName; 40 | } 41 | NSFont *font = [NSFont fontWithName:fullName size:1]; 42 | return (__bridge NSString *)(CTFontCopyPostScriptName((__bridge CTFontRef)(font))); 43 | } 44 | 45 | + (NSFont *)fontWithName:(NSString *)name size:(CGFloat)size boldTrait:(BOOL)isBold italicTrait:(BOOL)isItalic { 46 | // avoid error with "All system UI font access should be through proper APIs..." 47 | // by bailing early if the user gets here with a system font 48 | if ([name containsString:@"SFNS"]) { 49 | NSFontManager *fontManager = [NSFontManager sharedFontManager]; 50 | NSFont *sysFont = [NSFont systemFontOfSize:size]; 51 | if (isItalic) { 52 | sysFont = [fontManager convertFont:sysFont toHaveTrait:NSFontItalicTrait]; 53 | } 54 | if (isBold) { 55 | sysFont = [fontManager convertFont:sysFont toHaveTrait:NSFontBoldTrait]; 56 | } 57 | return sysFont; 58 | } 59 | NSString *postScriptName = [NSFont postscriptNameFromFullName:name]; 60 | 61 | CTFontRef fontWithoutTrait = CTFontCreateWithName((__bridge CFStringRef)(postScriptName), size, NULL); 62 | CTFontSymbolicTraits traits = 0; 63 | CTFontRef newFontRef; 64 | 65 | if (isItalic) { 66 | traits |= kCTFontItalicTrait; 67 | } 68 | 69 | if (isBold) { 70 | traits |= kCTFontBoldTrait; 71 | } 72 | 73 | if (traits == 0) { 74 | newFontRef = CTFontCreateCopyWithAttributes(fontWithoutTrait, 0.0, NULL, NULL); 75 | } 76 | else { 77 | newFontRef = CTFontCreateCopyWithSymbolicTraits(fontWithoutTrait, 0.0, NULL, traits, traits); 78 | } 79 | 80 | if (fontWithoutTrait) { 81 | CFRelease(fontWithoutTrait); 82 | } 83 | 84 | if (newFontRef) { 85 | NSString *fontNameKey = (__bridge NSString *)(CTFontCopyName(newFontRef, kCTFontPostScriptNameKey)); 86 | CGFloat size = CTFontGetSize(newFontRef); 87 | CFRelease(newFontRef); 88 | return [NSFont fontWithName:fontNameKey size:size]; 89 | } 90 | 91 | return nil; 92 | } 93 | 94 | - (NSFont *)fontWithBoldTrait:(BOOL)bold italicTrait:(BOOL)italic andSize:(CGFloat)size { 95 | CTFontRef fontRef = (__bridge CTFontRef)self; 96 | NSString *familyName = (__bridge NSString *)(CTFontCopyName(fontRef, kCTFontFamilyNameKey)); 97 | NSString *postScriptName = [NSFont postscriptNameFromFullName:familyName]; 98 | return [[self class] fontWithName:postScriptName size:size boldTrait:bold italicTrait:italic]; 99 | } 100 | 101 | - (NSFont *)fontWithBoldTrait:(BOOL)bold andItalicTrait:(BOOL)italic { 102 | return [self fontWithBoldTrait:bold italicTrait:italic andSize:self.pointSize]; 103 | } 104 | 105 | - (BOOL)isBold { 106 | CTFontSymbolicTraits trait = CTFontGetSymbolicTraits((__bridge CTFontRef)self); 107 | if ((trait & kCTFontTraitBold) == kCTFontTraitBold) { 108 | return YES; 109 | } 110 | 111 | return NO; 112 | } 113 | 114 | - (BOOL)isItalic { 115 | CTFontSymbolicTraits trait = CTFontGetSymbolicTraits((__bridge CTFontRef)self); 116 | if ((trait & kCTFontTraitItalic) == kCTFontTraitItalic) { 117 | return YES; 118 | } 119 | 120 | return NO; 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // macOSRTESample 4 | // 5 | // Created by Deadpikle on 3/28/18. 6 | // Copyright © 2018 Pikle Productions. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | 12 | @interface NSImage (Tint) 13 | 14 | - (NSImage *)imageTintedWithColor:(NSColor *)tint; 15 | 16 | @end 17 | 18 | @implementation NSImage (Tint) 19 | 20 | // https://stackoverflow.com/a/16138027/3938401 21 | - (NSImage *)imageTintedWithColor:(NSColor *)tint { 22 | NSImage *image = [self copy]; 23 | if (tint) { 24 | [image lockFocus]; 25 | [tint set]; 26 | NSRect imageRect = {NSZeroPoint, [image size]}; 27 | NSRectFillUsingOperation(imageRect, NSCompositingOperationSourceAtop); 28 | [image unlockFocus]; 29 | } 30 | return image; 31 | } 32 | 33 | @end 34 | 35 | @interface ViewController () 36 | 37 | @property (unsafe_unretained) IBOutlet RichTextEditor *richTextEditor; 38 | 39 | @property (weak) IBOutlet NSButton *boldButton; 40 | @property (weak) IBOutlet NSButton *italicButton; 41 | @property (weak) IBOutlet NSButton *underlineButton; 42 | @property (weak) IBOutlet NSButton *bulletedListButton; 43 | @property (weak) IBOutlet NSButton *decreaseIndentButton; 44 | @property (weak) IBOutlet NSButton *increaseIndentButton; 45 | @property (weak) IBOutlet NSColorWell *fontColorWell; 46 | @property (weak) IBOutlet NSColorWell *highlightColorWell; 47 | @property (weak) IBOutlet NSButton *tabKeyAlwaysIndentsButton; 48 | 49 | -(IBAction)toggleBold:(id)sender; 50 | -(IBAction)toggleItalic:(id)sender; 51 | -(IBAction)toggleUnderline:(id)sender; 52 | 53 | -(IBAction)toggleBulletedList:(id)sender; 54 | 55 | -(IBAction)decreaseIndent:(id)sender; 56 | -(IBAction)increaseIndent:(id)sender; 57 | 58 | -(IBAction)decreaseFontSize:(id)sender; 59 | -(IBAction)increaseFontSize:(id)sender; 60 | 61 | - (IBAction)fontColorChanged:(id)sender; 62 | - (IBAction)highlightColorChanged:(id)sender; 63 | 64 | - (IBAction)tabAlwaysIndentsChecked:(id)sender; 65 | 66 | @end 67 | 68 | @implementation ViewController 69 | 70 | - (void)viewDidLoad { 71 | [super viewDidLoad]; 72 | self.richTextEditor.rteDelegate = self; 73 | } 74 | 75 | -(IBAction)toggleBold:(id)sender { 76 | [self.richTextEditor userSelectedBold]; 77 | } 78 | 79 | -(IBAction)toggleItalic:(id)sender { 80 | [self.richTextEditor userSelectedItalic]; 81 | } 82 | 83 | -(IBAction)toggleUnderline:(id)sender { 84 | [self.richTextEditor userSelectedUnderline]; 85 | } 86 | 87 | -(IBAction)toggleBulletedList:(id)sender { 88 | [self.richTextEditor userSelectedBullet]; 89 | } 90 | 91 | -(IBAction)decreaseIndent:(id)sender { 92 | [self.richTextEditor userSelectedDecreaseIndent]; 93 | } 94 | 95 | -(IBAction)increaseIndent:(id)sender { 96 | [self.richTextEditor userSelectedIncreaseIndent]; 97 | } 98 | 99 | -(IBAction)decreaseFontSize:(id)sender { 100 | [self.richTextEditor decreaseFontSize]; 101 | } 102 | 103 | -(IBAction)increaseFontSize:(id)sender { 104 | [self.richTextEditor increaseFontSize]; 105 | } 106 | 107 | - (IBAction)fontColorChanged:(id)sender { 108 | [self.richTextEditor userSelectedTextColor:self.fontColorWell.color]; 109 | } 110 | 111 | - (IBAction)highlightColorChanged:(id)sender { 112 | [self.richTextEditor userSelectedTextBackgroundColor:self.highlightColorWell.color]; 113 | } 114 | 115 | - (void)richTextEditor:(RichTextEditor*)editor changeAboutToOccurOfType:(RichTextEditorPreviewChange)type { 116 | NSLog(@"User just edited the RTE by performing this operation: %@", [RichTextEditor convertPreviewChangeTypeToString:type withNonSpecialChangeText:YES]); 117 | } 118 | 119 | -(void)selectionForEditor:(RichTextEditor*)editor changedTo:(NSRange)range isBold:(BOOL)isBold isItalic:(BOOL)isItalic isUnderline:(BOOL)isUnderline isInBulletedList:(BOOL)isInBulletedList textBackgroundColor:(NSColor*)textBackgroundColor textColor:(NSColor*)textColor { 120 | if (isBold) { 121 | self.boldButton.image = [[NSImage imageNamed:@"bold"] imageTintedWithColor:NSColor.blueColor]; 122 | } 123 | else { 124 | self.boldButton.image = [[NSImage imageNamed:@"bold"] imageTintedWithColor:NSColor.blackColor]; 125 | } 126 | if (isItalic) { 127 | self.italicButton.image = [[NSImage imageNamed:@"italic"] imageTintedWithColor:NSColor.blueColor]; 128 | } 129 | else { 130 | self.italicButton.image = [[NSImage imageNamed:@"italic"] imageTintedWithColor:NSColor.blackColor]; 131 | } 132 | if (isUnderline) { 133 | self.underlineButton.image = [[NSImage imageNamed:@"underline"] imageTintedWithColor:NSColor.blueColor]; 134 | } 135 | else { 136 | self.underlineButton.image = [[NSImage imageNamed:@"underline"] imageTintedWithColor:NSColor.blackColor]; 137 | } 138 | if (isInBulletedList) { 139 | self.bulletedListButton.image = [[NSImage imageNamed:@"bulleted-list"] imageTintedWithColor:NSColor.blueColor]; 140 | } 141 | else { 142 | self.bulletedListButton.image = [[NSImage imageNamed:@"bulleted-list"] imageTintedWithColor:NSColor.blackColor]; 143 | } 144 | self.fontColorWell.color = textColor; 145 | } 146 | 147 | - (IBAction)tabAlwaysIndentsChecked:(id)sender { 148 | self.richTextEditor.tabKeyAlwaysIndentsOutdents = self.tabKeyAlwaysIndentsButton.state == NSOnState; 149 | } 150 | 151 | @end 152 | -------------------------------------------------------------------------------- /Sample/macOSRTESample/macOSRichTextEditor.framework/Versions/A/Headers/RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | // Make sure to call removeTextObserverForDealloc before going away (forever) from the screen with the RTE (TODO: We should find a better way for this and fix it!) 32 | 33 | // TODO: better documentation 34 | // TODO: Clean up, clean up, everybody do your share! 35 | 36 | #import 37 | 38 | @class RichTextEditor; 39 | @protocol RichTextEditorDataSource 40 | @optional 41 | - (NSArray *)fontSizeSelectionForRichTextEditor:(RichTextEditor *)richTextEditor; 42 | - (NSArray *)fontFamilySelectionForRichTextEditor:(RichTextEditor *)richTextEditor; 43 | - (BOOL)shouldDisplayToolbarForRichTextEditor:(RichTextEditor *)richTextEditor; 44 | - (BOOL)shouldDisplayRichTextOptionsInMenuControllerForRichTextEditor:(RichTextEditor *)richTextEditor; 45 | - (NSUInteger)levelsOfUndo; 46 | @end 47 | 48 | // These values will always start from 0 and go up. If you want to add your own 49 | // preview changes via a subclass, start from 9999 and go down (or similar) and 50 | // override convertPreviewChangeTypeToString:withNonSpecialChangeText: 51 | typedef NS_ENUM(NSInteger, RichTextEditorPreviewChange) { 52 | RichTextEditorPreviewChangeBold = 0, 53 | RichTextEditorPreviewChangeItalic = 1, 54 | RichTextEditorPreviewChangeUnderline = 2, 55 | RichTextEditorPreviewChangeFontResize = 3, 56 | RichTextEditorPreviewChangeHighlight = 4, 57 | RichTextEditorPreviewChangeFontSize = 5, 58 | RichTextEditorPreviewChangeFontColor = 6, 59 | RichTextEditorPreviewChangeIndentIncrease = 7, 60 | RichTextEditorPreviewChangeIndentDecrease = 8, 61 | RichTextEditorPreviewChangeCut = 9, 62 | RichTextEditorPreviewChangePaste = 10, 63 | RichTextEditorPreviewChangeSpace = 11, 64 | RichTextEditorPreviewChangeEnter = 12, 65 | RichTextEditorPreviewChangeBullet = 13, 66 | RichTextEditorPreviewChangeMouseDown = 14, 67 | RichTextEditorPreviewChangeArrowKey = 15, 68 | RichTextEditorPreviewChangeKeyDown = 16, 69 | RichTextEditorPreviewChangeDelete = 17, 70 | RichTextEditorPreviewChangeFindReplace = 18 71 | }; 72 | 73 | @protocol RichTextEditorDelegate 74 | 75 | @required 76 | 77 | -(void)selectionForEditor:(RichTextEditor*)editor changedTo:(NSRange)range isBold:(BOOL)isBold isItalic:(BOOL)isItalic isUnderline:(BOOL)isUnderline isInBulletedList:(BOOL)isInBulletedList textBackgroundColor:(NSColor*)textBackgroundColor textColor:(NSColor*)textColor; 78 | 79 | @optional 80 | 81 | - (BOOL)richTextEditor:(RichTextEditor*)editor keyDownEvent:(NSEvent*)event; // return YES if handled by delegate, NO if RTE should process it 82 | 83 | - (BOOL)handlesUndoRedoForText; 84 | - (void)userPerformedUndo; // TODO: remove? 85 | - (void)userPerformedRedo; // TODO: remove? 86 | 87 | - (void)richTextEditor:(RichTextEditor*)editor changeAboutToOccurOfType:(RichTextEditorPreviewChange)type; 88 | 89 | @end 90 | 91 | typedef NS_ENUM(NSInteger, ParagraphIndentation) { 92 | ParagraphIndentationIncrease, 93 | ParagraphIndentationDecrease 94 | }; 95 | 96 | @interface RichTextEditor : NSTextView 97 | 98 | @property (assign) IBOutlet id rteDataSource; 99 | @property (assign) IBOutlet id rteDelegate; 100 | @property (nonatomic, assign) CGFloat defaultIndentationSize; 101 | @property (nonatomic, readonly) unichar lastSingleKeyPressed; 102 | 103 | // If YES, only pastes text as rich text if the copy operation came from this class. 104 | // Note: not this *object* -- this class (so other RichTextEditor boxes can paste 105 | // between each other). If the text did not come from a RichTextEditor box, then 106 | // pastes as plain text. 107 | // If NO, performs the default paste: operation. 108 | // Defaults to YES. 109 | @property BOOL allowsRichTextPasteOnlyFromThisClass; 110 | 111 | +(NSString*)pasteboardDataType; 112 | 113 | // call these methods when the user does the given action (clicks bold button, etc.) 114 | - (void)userSelectedBold; 115 | - (void)userSelectedItalic; 116 | - (void)userSelectedUnderline; 117 | - (void)userSelectedBullet; 118 | - (void)userSelectedIncreaseIndent; 119 | - (void)userSelectedDecreaseIndent; 120 | - (void)userSelectedTextBackgroundColor:(NSColor*)color; 121 | - (void)userSelectedTextColor:(NSColor*)color; 122 | 123 | - (void)undo; 124 | - (void)redo; 125 | 126 | - (void)userChangedToFontName:(NSString*)fontName; 127 | - (void)userChangedToFontSize:(NSNumber*)fontSize; 128 | 129 | - (void)increaseFontSize; 130 | - (void)decreaseFontSize; 131 | 132 | - (void)userSelectedParagraphFirstLineHeadIndent; 133 | - (void)userSelectedTextAlignment:(NSTextAlignment)textAlignment; 134 | 135 | - (BOOL)hasSelection; // convenience method; YES if user has something selected 136 | 137 | - (void)changeToAttributedString:(NSAttributedString*)string; 138 | 139 | - (void)setBorderColor:(NSColor*)borderColor; 140 | - (void)setBorderWidth:(CGFloat)borderWidth; 141 | 142 | - (NSString *)htmlString; 143 | - (void)setHtmlString:(NSString *)htmlString; 144 | 145 | - (NSString*)bulletString; 146 | 147 | + (NSString *)htmlStringFromAttributedText:(NSAttributedString*)text; 148 | + (NSAttributedString*)attributedStringFromHTMLString:(NSString *)htmlString; 149 | 150 | + (NSString *)convertPreviewChangeTypeToString:(RichTextEditorPreviewChange)changeType withNonSpecialChangeText:(BOOL)shouldReturnStringForNonSpecialType; 151 | 152 | // I'm not sure why you'd call these externally, but subclasses can make use of this for custom toolbar items or what have you. 153 | // It's just easier to put these in the public header than to have a protected/subclasses-only header. 154 | -(void)sendDelegatePreviewChangeOfType:(RichTextEditorPreviewChange)type; 155 | -(void)sendDelegateTVChanged; 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/Categories/NSAttributedString+RichTextEditor.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RichTextEditor.m 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in 20 | // all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | // THE SOFTWARE. 29 | 30 | #import "NSAttributedString+RichTextEditor.h" 31 | 32 | @implementation NSAttributedString (RichTextEditor) 33 | 34 | #pragma mark - Public Methods - 35 | 36 | - (NSRange)firstParagraphRangeFromTextRange:(NSRange)range { 37 | if (self.string.length == 0 || self.string.length < range.location) { 38 | return NSMakeRange(0, 0); 39 | } 40 | 41 | NSInteger start = -1; 42 | NSInteger end = -1; 43 | NSInteger length = 0; 44 | 45 | NSInteger startingRange = (range.location == self.string.length || [self.string characterAtIndex:range.location] == '\n') ? 46 | range.location-1 : 47 | range.location; 48 | 49 | for (NSInteger i = startingRange; i >= 0; i--) { 50 | char c = [self.string characterAtIndex:i]; 51 | if (c == '\n') { 52 | start = i+1; 53 | break; 54 | } 55 | } 56 | 57 | start = (start == -1) ? 0 : start; 58 | 59 | NSInteger moveForwardIndex = (range.location > start) ? range.location : start; 60 | 61 | for (NSInteger i = moveForwardIndex; i <= self.string.length - 1; i++) { 62 | char c = [self.string characterAtIndex:i]; 63 | if (c == '\n') 64 | { 65 | end = i; 66 | break; 67 | } 68 | } 69 | 70 | end = (end == -1) ? self.string.length : end; 71 | length = end - start; 72 | 73 | return NSMakeRange(start, length); 74 | } 75 | 76 | - (NSArray *)rangeOfParagraphsFromTextRange:(NSRange)textRange { 77 | NSMutableArray *paragraphRanges = [NSMutableArray array]; 78 | NSInteger rangeStartIndex = textRange.location; 79 | 80 | while (true) { 81 | NSRange range = [self firstParagraphRangeFromTextRange:NSMakeRange(rangeStartIndex, 0)]; 82 | rangeStartIndex = range.location + range.length + 1; 83 | 84 | [paragraphRanges addObject:[NSValue valueWithRange:range]]; 85 | 86 | if (range.location + range.length >= textRange.location + textRange.length) { 87 | break; 88 | } 89 | } 90 | 91 | return paragraphRanges; 92 | } 93 | 94 | - (NSString *)htmlString { 95 | NSMutableString *htmlString = [NSMutableString string]; 96 | NSArray *paragraphRanges = [self rangeOfParagraphsFromTextRange:NSMakeRange(0, self.string.length-1)]; 97 | 98 | for (int i = 0; i < paragraphRanges.count; i++) { 99 | NSValue *value = paragraphRanges[i]; 100 | NSRange range = [value rangeValue]; 101 | NSDictionary *paragraphDictionary = [self attributesAtIndex:range.location effectiveRange:nil]; 102 | NSParagraphStyle *paragraphStyle = [paragraphDictionary objectForKey:NSParagraphStyleAttributeName]; 103 | NSString *textAlignmentString = [self htmlTextAlignmentString:paragraphStyle.alignment]; 104 | 105 | [htmlString appendString:@"

0) { 114 | [htmlString appendFormat:@"text-indent:%.0fpx; ", paragraphStyle.firstLineHeadIndent - paragraphStyle.headIndent]; 115 | } 116 | 117 | if (paragraphStyle.headIndent > 0) { 118 | [htmlString appendFormat:@"margin-left:%.0fpx; ", paragraphStyle.headIndent]; 119 | } 120 | 121 | 122 | [htmlString appendString:@" \">"]; 123 | 124 | [self enumerateAttributesInRange:range 125 | options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired 126 | usingBlock:^(NSDictionary *dictionary, NSRange range, BOOL *stop){ 127 | 128 | NSMutableString *fontString = [NSMutableString string]; 129 | NSFont *font = [dictionary objectForKey:NSFontAttributeName]; 130 | NSColor *foregroundColor = [dictionary objectForKey:NSForegroundColorAttributeName]; 131 | NSColor *backGroundColor = [dictionary objectForKey:NSBackgroundColorAttributeName]; 132 | NSNumber *underline = [dictionary objectForKey:NSUnderlineStyleAttributeName]; 133 | BOOL hasUnderline = (!underline || underline.intValue == NSUnderlineStyleNone) ? NO :YES; 134 | NSNumber *strikeThrough = [dictionary objectForKey:NSStrikethroughStyleAttributeName]; 135 | BOOL hasStrikeThrough = (!strikeThrough || strikeThrough.intValue == NSUnderlineStyleNone) ? NO :YES; 136 | 137 | [fontString appendFormat:@""]; 157 | [fontString appendString:[[self.string substringFromIndex:range.location] substringToIndex:range.length]]; 158 | [fontString appendString:@""]; 159 | if ([font isBold]) { 160 | [fontString insertString:@"" atIndex:0]; 161 | [fontString insertString:@"" atIndex:fontString.length]; 162 | } 163 | 164 | if ([font isItalic]) { 165 | [fontString insertString:@"" atIndex:0]; 166 | [fontString insertString:@"" atIndex:fontString.length]; 167 | } 168 | 169 | if (hasUnderline) { 170 | [fontString insertString:@"" atIndex:0]; 171 | [fontString insertString:@"" atIndex:fontString.length]; 172 | } 173 | 174 | if (hasStrikeThrough) { 175 | [fontString insertString:@"" atIndex:0]; 176 | [fontString insertString:@"" atIndex:fontString.length]; 177 | } 178 | 179 | [htmlString appendString:fontString]; 180 | }]; 181 | [htmlString appendString:@"

"]; 182 | } 183 | 184 | return htmlString; 185 | } 186 | 187 | #pragma mark - Helper Methods - 188 | 189 | - (NSString *)htmlTextAlignmentString:(NSTextAlignment)textAlignment { 190 | switch (textAlignment) { 191 | case NSLeftTextAlignment: 192 | return @"left"; 193 | case NSCenterTextAlignment: 194 | return @"center"; 195 | case NSRightTextAlignment: 196 | return @"right"; 197 | case NSJustifiedTextAlignment: 198 | return @"justify"; 199 | default: 200 | return nil; 201 | } 202 | } 203 | 204 | - (NSString *)htmlRgbColor:(NSColor *)color { 205 | CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0; 206 | [color getRed:&red green:&green blue:&blue alpha:&alpha]; 207 | return [NSString stringWithFormat:@"rgb(%d,%d,%d)",(int)(red*255.0), (int)(green*255.0), (int)(blue*255.0)]; 208 | } 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/RichTextEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | // TODO: better documentation 32 | // TODO: Clean up, clean up, everybody do your share! 33 | 34 | #import 35 | 36 | @class RichTextEditor; 37 | 38 | // These values will always start from 0 and go up. If you want to add your own 39 | // preview changes via a subclass, start from 9999 and go down (or similar) and 40 | // override convertPreviewChangeTypeToString:withNonSpecialChangeText: 41 | typedef NS_ENUM(NSInteger, RichTextEditorPreviewChange) { 42 | RichTextEditorPreviewChangeBold = 0, 43 | RichTextEditorPreviewChangeItalic = 1, 44 | RichTextEditorPreviewChangeUnderline = 2, 45 | RichTextEditorPreviewChangeFontResize = 3, 46 | RichTextEditorPreviewChangeHighlight = 4, 47 | RichTextEditorPreviewChangeFontSize = 5, 48 | RichTextEditorPreviewChangeFontColor = 6, 49 | RichTextEditorPreviewChangeIndentIncrease = 7, 50 | RichTextEditorPreviewChangeIndentDecrease = 8, 51 | RichTextEditorPreviewChangeCut = 9, 52 | RichTextEditorPreviewChangePaste = 10, 53 | RichTextEditorPreviewChangeSpace = 11, 54 | RichTextEditorPreviewChangeEnter = 12, 55 | RichTextEditorPreviewChangeBullet = 13, 56 | RichTextEditorPreviewChangeMouseDown = 14, 57 | RichTextEditorPreviewChangeArrowKey = 15, 58 | RichTextEditorPreviewChangeKeyDown = 16, 59 | RichTextEditorPreviewChangeDelete = 17, 60 | RichTextEditorPreviewChangeFindReplace = 18 61 | }; 62 | 63 | typedef NS_OPTIONS(NSUInteger, RichTextEditorShortcut) { 64 | RichTextEditorShortcutAll = 0, 65 | RichTextEditorShortcutBold = 1 << 0, 66 | RichTextEditorShortcutItalic = 1 << 1, 67 | RichTextEditorShortcutUnderline = 1 << 2, 68 | RichTextEditorShortcutIncreaseFontSize = 1 << 3, 69 | RichTextEditorShortcutDecreaseFontSize = 1 << 4, 70 | RichTextEditorShortcutBulletedList = 1 << 6, 71 | RichTextEditorShortcutLeaveBulletedList = 1 << 7, 72 | RichTextEditorShortcutDecreaseIndent = 1 << 8, 73 | RichTextEditorShortcutIncreaseIndent = 1 << 9 74 | }; 75 | 76 | 77 | @protocol RichTextEditorDataSource 78 | 79 | @optional 80 | 81 | - (NSUInteger)levelsOfUndo; 82 | 83 | /// If you do not want to enable all keyboard shortcuts (e.g. if you don't want users to resize font ever), 84 | /// then you can use this data source callback to selectively enable keyboard shortcuts. 85 | - (RichTextEditorShortcut)enabledKeyboardShortcuts; 86 | 87 | @end 88 | 89 | @protocol RichTextEditorDelegate 90 | 91 | @required 92 | 93 | -(void)selectionForEditor:(RichTextEditor*)editor changedTo:(NSRange)range isBold:(BOOL)isBold isItalic:(BOOL)isItalic isUnderline:(BOOL)isUnderline isInBulletedList:(BOOL)isInBulletedList textBackgroundColor:(NSColor*)textBackgroundColor textColor:(NSColor*)textColor; 94 | 95 | @optional 96 | 97 | - (BOOL)richTextEditor:(RichTextEditor*)editor keyDownEvent:(NSEvent*)event; // return YES if handled by delegate, NO if RTE should process it 98 | 99 | - (BOOL)handlesUndoRedoForText; 100 | - (void)userPerformedUndo; // TODO: remove? 101 | - (void)userPerformedRedo; // TODO: remove? 102 | 103 | - (void)richTextEditor:(RichTextEditor*)editor changeAboutToOccurOfType:(RichTextEditorPreviewChange)type; 104 | 105 | @end 106 | 107 | @interface RichTextEditor : NSTextView 108 | 109 | @property (assign) IBOutlet id rteDataSource; 110 | @property (assign) IBOutlet id rteDelegate; 111 | @property (nonatomic, assign) CGFloat defaultIndentationSize; 112 | @property (nonatomic, readonly) unichar lastSingleKeyPressed; 113 | 114 | /// If YES, only pastes text as rich text if the copy operation came from this class. 115 | /// Note: not this *object* -- this class (so other RichTextEditor boxes can paste 116 | /// between each other). If the text did not come from a RichTextEditor box, then 117 | /// pastes as plain text. 118 | /// If NO, performs the default paste: operation. 119 | /// Defaults to YES. 120 | @property BOOL allowsRichTextPasteOnlyFromThisClass; 121 | 122 | /// Amount to change font size on each increase/decrease font size call. 123 | /// Defaults to 10.0f 124 | @property CGFloat fontSizeChangeAmount; 125 | 126 | /// Maximum font size. Defaults to 128.0f. 127 | @property CGFloat maxFontSize; 128 | 129 | /// Minimum font size. Defaults to 10.0f. 130 | @property CGFloat minFontSize; 131 | 132 | /// true if tab should always indent and shift+tab should always outdent the current paragraph(s); 133 | /// false to let the tab key be used as normal 134 | @property BOOL tabKeyAlwaysIndentsOutdents; 135 | 136 | /// Pasteboard type string used when copying text from this NSTextView. 137 | +(NSString*)pasteboardDataType; 138 | 139 | /// Call the following methods when the user does the given action (clicks bold button, etc.) 140 | 141 | /// Toggle bold. 142 | - (void)userSelectedBold; 143 | 144 | /// Toggle italic. 145 | - (void)userSelectedItalic; 146 | 147 | /// Toggle underline. 148 | - (void)userSelectedUnderline; 149 | 150 | /// Toggle bulleted list. 151 | - (void)userSelectedBullet; 152 | 153 | /// Increase the total indentation of the current paragraph. 154 | - (void)userSelectedIncreaseIndent; 155 | 156 | /// Decrease the total indentation of the current paragraph. 157 | - (void)userSelectedDecreaseIndent; 158 | 159 | /// Change the text background (highlight) color for the currently selected text. 160 | - (void)userSelectedTextBackgroundColor:(NSColor*)color; 161 | 162 | /// Change the text color for the currently selected text. 163 | - (void)userSelectedTextColor:(NSColor*)color; 164 | 165 | /// Perform an undo operation if one is available. 166 | - (void)undo; 167 | 168 | /// Perform a redo operation if one is available. 169 | - (void)redo; 170 | 171 | /// Convert the font for all text to the given font while keeping bold/italic attributes 172 | - (void)changeToFont:(NSFont*)font; 173 | 174 | /// Change the currently selected text to the given font name. 175 | - (void)userChangedToFontName:(NSString*)fontName; 176 | 177 | /// Change the currently selected text to the specified font size. 178 | - (void)userChangedToFontSize:(NSNumber*)fontSize; 179 | 180 | /// Increases the font size of the currently selected text by self.fontSizeChangeAmount. 181 | - (void)increaseFontSize; 182 | 183 | /// Decreases the font size of the currently selected text by self.fontSizeChangeAmount. 184 | - (void)decreaseFontSize; 185 | 186 | /// Toggles whether or not the paragraphs in the currently selected text have a first 187 | /// line head indent value of self.defaultIndentationSize. 188 | - (void)userSelectedParagraphFirstLineHeadIndent; 189 | 190 | /// Change the text alignment for the paragraphs in the currently selected text. 191 | - (void)userSelectedTextAlignment:(NSTextAlignment)textAlignment; 192 | 193 | /// Convenience method; YES if user has something selected (selection length > 0). 194 | - (BOOL)hasSelection; 195 | 196 | /// Changes the editor's contents to the given attributed string. 197 | - (void)changeToAttributedString:(NSAttributedString*)string; 198 | 199 | /// Convenience method to set the editor's border color. 200 | - (void)setBorderColor:(NSColor*)borderColor; 201 | 202 | /// Convenience method to set the editor's border width. 203 | - (void)setBorderWidth:(CGFloat)borderWidth; 204 | 205 | /// Converts the current NSAttributedString to an HTML string. 206 | - (NSString *)htmlString; 207 | 208 | /// Converts the provided htmlString into an NSAttributedString and then 209 | /// sets the editor's text to the attributed string. 210 | - (void)setHtmlString:(NSString *)htmlString; 211 | 212 | /// Grabs the NSString used as the bulleted list prefix. 213 | - (NSString*)bulletString; 214 | 215 | /// Converts the provided NSAttributedString into an HTML string. 216 | + (NSString *)htmlStringFromAttributedText:(NSAttributedString*)text; 217 | 218 | /// Converts the given HTML string into an NSAttributedString. 219 | + (NSAttributedString*)attributedStringFromHTMLString:(NSString *)htmlString; 220 | 221 | /// Converts a given RichTextEditorPreviewChange to a human-readable string 222 | + (NSString *)convertPreviewChangeTypeToString:(RichTextEditorPreviewChange)changeType withNonSpecialChangeText:(BOOL)shouldReturnStringForNonSpecialType; 223 | 224 | // // // // // // // // // // // // // // // // // // // // 225 | // I'm not sure why you'd call these externally, but subclasses can make use of this for custom toolbar items or what have you. 226 | // It's just easier to put these in the public header than to have a protected/subclasses-only header. 227 | -(void)sendDelegatePreviewChangeOfType:(RichTextEditorPreviewChange)type; 228 | -(void)sendDelegateTVChanged; 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Created by Aryan Gh on 5/4/13. 2 | Copyright (c) 2013 Aryan Ghassemi & Deadpikle. All rights reserved. 3 | 4 | https://github.com/aryaxt/iOS-Rich-Text-Editor 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | Modified by Deadpikle for bullet functionality and a few other fixes/improvements 25 | https://github.com/Deadpikle/macOS-Rich-Text-Editor 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in 34 | all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 42 | THE SOFTWARE. 43 | 44 | Google Material Design Icons: 45 | Attribution 4.0 International 46 | ======================================================================= 47 | Creative Commons Corporation ("Creative Commons") is not a law firm and 48 | does not provide legal services or legal advice. Distribution of 49 | Creative Commons public licenses does not create a lawyer-client or 50 | other relationship. Creative Commons makes its licenses and related 51 | information available on an "as-is" basis. Creative Commons gives no 52 | warranties regarding its licenses, any material licensed under their 53 | terms and conditions, or any related information. Creative Commons 54 | disclaims all liability for damages resulting from their use to the 55 | fullest extent possible. 56 | Using Creative Commons Public Licenses 57 | Creative Commons public licenses provide a standard set of terms and 58 | conditions that creators and other rights holders may use to share 59 | original works of authorship and other material subject to copyright 60 | and certain other rights specified in the public license below. The 61 | following considerations are for informational purposes only, are not 62 | exhaustive, and do not form part of our licenses. 63 | Considerations for licensors: Our public licenses are 64 | intended for use by those authorized to give the public 65 | permission to use material in ways otherwise restricted by 66 | copyright and certain other rights. Our licenses are 67 | irrevocable. Licensors should read and understand the terms 68 | and conditions of the license they choose before applying it. 69 | Licensors should also secure all rights necessary before 70 | applying our licenses so that the public can reuse the 71 | material as expected. Licensors should clearly mark any 72 | material not subject to the license. This includes other CC- 73 | licensed material, or material used under an exception or 74 | limitation to copyright. More considerations for licensors: 75 | wiki.creativecommons.org/Considerations_for_licensors 76 | Considerations for the public: By using one of our public 77 | licenses, a licensor grants the public permission to use the 78 | licensed material under specified terms and conditions. If 79 | the licensor's permission is not necessary for any reason--for 80 | example, because of any applicable exception or limitation to 81 | copyright--then that use is not regulated by the license. Our 82 | licenses grant only permissions under copyright and certain 83 | other rights that a licensor has authority to grant. Use of 84 | the licensed material may still be restricted for other 85 | reasons, including because others have copyright or other 86 | rights in the material. A licensor may make special requests, 87 | such as asking that all changes be marked or described. 88 | Although not required by our licenses, you are encouraged to 89 | respect those requests where reasonable. More_considerations 90 | for the public: 91 | wiki.creativecommons.org/Considerations_for_licensees 92 | ======================================================================= 93 | Creative Commons Attribution 4.0 International Public License 94 | By exercising the Licensed Rights (defined below), You accept and agree 95 | to be bound by the terms and conditions of this Creative Commons 96 | Attribution 4.0 International Public License ("Public License"). To the 97 | extent this Public License may be interpreted as a contract, You are 98 | granted the Licensed Rights in consideration of Your acceptance of 99 | these terms and conditions, and the Licensor grants You such rights in 100 | consideration of benefits the Licensor receives from making the 101 | Licensed Material available under these terms and conditions. 102 | Section 1 -- Definitions. 103 | a. Adapted Material means material subject to Copyright and Similar 104 | Rights that is derived from or based upon the Licensed Material 105 | and in which the Licensed Material is translated, altered, 106 | arranged, transformed, or otherwise modified in a manner requiring 107 | permission under the Copyright and Similar Rights held by the 108 | Licensor. For purposes of this Public License, where the Licensed 109 | Material is a musical work, performance, or sound recording, 110 | Adapted Material is always produced where the Licensed Material is 111 | synched in timed relation with a moving image. 112 | b. Adapter's License means the license You apply to Your Copyright 113 | and Similar Rights in Your contributions to Adapted Material in 114 | accordance with the terms and conditions of this Public License. 115 | c. Copyright and Similar Rights means copyright and/or similar rights 116 | closely related to copyright including, without limitation, 117 | performance, broadcast, sound recording, and Sui Generis Database 118 | Rights, without regard to how the rights are labeled or 119 | categorized. For purposes of this Public License, the rights 120 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 121 | Rights. 122 | d. Effective Technological Measures means those measures that, in the 123 | absence of proper authority, may not be circumvented under laws 124 | fulfilling obligations under Article 11 of the WIPO Copyright 125 | Treaty adopted on December 20, 1996, and/or similar international 126 | agreements. 127 | e. Exceptions and Limitations means fair use, fair dealing, and/or 128 | any other exception or limitation to Copyright and Similar Rights 129 | that applies to Your use of the Licensed Material. 130 | f. Licensed Material means the artistic or literary work, database, 131 | or other material to which the Licensor applied this Public 132 | License. 133 | g. Licensed Rights means the rights granted to You subject to the 134 | terms and conditions of this Public License, which are limited to 135 | all Copyright and Similar Rights that apply to Your use of the 136 | Licensed Material and that the Licensor has authority to license. 137 | h. Licensor means the individual(s) or entity(ies) granting rights 138 | under this Public License. 139 | i. Share means to provide material to the public by any means or 140 | process that requires permission under the Licensed Rights, such 141 | as reproduction, public display, public performance, distribution, 142 | dissemination, communication, or importation, and to make material 143 | available to the public including in ways that members of the 144 | public may access the material from a place and at a time 145 | individually chosen by them. 146 | j. Sui Generis Database Rights means rights other than copyright 147 | resulting from Directive 96/9/EC of the European Parliament and of 148 | the Council of 11 March 1996 on the legal protection of databases, 149 | as amended and/or succeeded, as well as other essentially 150 | equivalent rights anywhere in the world. 151 | k. You means the individual or entity exercising the Licensed Rights 152 | under this Public License. Your has a corresponding meaning. 153 | Section 2 -- Scope. 154 | a. License grant. 155 | 1. Subject to the terms and conditions of this Public License, 156 | the Licensor hereby grants You a worldwide, royalty-free, 157 | non-sublicensable, non-exclusive, irrevocable license to 158 | exercise the Licensed Rights in the Licensed Material to: 159 | a. reproduce and Share the Licensed Material, in whole or 160 | in part; and 161 | b. produce, reproduce, and Share Adapted Material. 162 | 2. Exceptions and Limitations. For the avoidance of doubt, where 163 | Exceptions and Limitations apply to Your use, this Public 164 | License does not apply, and You do not need to comply with 165 | its terms and conditions. 166 | 3. Term. The term of this Public License is specified in Section 167 | 6(a). 168 | 4. Media and formats; technical modifications allowed. The 169 | Licensor authorizes You to exercise the Licensed Rights in 170 | all media and formats whether now known or hereafter created, 171 | and to make technical modifications necessary to do so. The 172 | Licensor waives and/or agrees not to assert any right or 173 | authority to forbid You from making technical modifications 174 | necessary to exercise the Licensed Rights, including 175 | technical modifications necessary to circumvent Effective 176 | Technological Measures. For purposes of this Public License, 177 | simply making modifications authorized by this Section 2(a) 178 | (4) never produces Adapted Material. 179 | 5. Downstream recipients. 180 | a. Offer from the Licensor -- Licensed Material. Every 181 | recipient of the Licensed Material automatically 182 | receives an offer from the Licensor to exercise the 183 | Licensed Rights under the terms and conditions of this 184 | Public License. 185 | b. No downstream restrictions. You may not offer or impose 186 | any additional or different terms or conditions on, or 187 | apply any Effective Technological Measures to, the 188 | Licensed Material if doing so restricts exercise of the 189 | Licensed Rights by any recipient of the Licensed 190 | Material. 191 | 6. No endorsement. Nothing in this Public License constitutes or 192 | may be construed as permission to assert or imply that You 193 | are, or that Your use of the Licensed Material is, connected 194 | with, or sponsored, endorsed, or granted official status by, 195 | the Licensor or others designated to receive attribution as 196 | provided in Section 3(a)(1)(A)(i). 197 | b. Other rights. 198 | 1. Moral rights, such as the right of integrity, are not 199 | licensed under this Public License, nor are publicity, 200 | privacy, and/or other similar personality rights; however, to 201 | the extent possible, the Licensor waives and/or agrees not to 202 | assert any such rights held by the Licensor to the limited 203 | extent necessary to allow You to exercise the Licensed 204 | Rights, but not otherwise. 205 | 2. Patent and trademark rights are not licensed under this 206 | Public License. 207 | 3. To the extent possible, the Licensor waives any right to 208 | collect royalties from You for the exercise of the Licensed 209 | Rights, whether directly or through a collecting society 210 | under any voluntary or waivable statutory or compulsory 211 | licensing scheme. In all other cases the Licensor expressly 212 | reserves any right to collect such royalties. 213 | Section 3 -- License Conditions. 214 | Your exercise of the Licensed Rights is expressly made subject to the 215 | following conditions. 216 | a. Attribution. 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | a. retain the following if it is supplied by the Licensor 220 | with the Licensed Material: 221 | i. identification of the creator(s) of the Licensed 222 | Material and any others designated to receive 223 | attribution, in any reasonable manner requested by 224 | the Licensor (including by pseudonym if 225 | designated); 226 | ii. a copyright notice; 227 | iii. a notice that refers to this Public License; 228 | iv. a notice that refers to the disclaimer of 229 | warranties; 230 | v. a URI or hyperlink to the Licensed Material to the 231 | extent reasonably practicable; 232 | b. indicate if You modified the Licensed Material and 233 | retain an indication of any previous modifications; and 234 | c. indicate the Licensed Material is licensed under this 235 | Public License, and include the text of, or the URI or 236 | hyperlink to, this Public License. 237 | 2. You may satisfy the conditions in Section 3(a)(1) in any 238 | reasonable manner based on the medium, means, and context in 239 | which You Share the Licensed Material. For example, it may be 240 | reasonable to satisfy the conditions by providing a URI or 241 | hyperlink to a resource that includes the required 242 | information. 243 | 3. If requested by the Licensor, You must remove any of the 244 | information required by Section 3(a)(1)(A) to the extent 245 | reasonably practicable. 246 | 4. If You Share Adapted Material You produce, the Adapter's 247 | License You apply must not prevent recipients of the Adapted 248 | Material from complying with this Public License. 249 | Section 4 -- Sui Generis Database Rights. 250 | Where the Licensed Rights include Sui Generis Database Rights that 251 | apply to Your use of the Licensed Material: 252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 253 | to extract, reuse, reproduce, and Share all or a substantial 254 | portion of the contents of the database; 255 | b. if You include all or a substantial portion of the database 256 | contents in a database in which You have Sui Generis Database 257 | Rights, then the database in which You have Sui Generis Database 258 | Rights (but not its individual contents) is Adapted Material; and 259 | c. You must comply with the conditions in Section 3(a) if You Share 260 | all or a substantial portion of the contents of the database. 261 | For the avoidance of doubt, this Section 4 supplements and does not 262 | replace Your obligations under this Public License where the Licensed 263 | Rights include other Copyright and Similar Rights. 264 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 265 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 266 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 267 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 268 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 269 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 270 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 271 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 272 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 273 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 274 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 275 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 276 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 277 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 278 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 279 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 280 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 281 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 282 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 283 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 284 | c. The disclaimer of warranties and limitation of liability provided 285 | above shall be interpreted in a manner that, to the extent 286 | possible, most closely approximates an absolute disclaimer and 287 | waiver of all liability. 288 | Section 6 -- Term and Termination. 289 | a. This Public License applies for the term of the Copyright and 290 | Similar Rights licensed here. However, if You fail to comply with 291 | this Public License, then Your rights under this Public License 292 | terminate automatically. 293 | b. Where Your right to use the Licensed Material has terminated under 294 | Section 6(a), it reinstates: 295 | 1. automatically as of the date the violation is cured, provided 296 | it is cured within 30 days of Your discovery of the 297 | violation; or 298 | 2. upon express reinstatement by the Licensor. 299 | For the avoidance of doubt, this Section 6(b) does not affect any 300 | right the Licensor may have to seek remedies for Your violations 301 | of this Public License. 302 | c. For the avoidance of doubt, the Licensor may also offer the 303 | Licensed Material under separate terms or conditions or stop 304 | distributing the Licensed Material at any time; however, doing so 305 | will not terminate this Public License. 306 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 307 | License. 308 | Section 7 -- Other Terms and Conditions. 309 | a. The Licensor shall not be bound by any additional or different 310 | terms or conditions communicated by You unless expressly agreed. 311 | b. Any arrangements, understandings, or agreements regarding the 312 | Licensed Material not stated herein are separate from and 313 | independent of the terms and conditions of this Public License. 314 | Section 8 -- Interpretation. 315 | a. For the avoidance of doubt, this Public License does not, and 316 | shall not be interpreted to, reduce, limit, restrict, or impose 317 | conditions on any use of the Licensed Material that could lawfully 318 | be made without permission under this Public License. 319 | b. To the extent possible, if any provision of this Public License is 320 | deemed unenforceable, it shall be automatically reformed to the 321 | minimum extent necessary to make it enforceable. If the provision 322 | cannot be reformed, it shall be severed from this Public License 323 | without affecting the enforceability of the remaining terms and 324 | conditions. 325 | c. No term or condition of this Public License will be waived and no 326 | failure to comply consented to unless expressly agreed to by the 327 | Licensor. 328 | d. Nothing in this Public License constitutes or may be interpreted 329 | as a limitation upon, or waiver of, any privileges and immunities 330 | that apply to the Licensor or You, including from the legal 331 | processes of any jurisdiction or authority. 332 | ======================================================================= 333 | Creative Commons is not a party to its public licenses. 334 | Notwithstanding, Creative Commons may elect to apply one of its public 335 | licenses to material it publishes and in those instances will be 336 | considered the "Licensor." Except for the limited purpose of indicating 337 | that material is shared under a Creative Commons public license or as 338 | otherwise permitted by the Creative Commons policies published at 339 | creativecommons.org/policies, Creative Commons does not authorize the 340 | use of the trademark "Creative Commons" or any other trademark or logo 341 | of Creative Commons without its prior written consent including, 342 | without limitation, in connection with any unauthorized modifications 343 | to any of its public licenses or any other arrangements, 344 | understandings, or agreements concerning use of licensed material. For 345 | the avoidance of doubt, this paragraph does not form part of the public 346 | licenses. 347 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /Library/macOSRichTextEditor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 342F59DF206BF5D00045E75A /* macOSRichTextEditor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 342F59D5206BF5D00045E75A /* macOSRichTextEditor.framework */; }; 11 | 342F59E4206BF5D00045E75A /* macOSRichTextEditorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F59E3206BF5D00045E75A /* macOSRichTextEditorTests.m */; }; 12 | 342F59E6206BF5D00045E75A /* macOSRichTextEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = 342F59D8206BF5D00045E75A /* macOSRichTextEditor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 342F59F9206BF5FC0045E75A /* RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F59F0206BF5FC0045E75A /* RichTextEditor.m */; }; 14 | 342F59FA206BF5FC0045E75A /* WZProtocolInterceptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 342F59F1206BF5FC0045E75A /* WZProtocolInterceptor.h */; settings = {ATTRIBUTES = (Private, ); }; }; 15 | 342F59FB206BF5FC0045E75A /* RichTextEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = 342F59F2206BF5FC0045E75A /* RichTextEditor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 342F59FC206BF5FC0045E75A /* NSFont+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F59F4206BF5FC0045E75A /* NSFont+RichTextEditor.m */; }; 17 | 342F59FD206BF5FC0045E75A /* NSAttributedString+RichTextEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F59F5206BF5FC0045E75A /* NSAttributedString+RichTextEditor.m */; }; 18 | 342F59FE206BF5FC0045E75A /* NSAttributedString+RichTextEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = 342F59F6206BF5FC0045E75A /* NSAttributedString+RichTextEditor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 342F59FF206BF5FC0045E75A /* NSFont+RichTextEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = 342F59F7206BF5FC0045E75A /* NSFont+RichTextEditor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 342F5A00206BF5FC0045E75A /* WZProtocolInterceptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F59F8206BF5FC0045E75A /* WZProtocolInterceptor.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 342F59E0206BF5D00045E75A /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 342F59CC206BF5D00045E75A /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 342F59D4206BF5D00045E75A; 29 | remoteInfo = macOSRichTextEditor; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 342F59D5206BF5D00045E75A /* macOSRichTextEditor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = macOSRichTextEditor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 342F59D8206BF5D00045E75A /* macOSRichTextEditor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macOSRichTextEditor.h; sourceTree = ""; }; 36 | 342F59D9206BF5D00045E75A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 342F59DE206BF5D00045E75A /* macOSRichTextEditorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSRichTextEditorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 342F59E3206BF5D00045E75A /* macOSRichTextEditorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macOSRichTextEditorTests.m; sourceTree = ""; }; 39 | 342F59E5206BF5D00045E75A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 342F59F0206BF5FC0045E75A /* RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RichTextEditor.m; sourceTree = ""; }; 41 | 342F59F1206BF5FC0045E75A /* WZProtocolInterceptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WZProtocolInterceptor.h; sourceTree = ""; }; 42 | 342F59F2206BF5FC0045E75A /* RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RichTextEditor.h; sourceTree = ""; }; 43 | 342F59F4206BF5FC0045E75A /* NSFont+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFont+RichTextEditor.m"; sourceTree = ""; }; 44 | 342F59F5206BF5FC0045E75A /* NSAttributedString+RichTextEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+RichTextEditor.m"; sourceTree = ""; }; 45 | 342F59F6206BF5FC0045E75A /* NSAttributedString+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+RichTextEditor.h"; sourceTree = ""; }; 46 | 342F59F7206BF5FC0045E75A /* NSFont+RichTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFont+RichTextEditor.h"; sourceTree = ""; }; 47 | 342F59F8206BF5FC0045E75A /* WZProtocolInterceptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WZProtocolInterceptor.m; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 342F59D1206BF5D00045E75A /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | 342F59DB206BF5D00045E75A /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | 342F59DF206BF5D00045E75A /* macOSRichTextEditor.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 342F59CB206BF5D00045E75A = { 70 | isa = PBXGroup; 71 | children = ( 72 | 342F59D7206BF5D00045E75A /* macOSRichTextEditor */, 73 | 342F59E2206BF5D00045E75A /* macOSRichTextEditorTests */, 74 | 342F59D6206BF5D00045E75A /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 342F59D6206BF5D00045E75A /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 342F59D5206BF5D00045E75A /* macOSRichTextEditor.framework */, 82 | 342F59DE206BF5D00045E75A /* macOSRichTextEditorTests.xctest */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 342F59D7206BF5D00045E75A /* macOSRichTextEditor */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 342F5A01206BF6840045E75A /* Source */, 91 | 342F59D8206BF5D00045E75A /* macOSRichTextEditor.h */, 92 | 342F59D9206BF5D00045E75A /* Info.plist */, 93 | ); 94 | path = macOSRichTextEditor; 95 | sourceTree = ""; 96 | }; 97 | 342F59E2206BF5D00045E75A /* macOSRichTextEditorTests */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 342F59E3206BF5D00045E75A /* macOSRichTextEditorTests.m */, 101 | 342F59E5206BF5D00045E75A /* Info.plist */, 102 | ); 103 | path = macOSRichTextEditorTests; 104 | sourceTree = ""; 105 | }; 106 | 342F59F3206BF5FC0045E75A /* Categories */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 342F59F7206BF5FC0045E75A /* NSFont+RichTextEditor.h */, 110 | 342F59F4206BF5FC0045E75A /* NSFont+RichTextEditor.m */, 111 | 342F59F6206BF5FC0045E75A /* NSAttributedString+RichTextEditor.h */, 112 | 342F59F5206BF5FC0045E75A /* NSAttributedString+RichTextEditor.m */, 113 | ); 114 | path = Categories; 115 | sourceTree = ""; 116 | }; 117 | 342F5A01206BF6840045E75A /* Source */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 342F59F3206BF5FC0045E75A /* Categories */, 121 | 342F59F2206BF5FC0045E75A /* RichTextEditor.h */, 122 | 342F59F0206BF5FC0045E75A /* RichTextEditor.m */, 123 | 342F59F1206BF5FC0045E75A /* WZProtocolInterceptor.h */, 124 | 342F59F8206BF5FC0045E75A /* WZProtocolInterceptor.m */, 125 | ); 126 | path = Source; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXHeadersBuildPhase section */ 132 | 342F59D2206BF5D00045E75A /* Headers */ = { 133 | isa = PBXHeadersBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | 342F59E6206BF5D00045E75A /* macOSRichTextEditor.h in Headers */, 137 | 342F59FF206BF5FC0045E75A /* NSFont+RichTextEditor.h in Headers */, 138 | 342F59FB206BF5FC0045E75A /* RichTextEditor.h in Headers */, 139 | 342F59FE206BF5FC0045E75A /* NSAttributedString+RichTextEditor.h in Headers */, 140 | 342F59FA206BF5FC0045E75A /* WZProtocolInterceptor.h in Headers */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXHeadersBuildPhase section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | 342F59D4206BF5D00045E75A /* macOSRichTextEditor */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 342F59E9206BF5D00045E75A /* Build configuration list for PBXNativeTarget "macOSRichTextEditor" */; 150 | buildPhases = ( 151 | 342F59D0206BF5D00045E75A /* Sources */, 152 | 342F59D1206BF5D00045E75A /* Frameworks */, 153 | 342F59D2206BF5D00045E75A /* Headers */, 154 | 342F59D3206BF5D00045E75A /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = macOSRichTextEditor; 161 | productName = macOSRichTextEditor; 162 | productReference = 342F59D5206BF5D00045E75A /* macOSRichTextEditor.framework */; 163 | productType = "com.apple.product-type.framework"; 164 | }; 165 | 342F59DD206BF5D00045E75A /* macOSRichTextEditorTests */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 342F59EC206BF5D00045E75A /* Build configuration list for PBXNativeTarget "macOSRichTextEditorTests" */; 168 | buildPhases = ( 169 | 342F59DA206BF5D00045E75A /* Sources */, 170 | 342F59DB206BF5D00045E75A /* Frameworks */, 171 | 342F59DC206BF5D00045E75A /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | 342F59E1206BF5D00045E75A /* PBXTargetDependency */, 177 | ); 178 | name = macOSRichTextEditorTests; 179 | productName = macOSRichTextEditorTests; 180 | productReference = 342F59DE206BF5D00045E75A /* macOSRichTextEditorTests.xctest */; 181 | productType = "com.apple.product-type.bundle.unit-test"; 182 | }; 183 | /* End PBXNativeTarget section */ 184 | 185 | /* Begin PBXProject section */ 186 | 342F59CC206BF5D00045E75A /* Project object */ = { 187 | isa = PBXProject; 188 | attributes = { 189 | LastUpgradeCheck = 0930; 190 | ORGANIZATIONNAME = "Pikle Productions"; 191 | TargetAttributes = { 192 | 342F59D4206BF5D00045E75A = { 193 | CreatedOnToolsVersion = 9.2; 194 | ProvisioningStyle = Automatic; 195 | }; 196 | 342F59DD206BF5D00045E75A = { 197 | CreatedOnToolsVersion = 9.2; 198 | ProvisioningStyle = Automatic; 199 | }; 200 | }; 201 | }; 202 | buildConfigurationList = 342F59CF206BF5D00045E75A /* Build configuration list for PBXProject "macOSRichTextEditor" */; 203 | compatibilityVersion = "Xcode 8.0"; 204 | developmentRegion = en; 205 | hasScannedForEncodings = 0; 206 | knownRegions = ( 207 | en, 208 | Base, 209 | ); 210 | mainGroup = 342F59CB206BF5D00045E75A; 211 | productRefGroup = 342F59D6206BF5D00045E75A /* Products */; 212 | projectDirPath = ""; 213 | projectRoot = ""; 214 | targets = ( 215 | 342F59D4206BF5D00045E75A /* macOSRichTextEditor */, 216 | 342F59DD206BF5D00045E75A /* macOSRichTextEditorTests */, 217 | ); 218 | }; 219 | /* End PBXProject section */ 220 | 221 | /* Begin PBXResourcesBuildPhase section */ 222 | 342F59D3206BF5D00045E75A /* Resources */ = { 223 | isa = PBXResourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | 342F59DC206BF5D00045E75A /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | 342F59D0206BF5D00045E75A /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 342F5A00206BF5FC0045E75A /* WZProtocolInterceptor.m in Sources */, 244 | 342F59F9206BF5FC0045E75A /* RichTextEditor.m in Sources */, 245 | 342F59FC206BF5FC0045E75A /* NSFont+RichTextEditor.m in Sources */, 246 | 342F59FD206BF5FC0045E75A /* NSAttributedString+RichTextEditor.m in Sources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | 342F59DA206BF5D00045E75A /* Sources */ = { 251 | isa = PBXSourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | 342F59E4206BF5D00045E75A /* macOSRichTextEditorTests.m in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXSourcesBuildPhase section */ 259 | 260 | /* Begin PBXTargetDependency section */ 261 | 342F59E1206BF5D00045E75A /* PBXTargetDependency */ = { 262 | isa = PBXTargetDependency; 263 | target = 342F59D4206BF5D00045E75A /* macOSRichTextEditor */; 264 | targetProxy = 342F59E0206BF5D00045E75A /* PBXContainerItemProxy */; 265 | }; 266 | /* End PBXTargetDependency section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | 342F59E7206BF5D00045E75A /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | CLANG_ANALYZER_NONNULL = YES; 274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 280 | CLANG_WARN_BOOL_CONVERSION = YES; 281 | CLANG_WARN_COMMA = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INFINITE_RECURSION = YES; 289 | CLANG_WARN_INT_CONVERSION = YES; 290 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 291 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 292 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 295 | CLANG_WARN_STRICT_PROTOTYPES = YES; 296 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 297 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 298 | CLANG_WARN_UNREACHABLE_CODE = YES; 299 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 300 | CODE_SIGN_IDENTITY = "-"; 301 | COPY_PHASE_STRIP = NO; 302 | CURRENT_PROJECT_VERSION = 1; 303 | DEBUG_INFORMATION_FORMAT = dwarf; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_TESTABILITY = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu11; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | MACOSX_DEPLOYMENT_TARGET = 10.10; 321 | MTL_ENABLE_DEBUG_INFO = YES; 322 | ONLY_ACTIVE_ARCH = YES; 323 | SDKROOT = macosx; 324 | VERSIONING_SYSTEM = "apple-generic"; 325 | VERSION_INFO_PREFIX = ""; 326 | }; 327 | name = Debug; 328 | }; 329 | 342F59E8206BF5D00045E75A /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_ANALYZER_NONNULL = YES; 334 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 345 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INFINITE_RECURSION = YES; 349 | CLANG_WARN_INT_CONVERSION = YES; 350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | CODE_SIGN_IDENTITY = "-"; 361 | COPY_PHASE_STRIP = NO; 362 | CURRENT_PROJECT_VERSION = 1; 363 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 364 | ENABLE_NS_ASSERTIONS = NO; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu11; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 370 | GCC_WARN_UNDECLARED_SELECTOR = YES; 371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 372 | GCC_WARN_UNUSED_FUNCTION = YES; 373 | GCC_WARN_UNUSED_VARIABLE = YES; 374 | MACOSX_DEPLOYMENT_TARGET = 10.10; 375 | MTL_ENABLE_DEBUG_INFO = NO; 376 | SDKROOT = macosx; 377 | VERSIONING_SYSTEM = "apple-generic"; 378 | VERSION_INFO_PREFIX = ""; 379 | }; 380 | name = Release; 381 | }; 382 | 342F59EA206BF5D00045E75A /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | CODE_SIGN_IDENTITY = ""; 386 | CODE_SIGN_STYLE = Automatic; 387 | COMBINE_HIDPI_IMAGES = YES; 388 | DEFINES_MODULE = YES; 389 | DYLIB_COMPATIBILITY_VERSION = 1; 390 | DYLIB_CURRENT_VERSION = 1; 391 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 392 | FRAMEWORK_VERSION = A; 393 | INFOPLIST_FILE = macOSRichTextEditor/Info.plist; 394 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRichTextEditor; 397 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 398 | SKIP_INSTALL = YES; 399 | }; 400 | name = Debug; 401 | }; 402 | 342F59EB206BF5D00045E75A /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | CODE_SIGN_IDENTITY = ""; 406 | CODE_SIGN_STYLE = Automatic; 407 | COMBINE_HIDPI_IMAGES = YES; 408 | DEFINES_MODULE = YES; 409 | DYLIB_COMPATIBILITY_VERSION = 1; 410 | DYLIB_CURRENT_VERSION = 1; 411 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 412 | FRAMEWORK_VERSION = A; 413 | INFOPLIST_FILE = macOSRichTextEditor/Info.plist; 414 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 416 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRichTextEditor; 417 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 418 | SKIP_INSTALL = YES; 419 | }; 420 | name = Release; 421 | }; 422 | 342F59ED206BF5D00045E75A /* Debug */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | CODE_SIGN_STYLE = Automatic; 426 | COMBINE_HIDPI_IMAGES = YES; 427 | INFOPLIST_FILE = macOSRichTextEditorTests/Info.plist; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRichTextEditorTests; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | }; 432 | name = Debug; 433 | }; 434 | 342F59EE206BF5D00045E75A /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | CODE_SIGN_STYLE = Automatic; 438 | COMBINE_HIDPI_IMAGES = YES; 439 | INFOPLIST_FILE = macOSRichTextEditorTests/Info.plist; 440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 441 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRichTextEditorTests; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | }; 444 | name = Release; 445 | }; 446 | /* End XCBuildConfiguration section */ 447 | 448 | /* Begin XCConfigurationList section */ 449 | 342F59CF206BF5D00045E75A /* Build configuration list for PBXProject "macOSRichTextEditor" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 342F59E7206BF5D00045E75A /* Debug */, 453 | 342F59E8206BF5D00045E75A /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 342F59E9206BF5D00045E75A /* Build configuration list for PBXNativeTarget "macOSRichTextEditor" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 342F59EA206BF5D00045E75A /* Debug */, 462 | 342F59EB206BF5D00045E75A /* Release */, 463 | ); 464 | defaultConfigurationIsVisible = 0; 465 | defaultConfigurationName = Release; 466 | }; 467 | 342F59EC206BF5D00045E75A /* Build configuration list for PBXNativeTarget "macOSRichTextEditorTests" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 342F59ED206BF5D00045E75A /* Debug */, 471 | 342F59EE206BF5D00045E75A /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | /* End XCConfigurationList section */ 477 | }; 478 | rootObject = 342F59CC206BF5D00045E75A /* Project object */; 479 | } 480 | -------------------------------------------------------------------------------- /Sample/macOSRTESample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 342F5A10206BF6DA0045E75A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F5A0F206BF6DA0045E75A /* AppDelegate.m */; }; 11 | 342F5A13206BF6DA0045E75A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F5A12206BF6DA0045E75A /* ViewController.m */; }; 12 | 342F5A15206BF6DA0045E75A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 342F5A14206BF6DA0045E75A /* Assets.xcassets */; }; 13 | 342F5A18206BF6DA0045E75A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 342F5A16206BF6DA0045E75A /* Main.storyboard */; }; 14 | 342F5A1B206BF6DA0045E75A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F5A1A206BF6DA0045E75A /* main.m */; }; 15 | 342F5A26206BF6DA0045E75A /* macOSRTESampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F5A25206BF6DA0045E75A /* macOSRTESampleTests.m */; }; 16 | 342F5A31206BF6DA0045E75A /* macOSRTESampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 342F5A30206BF6DA0045E75A /* macOSRTESampleUITests.m */; }; 17 | 342F5A3F206BF6E40045E75A /* macOSRichTextEditor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 342F5A3E206BF6E40045E75A /* macOSRichTextEditor.framework */; }; 18 | 342F5A40206BF6E40045E75A /* macOSRichTextEditor.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 342F5A3E206BF6E40045E75A /* macOSRichTextEditor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 342F5A22206BF6DA0045E75A /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 342F5A03206BF6DA0045E75A /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 342F5A0A206BF6DA0045E75A; 27 | remoteInfo = macOSRTESample; 28 | }; 29 | 342F5A2D206BF6DA0045E75A /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 342F5A03206BF6DA0045E75A /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 342F5A0A206BF6DA0045E75A; 34 | remoteInfo = macOSRTESample; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXCopyFilesBuildPhase section */ 39 | 342F5A41206BF6E40045E75A /* Embed Frameworks */ = { 40 | isa = PBXCopyFilesBuildPhase; 41 | buildActionMask = 2147483647; 42 | dstPath = ""; 43 | dstSubfolderSpec = 10; 44 | files = ( 45 | 342F5A40206BF6E40045E75A /* macOSRichTextEditor.framework in Embed Frameworks */, 46 | ); 47 | name = "Embed Frameworks"; 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXCopyFilesBuildPhase section */ 51 | 52 | /* Begin PBXFileReference section */ 53 | 342F5A0B206BF6DA0045E75A /* macOSRTESample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = macOSRTESample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 342F5A0E206BF6DA0045E75A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 55 | 342F5A0F206BF6DA0045E75A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 56 | 342F5A11206BF6DA0045E75A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 57 | 342F5A12206BF6DA0045E75A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 58 | 342F5A14206BF6DA0045E75A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 342F5A17206BF6DA0045E75A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 342F5A19206BF6DA0045E75A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 342F5A1A206BF6DA0045E75A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 62 | 342F5A1C206BF6DA0045E75A /* macOSRTESample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOSRTESample.entitlements; sourceTree = ""; }; 63 | 342F5A21206BF6DA0045E75A /* macOSRTESampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSRTESampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 342F5A25206BF6DA0045E75A /* macOSRTESampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macOSRTESampleTests.m; sourceTree = ""; }; 65 | 342F5A27206BF6DA0045E75A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 342F5A2C206BF6DA0045E75A /* macOSRTESampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSRTESampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 342F5A30206BF6DA0045E75A /* macOSRTESampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macOSRTESampleUITests.m; sourceTree = ""; }; 68 | 342F5A32206BF6DA0045E75A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 342F5A3E206BF6E40045E75A /* macOSRichTextEditor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = macOSRichTextEditor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | 342F5A08206BF6DA0045E75A /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | 342F5A3F206BF6E40045E75A /* macOSRichTextEditor.framework in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 342F5A1E206BF6DA0045E75A /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 342F5A29206BF6DA0045E75A /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 342F5A02206BF6DA0045E75A = { 99 | isa = PBXGroup; 100 | children = ( 101 | 342F5A3E206BF6E40045E75A /* macOSRichTextEditor.framework */, 102 | 342F5A0D206BF6DA0045E75A /* macOSRTESample */, 103 | 342F5A24206BF6DA0045E75A /* macOSRTESampleTests */, 104 | 342F5A2F206BF6DA0045E75A /* macOSRTESampleUITests */, 105 | 342F5A0C206BF6DA0045E75A /* Products */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 342F5A0C206BF6DA0045E75A /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 342F5A0B206BF6DA0045E75A /* macOSRTESample.app */, 113 | 342F5A21206BF6DA0045E75A /* macOSRTESampleTests.xctest */, 114 | 342F5A2C206BF6DA0045E75A /* macOSRTESampleUITests.xctest */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | 342F5A0D206BF6DA0045E75A /* macOSRTESample */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 342F5A0E206BF6DA0045E75A /* AppDelegate.h */, 123 | 342F5A0F206BF6DA0045E75A /* AppDelegate.m */, 124 | 342F5A11206BF6DA0045E75A /* ViewController.h */, 125 | 342F5A12206BF6DA0045E75A /* ViewController.m */, 126 | 342F5A14206BF6DA0045E75A /* Assets.xcassets */, 127 | 342F5A16206BF6DA0045E75A /* Main.storyboard */, 128 | 342F5A19206BF6DA0045E75A /* Info.plist */, 129 | 342F5A1A206BF6DA0045E75A /* main.m */, 130 | 342F5A1C206BF6DA0045E75A /* macOSRTESample.entitlements */, 131 | ); 132 | path = macOSRTESample; 133 | sourceTree = ""; 134 | }; 135 | 342F5A24206BF6DA0045E75A /* macOSRTESampleTests */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 342F5A25206BF6DA0045E75A /* macOSRTESampleTests.m */, 139 | 342F5A27206BF6DA0045E75A /* Info.plist */, 140 | ); 141 | path = macOSRTESampleTests; 142 | sourceTree = ""; 143 | }; 144 | 342F5A2F206BF6DA0045E75A /* macOSRTESampleUITests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 342F5A30206BF6DA0045E75A /* macOSRTESampleUITests.m */, 148 | 342F5A32206BF6DA0045E75A /* Info.plist */, 149 | ); 150 | path = macOSRTESampleUITests; 151 | sourceTree = ""; 152 | }; 153 | /* End PBXGroup section */ 154 | 155 | /* Begin PBXNativeTarget section */ 156 | 342F5A0A206BF6DA0045E75A /* macOSRTESample */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 342F5A35206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESample" */; 159 | buildPhases = ( 160 | 342F5A07206BF6DA0045E75A /* Sources */, 161 | 342F5A08206BF6DA0045E75A /* Frameworks */, 162 | 342F5A09206BF6DA0045E75A /* Resources */, 163 | 342F5A41206BF6E40045E75A /* Embed Frameworks */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = macOSRTESample; 170 | productName = macOSRTESample; 171 | productReference = 342F5A0B206BF6DA0045E75A /* macOSRTESample.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | 342F5A20206BF6DA0045E75A /* macOSRTESampleTests */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 342F5A38206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESampleTests" */; 177 | buildPhases = ( 178 | 342F5A1D206BF6DA0045E75A /* Sources */, 179 | 342F5A1E206BF6DA0045E75A /* Frameworks */, 180 | 342F5A1F206BF6DA0045E75A /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | 342F5A23206BF6DA0045E75A /* PBXTargetDependency */, 186 | ); 187 | name = macOSRTESampleTests; 188 | productName = macOSRTESampleTests; 189 | productReference = 342F5A21206BF6DA0045E75A /* macOSRTESampleTests.xctest */; 190 | productType = "com.apple.product-type.bundle.unit-test"; 191 | }; 192 | 342F5A2B206BF6DA0045E75A /* macOSRTESampleUITests */ = { 193 | isa = PBXNativeTarget; 194 | buildConfigurationList = 342F5A3B206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESampleUITests" */; 195 | buildPhases = ( 196 | 342F5A28206BF6DA0045E75A /* Sources */, 197 | 342F5A29206BF6DA0045E75A /* Frameworks */, 198 | 342F5A2A206BF6DA0045E75A /* Resources */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | 342F5A2E206BF6DA0045E75A /* PBXTargetDependency */, 204 | ); 205 | name = macOSRTESampleUITests; 206 | productName = macOSRTESampleUITests; 207 | productReference = 342F5A2C206BF6DA0045E75A /* macOSRTESampleUITests.xctest */; 208 | productType = "com.apple.product-type.bundle.ui-testing"; 209 | }; 210 | /* End PBXNativeTarget section */ 211 | 212 | /* Begin PBXProject section */ 213 | 342F5A03206BF6DA0045E75A /* Project object */ = { 214 | isa = PBXProject; 215 | attributes = { 216 | LastUpgradeCheck = 0930; 217 | ORGANIZATIONNAME = "Pikle Productions"; 218 | TargetAttributes = { 219 | 342F5A0A206BF6DA0045E75A = { 220 | CreatedOnToolsVersion = 9.2; 221 | ProvisioningStyle = Automatic; 222 | }; 223 | 342F5A20206BF6DA0045E75A = { 224 | CreatedOnToolsVersion = 9.2; 225 | ProvisioningStyle = Automatic; 226 | TestTargetID = 342F5A0A206BF6DA0045E75A; 227 | }; 228 | 342F5A2B206BF6DA0045E75A = { 229 | CreatedOnToolsVersion = 9.2; 230 | ProvisioningStyle = Automatic; 231 | TestTargetID = 342F5A0A206BF6DA0045E75A; 232 | }; 233 | }; 234 | }; 235 | buildConfigurationList = 342F5A06206BF6DA0045E75A /* Build configuration list for PBXProject "macOSRTESample" */; 236 | compatibilityVersion = "Xcode 8.0"; 237 | developmentRegion = en; 238 | hasScannedForEncodings = 0; 239 | knownRegions = ( 240 | en, 241 | Base, 242 | ); 243 | mainGroup = 342F5A02206BF6DA0045E75A; 244 | productRefGroup = 342F5A0C206BF6DA0045E75A /* Products */; 245 | projectDirPath = ""; 246 | projectRoot = ""; 247 | targets = ( 248 | 342F5A0A206BF6DA0045E75A /* macOSRTESample */, 249 | 342F5A20206BF6DA0045E75A /* macOSRTESampleTests */, 250 | 342F5A2B206BF6DA0045E75A /* macOSRTESampleUITests */, 251 | ); 252 | }; 253 | /* End PBXProject section */ 254 | 255 | /* Begin PBXResourcesBuildPhase section */ 256 | 342F5A09206BF6DA0045E75A /* Resources */ = { 257 | isa = PBXResourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 342F5A15206BF6DA0045E75A /* Assets.xcassets in Resources */, 261 | 342F5A18206BF6DA0045E75A /* Main.storyboard in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | 342F5A1F206BF6DA0045E75A /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | 342F5A2A206BF6DA0045E75A /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXResourcesBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | 342F5A07206BF6DA0045E75A /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | 342F5A13206BF6DA0045E75A /* ViewController.m in Sources */, 287 | 342F5A1B206BF6DA0045E75A /* main.m in Sources */, 288 | 342F5A10206BF6DA0045E75A /* AppDelegate.m in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | 342F5A1D206BF6DA0045E75A /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | 342F5A26206BF6DA0045E75A /* macOSRTESampleTests.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | 342F5A28206BF6DA0045E75A /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 342F5A31206BF6DA0045E75A /* macOSRTESampleUITests.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXTargetDependency section */ 311 | 342F5A23206BF6DA0045E75A /* PBXTargetDependency */ = { 312 | isa = PBXTargetDependency; 313 | target = 342F5A0A206BF6DA0045E75A /* macOSRTESample */; 314 | targetProxy = 342F5A22206BF6DA0045E75A /* PBXContainerItemProxy */; 315 | }; 316 | 342F5A2E206BF6DA0045E75A /* PBXTargetDependency */ = { 317 | isa = PBXTargetDependency; 318 | target = 342F5A0A206BF6DA0045E75A /* macOSRTESample */; 319 | targetProxy = 342F5A2D206BF6DA0045E75A /* PBXContainerItemProxy */; 320 | }; 321 | /* End PBXTargetDependency section */ 322 | 323 | /* Begin PBXVariantGroup section */ 324 | 342F5A16206BF6DA0045E75A /* Main.storyboard */ = { 325 | isa = PBXVariantGroup; 326 | children = ( 327 | 342F5A17206BF6DA0045E75A /* Base */, 328 | ); 329 | name = Main.storyboard; 330 | sourceTree = ""; 331 | }; 332 | /* End PBXVariantGroup section */ 333 | 334 | /* Begin XCBuildConfiguration section */ 335 | 342F5A33206BF6DA0045E75A /* Debug */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ALWAYS_SEARCH_USER_PATHS = NO; 339 | CLANG_ANALYZER_NONNULL = YES; 340 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | CODE_SIGN_IDENTITY = "-"; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu11; 372 | GCC_DYNAMIC_NO_PIC = NO; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_OPTIMIZATION_LEVEL = 0; 375 | GCC_PREPROCESSOR_DEFINITIONS = ( 376 | "DEBUG=1", 377 | "$(inherited)", 378 | ); 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | MACOSX_DEPLOYMENT_TARGET = 10.13; 386 | MTL_ENABLE_DEBUG_INFO = YES; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = macosx; 389 | }; 390 | name = Debug; 391 | }; 392 | 342F5A34206BF6DA0045E75A /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_ANALYZER_NONNULL = YES; 397 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 398 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 399 | CLANG_CXX_LIBRARY = "libc++"; 400 | CLANG_ENABLE_MODULES = YES; 401 | CLANG_ENABLE_OBJC_ARC = YES; 402 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 403 | CLANG_WARN_BOOL_CONVERSION = YES; 404 | CLANG_WARN_COMMA = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INFINITE_RECURSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | CODE_SIGN_IDENTITY = "-"; 424 | COPY_PHASE_STRIP = NO; 425 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 426 | ENABLE_NS_ASSERTIONS = NO; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu11; 429 | GCC_NO_COMMON_BLOCKS = YES; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | MACOSX_DEPLOYMENT_TARGET = 10.13; 437 | MTL_ENABLE_DEBUG_INFO = NO; 438 | SDKROOT = macosx; 439 | }; 440 | name = Release; 441 | }; 442 | 342F5A36206BF6DA0045E75A /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 446 | CODE_SIGN_ENTITLEMENTS = macOSRTESample/macOSRTESample.entitlements; 447 | CODE_SIGN_STYLE = Automatic; 448 | COMBINE_HIDPI_IMAGES = YES; 449 | INFOPLIST_FILE = macOSRTESample/Info.plist; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESample; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | }; 454 | name = Debug; 455 | }; 456 | 342F5A37206BF6DA0045E75A /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | CODE_SIGN_ENTITLEMENTS = macOSRTESample/macOSRTESample.entitlements; 461 | CODE_SIGN_STYLE = Automatic; 462 | COMBINE_HIDPI_IMAGES = YES; 463 | INFOPLIST_FILE = macOSRTESample/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 465 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESample; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | }; 468 | name = Release; 469 | }; 470 | 342F5A39206BF6DA0045E75A /* Debug */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | BUNDLE_LOADER = "$(TEST_HOST)"; 474 | CODE_SIGN_STYLE = Automatic; 475 | COMBINE_HIDPI_IMAGES = YES; 476 | INFOPLIST_FILE = macOSRTESampleTests/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESampleTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/macOSRTESample.app/Contents/MacOS/macOSRTESample"; 481 | }; 482 | name = Debug; 483 | }; 484 | 342F5A3A206BF6DA0045E75A /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | BUNDLE_LOADER = "$(TEST_HOST)"; 488 | CODE_SIGN_STYLE = Automatic; 489 | COMBINE_HIDPI_IMAGES = YES; 490 | INFOPLIST_FILE = macOSRTESampleTests/Info.plist; 491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 492 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESampleTests; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/macOSRTESample.app/Contents/MacOS/macOSRTESample"; 495 | }; 496 | name = Release; 497 | }; 498 | 342F5A3C206BF6DA0045E75A /* Debug */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | CODE_SIGN_STYLE = Automatic; 502 | COMBINE_HIDPI_IMAGES = YES; 503 | INFOPLIST_FILE = macOSRTESampleUITests/Info.plist; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESampleUITests; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | TEST_TARGET_NAME = macOSRTESample; 508 | }; 509 | name = Debug; 510 | }; 511 | 342F5A3D206BF6DA0045E75A /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | CODE_SIGN_STYLE = Automatic; 515 | COMBINE_HIDPI_IMAGES = YES; 516 | INFOPLIST_FILE = macOSRTESampleUITests/Info.plist; 517 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 518 | PRODUCT_BUNDLE_IDENTIFIER = com.pikleproductions.macOSRTESampleUITests; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | TEST_TARGET_NAME = macOSRTESample; 521 | }; 522 | name = Release; 523 | }; 524 | /* End XCBuildConfiguration section */ 525 | 526 | /* Begin XCConfigurationList section */ 527 | 342F5A06206BF6DA0045E75A /* Build configuration list for PBXProject "macOSRTESample" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 342F5A33206BF6DA0045E75A /* Debug */, 531 | 342F5A34206BF6DA0045E75A /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | 342F5A35206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESample" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 342F5A36206BF6DA0045E75A /* Debug */, 540 | 342F5A37206BF6DA0045E75A /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | 342F5A38206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESampleTests" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | 342F5A39206BF6DA0045E75A /* Debug */, 549 | 342F5A3A206BF6DA0045E75A /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | 342F5A3B206BF6DA0045E75A /* Build configuration list for PBXNativeTarget "macOSRTESampleUITests" */ = { 555 | isa = XCConfigurationList; 556 | buildConfigurations = ( 557 | 342F5A3C206BF6DA0045E75A /* Debug */, 558 | 342F5A3D206BF6DA0045E75A /* Release */, 559 | ); 560 | defaultConfigurationIsVisible = 0; 561 | defaultConfigurationName = Release; 562 | }; 563 | /* End XCConfigurationList section */ 564 | }; 565 | rootObject = 342F5A03206BF6DA0045E75A /* Project object */; 566 | } 567 | -------------------------------------------------------------------------------- /Library/macOSRichTextEditor/Source/RichTextEditor.m: -------------------------------------------------------------------------------- 1 | // 2 | // RichTextEditor.h 3 | // RichTextEdtor 4 | // 5 | // Created by Aryan Gh on 7/21/13. 6 | // Copyright (c) 2013 Aryan Ghassemi. All rights reserved. 7 | // Heavily modified for macOS by Deadpikle 8 | // Copyright (c) 2016 Deadpikle. All rights reserved. 9 | // 10 | // https://github.com/aryaxt/iOS-Rich-Text-Editor -- Original 11 | // https://github.com/Deadpikle/macOS-Rich-Text-Editor -- Fork 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | // Text editing architecture guide: https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TextEditing/TextEditing.html#//apple_ref/doc/uid/TP40009459-CH3-SW1 32 | 33 | #import "RichTextEditor.h" 34 | #import 35 | #import "NSFont+RichTextEditor.h" 36 | #import "NSAttributedString+RichTextEditor.h" 37 | #import "WZProtocolInterceptor.h" 38 | #import 39 | 40 | typedef NS_ENUM(NSInteger, ParagraphIndentation) { 41 | ParagraphIndentationIncrease, 42 | ParagraphIndentationDecrease 43 | }; 44 | 45 | @interface RichTextEditor () { 46 | } 47 | 48 | // Gets set to YES when the user starts changing attributes when there is no text selection (selecting bold, italic, etc) 49 | // Gets set to NO when the user changes selection or starts typing 50 | @property (nonatomic, assign) BOOL typingAttributesInProgress; 51 | 52 | @property float currSysVersion; 53 | 54 | @property NSInteger MAX_INDENT; 55 | @property BOOL isInTextDidChange; 56 | 57 | @property NSString *BULLET_STRING; 58 | 59 | @property NSUInteger levelsOfUndo; 60 | @property NSUInteger previousCursorPosition; 61 | 62 | @property BOOL inBulletedList; 63 | @property BOOL justDeletedBackward; 64 | @property NSString *latestReplacementString; 65 | @property NSString *latestStringReplaced; 66 | 67 | @property (nonatomic) NSRange lastAnchorPoint; 68 | @property BOOL shouldEndColorChangeOnLeft; 69 | 70 | @property WZProtocolInterceptor *delegate_interceptor; 71 | 72 | @end 73 | 74 | @implementation RichTextEditor 75 | 76 | +(NSString*)pasteboardDataType { 77 | return @"macOSRichTextEditor57"; 78 | } 79 | 80 | #pragma mark - Initialization - 81 | 82 | - (id)init { 83 | if (self = [super init]) { 84 | [self commonInitialization]; 85 | } 86 | 87 | return self; 88 | } 89 | 90 | - (id)initWithFrame:(CGRect)frame { 91 | if (self = [super initWithFrame:frame]) { 92 | [self commonInitialization]; 93 | } 94 | return self; 95 | } 96 | 97 | - (id)initWithCoder:(NSCoder *)aDecoder { 98 | if (self = [super initWithCoder:aDecoder]) { 99 | [self commonInitialization]; 100 | } 101 | 102 | return self; 103 | } 104 | 105 | - (id)delegate { 106 | return self.delegate_interceptor; 107 | } 108 | 109 | - (void)setDelegate:(id)newDelegate { 110 | [super setDelegate:nil]; 111 | self.delegate_interceptor.receiver = newDelegate; 112 | [super setDelegate:(id)self.delegate_interceptor]; 113 | } 114 | 115 | - (void)commonInitialization { 116 | // Prevent the use of self.delegate = self 117 | // http://stackoverflow.com/questions/3498158/intercept-objective-c-delegate-messages-within-a-subclass 118 | Protocol *p = objc_getProtocol("NSTextViewDelegate"); 119 | self.delegate_interceptor = [[WZProtocolInterceptor alloc] initWithInterceptedProtocol:p]; 120 | [self.delegate_interceptor setMiddleMan:self]; 121 | [super setDelegate:(id)self.delegate_interceptor]; 122 | self.allowsRichTextPasteOnlyFromThisClass = YES; 123 | 124 | self.borderColor = [NSColor lightGrayColor]; 125 | self.borderWidth = 1.0; 126 | 127 | self.shouldEndColorChangeOnLeft = NO; 128 | 129 | self.typingAttributesInProgress = NO; 130 | self.isInTextDidChange = NO; 131 | self.fontSizeChangeAmount = 6.0f; 132 | self.maxFontSize = 128.0f; 133 | self.minFontSize = 10.0f; 134 | 135 | self.levelsOfUndo = 10; 136 | 137 | self.BULLET_STRING = @"•\u00A0"; // bullet is \u2022 138 | self.latestReplacementString = @""; 139 | self.latestStringReplaced = @""; 140 | 141 | // Instead of hard-coding the default indentation size, which can make bulleted lists look a little 142 | // odd when increasing/decreasing their indent, use a \t character width instead 143 | // The old defaultIndentationSize was 15 144 | // TODO: readjust this defaultIndentationSize when font size changes? Might make things weird. 145 | NSDictionary *dictionary = [self dictionaryAtIndex:self.selectedRange.location]; 146 | CGSize expectedStringSize = [@"\t" sizeWithAttributes:dictionary]; 147 | self.defaultIndentationSize = expectedStringSize.width; 148 | self.MAX_INDENT = self.defaultIndentationSize * 10; 149 | 150 | if (self.rteDataSource && [self.rteDataSource respondsToSelector:@selector(levelsOfUndo)]) { 151 | [[self undoManager] setLevelsOfUndo:[self.rteDataSource levelsOfUndo]]; 152 | } 153 | else { 154 | [[self undoManager] setLevelsOfUndo:self.levelsOfUndo]; 155 | } 156 | 157 | // http://stackoverflow.com/questions/26454037/uitextview-text-selection-and-highlight-jumping-in-ios-8 158 | self.layoutManager.allowsNonContiguousLayout = NO; 159 | self.selectedRange = NSMakeRange(0, 0); 160 | if ([[self.string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:@""]) { 161 | [self.textStorage setAttributedString:[[NSAttributedString alloc] initWithString:@""]]; 162 | } 163 | } 164 | 165 | - (BOOL)rangeExists:(NSRange)range { 166 | return range.location != NSNotFound && range.location + range.length <= self.attributedString.length; 167 | } 168 | 169 | - (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString { 170 | self.latestReplacementString = replacementString; 171 | if (affectedCharRange.length > 0 && [self rangeExists:affectedCharRange]) { 172 | self.latestStringReplaced = [self.string substringWithRange:affectedCharRange]; 173 | } 174 | else { 175 | self.latestStringReplaced = @""; 176 | } 177 | if ([replacementString isEqualToString:@"\n"]) { 178 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeEnter]; 179 | self.inBulletedList = [self isInBulletedList]; 180 | } 181 | if ([replacementString isEqualToString:@" "]) { 182 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeSpace]; 183 | } 184 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementString:)]) { 185 | return [self.delegate_interceptor.receiver textView:textView shouldChangeTextInRange:affectedCharRange replacementString:replacementString]; 186 | } 187 | if (self.tabKeyAlwaysIndentsOutdents && [replacementString isEqualToString:@"\t"] && affectedCharRange.length == 0) { 188 | //[self userSelectedIncreaseIndent]; 189 | //return NO; 190 | } 191 | return YES; 192 | } 193 | 194 | // http://stackoverflow.com/questions/2484072/how-can-i-make-the-tab-key-move-focus-out-of-a-nstextview 195 | - (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector { 196 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textView:doCommandBySelector:)]) { 197 | return [self.delegate_interceptor.receiver textView:aTextView doCommandBySelector:aSelector]; 198 | } 199 | if (aSelector == @selector(insertTab:)) { 200 | if ([self isInEmptyBulletedListItem]) { 201 | [self userSelectedIncreaseIndent]; 202 | return YES; 203 | } 204 | } 205 | else if (aSelector == @selector(insertBacktab:)) { 206 | if ([self isInEmptyBulletedListItem]) { 207 | [self userSelectedDecreaseIndent]; 208 | return YES; 209 | } 210 | } 211 | else if (aSelector == @selector(deleteForward:)) { 212 | // Do something against DELETE key 213 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeDelete]; 214 | } 215 | else if (aSelector == @selector(deleteBackward:)) { 216 | // Do something against BACKSPACE key 217 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeDelete]; 218 | } 219 | return NO; 220 | } 221 | 222 | -(void)deleteBackward:(id)sender { 223 | self.justDeletedBackward = YES; 224 | [super deleteBackward:sender]; 225 | } 226 | 227 | // https://stackoverflow.com/a/23667851/3938401 228 | - (void)setSelectedRange:(NSRange)charRange affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag { 229 | if (charRange.length == 0) { 230 | self.lastAnchorPoint = charRange; 231 | } 232 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:charRange]; 233 | charRange = [self adjustSelectedRangeForBulletsWithStart:rangeOfCurrentParagraph Previous:NSMakeRange(NSNotFound, 0) andCurrent:charRange isMouseClick:YES]; 234 | [super setSelectedRange:charRange affinity:affinity stillSelecting:stillSelectingFlag]; 235 | } 236 | 237 | // https://stackoverflow.com/a/23667851/3938401 238 | - (NSRange)textView:(NSTextView *)textView willChangeSelectionFromCharacterRange:(NSRange)oldSelectedCharRange toCharacterRange:(NSRange)newSelectedCharRange { 239 | 240 | if (newSelectedCharRange.length != 0) { 241 | int anchorStart = (int)self.lastAnchorPoint.location; 242 | int selectionStart = (int)newSelectedCharRange.location; 243 | int selectionLength = (int)newSelectedCharRange.length; 244 | 245 | /* 246 | If mouse selects left, and then a user arrows right, or the opposite, anchor point flips. 247 | */ 248 | int difference = anchorStart - selectionStart; 249 | if (difference > 0 && difference != selectionLength) { 250 | if (oldSelectedCharRange.location == newSelectedCharRange.location) { 251 | // We were selecting left via mouse, but now we are selecting to the right via arrows 252 | anchorStart = selectionStart; 253 | } 254 | else { 255 | // We were selecting right via mouse, but now we are selecting to the left via arrows 256 | anchorStart = selectionStart + selectionLength; 257 | } 258 | self.lastAnchorPoint = NSMakeRange(anchorStart, 0); 259 | } 260 | 261 | // Evaluate Selection Direction 262 | if (anchorStart == selectionStart) { 263 | if (oldSelectedCharRange.length < newSelectedCharRange.length) { 264 | // Bigger 265 | //NSLog(@"Will select right in overall right selection"); 266 | } 267 | else { 268 | // Smaller 269 | //NSLog(@"Will select left in overall right selection"); 270 | } 271 | self.shouldEndColorChangeOnLeft = NO; 272 | } 273 | else { 274 | self.shouldEndColorChangeOnLeft = YES; 275 | if (oldSelectedCharRange.length < newSelectedCharRange.length) { 276 | // Bigger 277 | //NSLog(@"Will select left in overall left selection"); 278 | } 279 | else { 280 | // Smaller 281 | //NSLog(@"Will select right in overall left selection"); 282 | } 283 | } 284 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:oldSelectedCharRange]; 285 | newSelectedCharRange = [self adjustSelectedRangeForBulletsWithStart:rangeOfCurrentParagraph Previous:oldSelectedCharRange andCurrent:newSelectedCharRange isMouseClick:NO]; 286 | } 287 | 288 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:)]) { 289 | return [self.delegate_interceptor.receiver textView:textView willChangeSelectionFromCharacterRange:oldSelectedCharRange toCharacterRange:newSelectedCharRange]; 290 | } 291 | return newSelectedCharRange; 292 | } 293 | 294 | - (void)textViewDidChangeSelection:(NSNotification *)notification { 295 | [self setNeedsLayout:YES]; 296 | [self scrollRangeToVisible:self.selectedRange]; // fixes issue with cursor moving to top via keyboard and RTE not scrolling 297 | [self sendDelegateTypingAttrsUpdate]; 298 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textViewDidChangeSelection:)]) { 299 | [self.delegate_interceptor.receiver textViewDidChangeSelection:notification]; 300 | } 301 | } 302 | 303 | - (void)textDidChange:(NSNotification *)notification { 304 | if (!self.isInTextDidChange) { 305 | self.isInTextDidChange = YES; 306 | [self applyBulletListIfApplicable]; 307 | [self deleteBulletListWhenApplicable]; 308 | 309 | if ([self.latestStringReplaced hasSuffix:@"\n"]) { 310 | // get rest of paragraph as they just deleted a newline 311 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 312 | NSInteger rangeDiff = self.selectedRange.location - rangeOfCurrentParagraph.location; 313 | if (rangeDiff >= 0) { 314 | NSRange restOfLineRange = NSMakeRange(rangeOfCurrentParagraph.location + rangeDiff, rangeOfCurrentParagraph.length - rangeDiff); 315 | NSString *restOfLine = [self.string substringWithRange:restOfLineRange]; 316 | if ([restOfLine hasPrefix:self.BULLET_STRING]) { 317 | // we must have deleted a newline under a previous list! Get rid of the bullet! 318 | [self.textStorage replaceCharactersInRange:NSMakeRange(restOfLineRange.location, self.BULLET_STRING.length) withString:@""]; 319 | } 320 | } 321 | } 322 | 323 | self.isInTextDidChange = NO; 324 | } 325 | self.justDeletedBackward = NO; 326 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textDidChange:)]) { 327 | [self.delegate_interceptor.receiver textDidChange:notification]; 328 | } 329 | } 330 | 331 | - (BOOL)isInBulletedList { 332 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 333 | return [[[self.attributedString string] substringFromIndex:rangeOfCurrentParagraph.location] hasPrefix:self.BULLET_STRING]; 334 | } 335 | 336 | -(BOOL)isInEmptyBulletedListItem { 337 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 338 | return [[[self.attributedString string] substringFromIndex:rangeOfCurrentParagraph.location] isEqualToString:self.BULLET_STRING]; 339 | } 340 | 341 | - (void)paste:(id)sender { 342 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangePaste]; 343 | if (self.allowsRichTextPasteOnlyFromThisClass) { 344 | if ([[NSPasteboard generalPasteboard] dataForType:[RichTextEditor pasteboardDataType]]) { 345 | [super paste:sender]; // just call paste so we don't have to bother doing the check again 346 | } 347 | else { 348 | [self pasteAsPlainText:self]; 349 | } 350 | } 351 | else { 352 | [super paste:sender]; 353 | } 354 | } 355 | 356 | - (void)pasteAsRichText:(id)sender { 357 | BOOL hasCopyDataFromThisClass = [[NSPasteboard generalPasteboard] dataForType:[RichTextEditor pasteboardDataType]] != nil; 358 | if (self.allowsRichTextPasteOnlyFromThisClass) { 359 | if (hasCopyDataFromThisClass) { 360 | [super pasteAsRichText:sender]; 361 | } 362 | else { 363 | [self pasteAsPlainText:sender]; 364 | } 365 | } 366 | else { 367 | [super pasteAsRichText:sender]; 368 | } 369 | } 370 | 371 | - (void)pasteAsPlainText:(id)sender { 372 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangePaste]; 373 | // Apparently paste as "plain" text doesn't ignore background and foreground colors... 374 | NSMutableDictionary *typingAttributes = [self.typingAttributes mutableCopy]; 375 | [typingAttributes removeObjectForKey:NSBackgroundColorAttributeName]; 376 | [typingAttributes removeObjectForKey:NSForegroundColorAttributeName]; 377 | self.typingAttributes = typingAttributes; 378 | [super pasteAsPlainText:sender]; 379 | } 380 | 381 | - (void)cut:(id)sender { 382 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeCut]; 383 | [super cut:sender]; 384 | } 385 | 386 | -(void)copy:(id)sender { 387 | [super copy:sender]; 388 | NSPasteboard *currentPasteboard = [NSPasteboard generalPasteboard]; 389 | [currentPasteboard setData:[@"" dataUsingEncoding:NSUTF8StringEncoding] forType:[RichTextEditor pasteboardDataType]]; 390 | } 391 | 392 | #pragma mark - 393 | 394 | - (void)sendDelegateTypingAttrsUpdate { 395 | if (self.rteDelegate) { 396 | NSDictionary *attributes = [self typingAttributes]; 397 | NSFont *font = [attributes objectForKey:NSFontAttributeName]; 398 | NSColor *fontColor = [attributes objectForKey:NSForegroundColorAttributeName]; 399 | NSColor *backgroundColor = [attributes objectForKey:NSBackgroundColorAttributeName]; // may want NSBackgroundColorAttributeName 400 | BOOL isInBulletedList = [self isInBulletedList]; 401 | [self.rteDelegate selectionForEditor:self changedTo:[self selectedRange] isBold:[font isBold] isItalic:[font isItalic] isUnderline:[self isCurrentFontUnderlined] isInBulletedList:isInBulletedList textBackgroundColor:backgroundColor textColor:fontColor]; 402 | } 403 | } 404 | 405 | -(void)sendDelegateTVChanged { 406 | if (self.delegate_interceptor.receiver && [self.delegate_interceptor.receiver respondsToSelector:@selector(textDidChange:)]) { 407 | [self.delegate_interceptor.receiver textDidChange:[NSNotification notificationWithName:@"textDidChange:" object:self]]; 408 | } 409 | } 410 | 411 | -(void)sendDelegatePreviewChangeOfType:(RichTextEditorPreviewChange)type { 412 | if (self.rteDelegate && [self.rteDelegate respondsToSelector:@selector(richTextEditor:changeAboutToOccurOfType:)]) { 413 | [self.rteDelegate richTextEditor:self changeAboutToOccurOfType:type]; 414 | } 415 | } 416 | 417 | -(void)userSelectedBold { 418 | NSFont *font = [[self typingAttributes] objectForKey:NSFontAttributeName]; 419 | if (!font) { 420 | font = [NSFont systemFontOfSize:12.0f]; 421 | } 422 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeBold]; 423 | [self applyFontAttributesToSelectedRangeWithBoldTrait:[NSNumber numberWithBool:![font isBold]] italicTrait:nil fontName:nil fontSize:nil]; 424 | [self sendDelegateTypingAttrsUpdate]; 425 | [self sendDelegateTVChanged]; 426 | } 427 | 428 | -(void)userSelectedItalic { 429 | NSFont *font = [[self typingAttributes] objectForKey:NSFontAttributeName]; 430 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeItalic]; 431 | [self applyFontAttributesToSelectedRangeWithBoldTrait:nil italicTrait:[NSNumber numberWithBool:![font isItalic]] fontName:nil fontSize:nil]; 432 | [self sendDelegateTypingAttrsUpdate]; 433 | [self sendDelegateTVChanged]; 434 | } 435 | 436 | -(void)userSelectedUnderline { 437 | NSNumber *existingUnderlineStyle; 438 | if (![self isCurrentFontUnderlined]) { 439 | existingUnderlineStyle = [NSNumber numberWithInteger:NSUnderlineStyleSingle]; 440 | } 441 | else { 442 | existingUnderlineStyle = [NSNumber numberWithInteger:NSUnderlineStyleNone]; 443 | } 444 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeUnderline]; 445 | [self applyAttributesToSelectedRange:existingUnderlineStyle forKey:NSUnderlineStyleAttributeName]; 446 | [self sendDelegateTypingAttrsUpdate]; 447 | [self sendDelegateTVChanged]; 448 | } 449 | 450 | -(void)userSelectedIncreaseIndent { 451 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeIndentIncrease]; 452 | [self userSelectedParagraphIndentation:ParagraphIndentationIncrease]; 453 | [self sendDelegateTVChanged]; 454 | } 455 | 456 | -(void)userSelectedDecreaseIndent { 457 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeIndentDecrease]; 458 | [self userSelectedParagraphIndentation:ParagraphIndentationDecrease]; 459 | [self sendDelegateTVChanged]; 460 | } 461 | 462 | -(void)userSelectedTextBackgroundColor:(NSColor*)color { 463 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeHighlight]; 464 | NSRange selectedRange = [self selectedRange]; 465 | if (color) { 466 | [self applyAttributesToSelectedRange:color forKey:NSBackgroundColorAttributeName]; 467 | } 468 | else { 469 | [self removeAttributeForKeyFromSelectedRange:NSBackgroundColorAttributeName]; 470 | } 471 | if (self.shouldEndColorChangeOnLeft) { 472 | [self setSelectedRange:NSMakeRange(selectedRange.location, 0)]; 473 | } 474 | else { 475 | [self setSelectedRange:NSMakeRange(selectedRange.location + selectedRange.length, 0)]; 476 | } 477 | [self sendDelegateTVChanged]; 478 | } 479 | 480 | -(void)userSelectedTextColor:(NSColor*)color { 481 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeFontColor]; 482 | if (color) { 483 | [self applyAttributesToSelectedRange:color forKey:NSForegroundColorAttributeName]; 484 | } 485 | else { 486 | [self removeAttributeForKeyFromSelectedRange:NSForegroundColorAttributeName]; 487 | } 488 | [self sendDelegateTVChanged]; 489 | } 490 | 491 | - (BOOL)canBecomeFirstResponder { 492 | return YES; 493 | } 494 | 495 | - (void)setFont:(NSFont *)font { 496 | [super setFont:font]; 497 | } 498 | 499 | #pragma mark - Public Methods - 500 | 501 | - (void)setHtmlString:(NSString *)htmlString { 502 | NSMutableAttributedString *attr = [[RichTextEditor attributedStringFromHTMLString:htmlString] mutableCopy]; 503 | if (attr) { 504 | if ([attr.string hasSuffix:@"\n"]) { 505 | [attr replaceCharactersInRange:NSMakeRange(attr.length - 1, 1) withString:@""]; 506 | } 507 | [self setAttributedString:attr]; 508 | } 509 | } 510 | 511 | - (NSString *)htmlString { 512 | return [RichTextEditor htmlStringFromAttributedText:self.attributedString]; 513 | } 514 | 515 | - (void)changeToAttributedString:(NSAttributedString*)string { 516 | [self setAttributedString:string]; 517 | } 518 | 519 | +(NSString *)htmlStringFromAttributedText:(NSAttributedString*)text { 520 | NSData *data = [text dataFromRange:NSMakeRange(0, text.length) 521 | documentAttributes: 522 | @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, 523 | NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} 524 | error:nil]; 525 | 526 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 527 | } 528 | 529 | +(NSAttributedString*)attributedStringFromHTMLString:(NSString *)htmlString { 530 | @try { 531 | NSError *error; 532 | NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding]; 533 | NSAttributedString *str = 534 | [[NSAttributedString alloc] initWithData:data 535 | options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, 536 | NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} 537 | documentAttributes:nil error:&error]; 538 | if (!error) 539 | return str; 540 | return nil; 541 | } 542 | @catch (NSException *e) { 543 | return nil; 544 | } 545 | } 546 | 547 | - (void)setBorderColor:(NSColor *)borderColor { 548 | self.layer.borderColor = borderColor.CGColor; 549 | } 550 | 551 | - (void)setBorderWidth:(CGFloat)borderWidth { 552 | self.layer.borderWidth = borderWidth; 553 | } 554 | 555 | - (void)userChangedToFontSize:(NSNumber*)fontSize { 556 | [self applyFontAttributesToSelectedRangeWithBoldTrait:nil italicTrait:nil fontName:nil fontSize:fontSize]; 557 | } 558 | 559 | - (void)userChangedToFontName:(NSString*)fontName { 560 | [self applyFontAttributesToSelectedRangeWithBoldTrait:nil italicTrait:nil fontName:fontName fontSize:nil]; 561 | } 562 | 563 | - (BOOL)isCurrentFontUnderlined { 564 | NSDictionary *dictionary = [self typingAttributes]; 565 | NSNumber *existingUnderlineStyle = [dictionary objectForKey:NSUnderlineStyleAttributeName]; 566 | 567 | if (!existingUnderlineStyle || existingUnderlineStyle.intValue == NSUnderlineStyleNone) { 568 | return NO; 569 | } 570 | return YES; 571 | } 572 | 573 | // try/catch blocks on undo/redo because it doesn't work right with bulleted lists when BULLET_STRING has more than 1 character 574 | - (void)undo { 575 | @try { 576 | BOOL shouldUseUndoManager = YES; 577 | if ([self.rteDelegate respondsToSelector:@selector(handlesUndoRedoForText)] && 578 | [self.rteDelegate respondsToSelector:@selector(userPerformedUndo)]) { 579 | if ([self.rteDelegate handlesUndoRedoForText]) { 580 | [self.rteDelegate userPerformedUndo]; 581 | shouldUseUndoManager = NO; 582 | } 583 | } 584 | if (shouldUseUndoManager && [[self undoManager] canUndo]) { 585 | [[self undoManager] undo]; 586 | } 587 | } 588 | @catch (NSException *e) { 589 | [[self undoManager] removeAllActions]; 590 | } 591 | } 592 | 593 | - (void)redo { 594 | @try { 595 | BOOL shouldUseUndoManager = YES; 596 | if ([self.rteDelegate respondsToSelector:@selector(handlesUndoRedoForText)] && 597 | [self.rteDelegate respondsToSelector:@selector(userPerformedRedo)]) { 598 | if ([self.rteDelegate handlesUndoRedoForText]) { 599 | [self.rteDelegate userPerformedRedo]; 600 | shouldUseUndoManager = NO; 601 | } 602 | } 603 | if (shouldUseUndoManager && [[self undoManager] canRedo]) 604 | [[self undoManager] redo]; 605 | } 606 | @catch (NSException *e) { 607 | [[self undoManager] removeAllActions]; 608 | } 609 | } 610 | 611 | - (void)userSelectedParagraphIndentation:(ParagraphIndentation)paragraphIndentation { 612 | self.isInTextDidChange = YES; 613 | __block NSDictionary *dictionary; 614 | __block NSMutableParagraphStyle *paragraphStyle; 615 | NSRange currSelectedRange = self.selectedRange; 616 | [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ 617 | dictionary = [self dictionaryAtIndex:paragraphRange.location]; 618 | paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 619 | if (!paragraphStyle) { 620 | paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 621 | } 622 | 623 | if (paragraphIndentation == ParagraphIndentationIncrease && 624 | paragraphStyle.headIndent < self.MAX_INDENT && paragraphStyle.firstLineHeadIndent < self.MAX_INDENT) { 625 | paragraphStyle.headIndent += self.defaultIndentationSize; 626 | paragraphStyle.firstLineHeadIndent += self.defaultIndentationSize; 627 | } 628 | else if (paragraphIndentation == ParagraphIndentationDecrease) { 629 | paragraphStyle.headIndent -= self.defaultIndentationSize; 630 | paragraphStyle.firstLineHeadIndent -= self.defaultIndentationSize; 631 | 632 | if (paragraphStyle.headIndent < 0) { 633 | paragraphStyle.headIndent = 0; // this is the right cursor placement 634 | } 635 | 636 | if (paragraphStyle.firstLineHeadIndent < 0) { 637 | paragraphStyle.firstLineHeadIndent = 0; // this affects left cursor placement 638 | } 639 | } 640 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:paragraphRange]; 641 | }]; 642 | [self setSelectedRange:currSelectedRange]; 643 | self.isInTextDidChange = NO; 644 | // Old iOS code 645 | // Following 2 lines allow the user to insta-type after indenting in a bulleted list 646 | //NSRange range = NSMakeRange(self.selectedRange.location+self.selectedRange.length, 0); 647 | //[self setSelectedRange:range]; 648 | // Check to see if the current paragraph is blank. If it is, manually get the cursor to move with a weird hack. 649 | 650 | // After NSTextStorage changes, these don't seem necessary 651 | /* NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 652 | BOOL currParagraphIsBlank = [[self.string substringWithRange:rangeOfCurrentParagraph] isEqualToString:@""] ? YES: NO; 653 | if (currParagraphIsBlank) 654 | { 655 | // [self setIndentationWithAttributes:dictionary paragraphStyle:paragraphStyle atRange:rangeOfCurrentParagraph]; 656 | } */ 657 | } 658 | 659 | // Manually ensures that the cursor is shown in the correct location. Ugly work around and weird but it works (at least in iOS 7 / OS X 10.11.2). 660 | // Basically what I do is add a " " with the correct indentation then delete it. For some reason with that 661 | // and applying that attribute to the current typing attributes it moves the cursor to the right place. 662 | // Would updating the typing attributes also work instead? That'd certainly be cleaner... 663 | -(void)setIndentationWithAttributes:(NSDictionary*)attributes paragraphStyle:(NSMutableParagraphStyle*)paragraphStyle atRange:(NSRange)range { 664 | NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:@" " attributes:attributes]; 665 | [space addAttributes:[NSDictionary dictionaryWithObject:paragraphStyle forKey:NSParagraphStyleAttributeName] range:NSMakeRange(0, 1)]; 666 | [self.textStorage insertAttributedString:space atIndex:range.location]; 667 | [self setSelectedRange:NSMakeRange(range.location, 1)]; 668 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:NSMakeRange(self.selectedRange.location+self.selectedRange.length-1, 1)]; 669 | [self setSelectedRange:NSMakeRange(range.location, 0)]; 670 | [self.textStorage deleteCharactersInRange:NSMakeRange(range.location, 1)]; 671 | [self applyAttributeToTypingAttribute:paragraphStyle forKey:NSParagraphStyleAttributeName]; 672 | } 673 | 674 | - (void)userSelectedParagraphFirstLineHeadIndent { 675 | [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ 676 | NSDictionary *dictionary = [self dictionaryAtIndex:paragraphRange.location]; 677 | NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 678 | 679 | if (!paragraphStyle) { 680 | paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 681 | } 682 | 683 | if (paragraphStyle.headIndent == paragraphStyle.firstLineHeadIndent) { 684 | paragraphStyle.firstLineHeadIndent += self.defaultIndentationSize; 685 | } 686 | else { 687 | paragraphStyle.firstLineHeadIndent = paragraphStyle.headIndent; 688 | } 689 | 690 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:paragraphRange]; 691 | }]; 692 | } 693 | 694 | - (void)userSelectedTextAlignment:(NSTextAlignment)textAlignment { 695 | [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ 696 | NSDictionary *dictionary = [self dictionaryAtIndex:paragraphRange.location]; 697 | NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 698 | 699 | if (!paragraphStyle) { 700 | paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 701 | } 702 | 703 | paragraphStyle.alignment = textAlignment; 704 | 705 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:paragraphRange]; 706 | [self setIndentationWithAttributes:dictionary paragraphStyle:paragraphStyle atRange:paragraphRange]; 707 | }]; 708 | } 709 | 710 | -(void)setAttributedString:(NSAttributedString*)attributedString { 711 | [self.textStorage setAttributedString:attributedString]; 712 | } 713 | 714 | // http://stackoverflow.com/questions/5810706/how-to-programmatically-add-bullet-list-to-nstextview might be useful to look at some day (or maybe not) 715 | - (void)userSelectedBullet { 716 | //NSLog(@"[RTE] Bullet code called"); 717 | if (!self.isEditable) { 718 | return; 719 | } 720 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeBullet]; 721 | NSRange initialSelectedRange = self.selectedRange; 722 | NSArray *rangeOfParagraphsInSelectedText = [self.attributedString rangeOfParagraphsFromTextRange:self.selectedRange]; 723 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 724 | BOOL firstParagraphHasBullet = [[self.string substringFromIndex:rangeOfCurrentParagraph.location] hasPrefix:self.BULLET_STRING]; 725 | 726 | NSRange rangeOfPreviousParagraph = [self.attributedString firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; 727 | NSDictionary *prevParaDict = [self dictionaryAtIndex:rangeOfPreviousParagraph.location]; 728 | NSMutableParagraphStyle *prevParaStyle = [prevParaDict objectForKey:NSParagraphStyleAttributeName]; 729 | 730 | __block NSInteger rangeOffset = 0; 731 | __block BOOL mustDecreaseIndentAfterRemovingBullet = NO; 732 | __block BOOL isInBulletedList = self.inBulletedList; 733 | [self enumarateThroughParagraphsInRange:self.selectedRange withBlock:^(NSRange paragraphRange){ 734 | NSRange range = NSMakeRange(paragraphRange.location + rangeOffset, paragraphRange.length); 735 | NSDictionary *dictionary = [self dictionaryAtIndex:MAX((int)range.location-1, 0)]; 736 | NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 737 | 738 | if (!paragraphStyle) { 739 | paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 740 | } 741 | 742 | BOOL currentParagraphHasBullet = [[self.string substringFromIndex:range.location] hasPrefix:self.BULLET_STRING]; 743 | 744 | if (firstParagraphHasBullet != currentParagraphHasBullet) { 745 | return; 746 | } 747 | if (currentParagraphHasBullet) { 748 | // User hit the bullet button and is in a bulleted list so we should get rid of the bullet 749 | range = NSMakeRange(range.location, range.length - self.BULLET_STRING.length); 750 | 751 | [self.textStorage deleteCharactersInRange:NSMakeRange(range.location, self.BULLET_STRING.length)]; 752 | 753 | paragraphStyle.firstLineHeadIndent = 0; 754 | paragraphStyle.headIndent = 0; 755 | 756 | rangeOffset = rangeOffset - self.BULLET_STRING.length; 757 | mustDecreaseIndentAfterRemovingBullet = YES; 758 | isInBulletedList = NO; 759 | } 760 | else { 761 | // We are adding a bullet 762 | range = NSMakeRange(range.location, range.length + self.BULLET_STRING.length); 763 | 764 | NSMutableAttributedString *bulletAttributedString = [[NSMutableAttributedString alloc] initWithString:self.BULLET_STRING attributes:nil]; 765 | // The following code attempts to remove any underline from the bullet string, but it doesn't work right. I don't know why. 766 | /* NSFont *prevFont = [dictionary objectForKey:NSFontAttributeName]; 767 | NSFont *bulletFont = [NSFont fontWithName:[prevFont familyName] size:[prevFont pointSize]]; 768 | 769 | NSMutableDictionary *bulletDict = [dictionary mutableCopy]; 770 | [bulletDict setObject:bulletFont forKey:NSFontAttributeName]; 771 | [bulletDict removeObjectForKey:NSStrikethroughStyleAttributeName]; 772 | [bulletDict setValue:NSUnderlineStyleNone forKey:NSUnderlineStyleAttributeName]; 773 | [bulletDict removeObjectForKey:NSStrokeColorAttributeName]; 774 | [bulletDict removeObjectForKey:NSStrokeWidthAttributeName]; 775 | dictionary = bulletDict;*/ 776 | 777 | [bulletAttributedString setAttributes:dictionary range:NSMakeRange(0, self.BULLET_STRING.length)]; 778 | 779 | [self.textStorage insertAttributedString:bulletAttributedString atIndex:range.location]; 780 | 781 | CGSize expectedStringSize = [self.BULLET_STRING sizeWithAttributes:dictionary]; 782 | 783 | // See if the previous paragraph has a bullet 784 | NSString *previousParagraph = [self.string substringWithRange:rangeOfPreviousParagraph]; 785 | BOOL doesPrefixWithBullet = [previousParagraph hasPrefix:self.BULLET_STRING]; 786 | 787 | // Look at the previous paragraph to see what the firstLineHeadIndent should be for the 788 | // current bullet 789 | // if the previous paragraph has a bullet, use that paragraph's indent 790 | // if not, then use defaultIndentation size 791 | if (!doesPrefixWithBullet) { 792 | paragraphStyle.firstLineHeadIndent = self.defaultIndentationSize; 793 | } 794 | else { 795 | paragraphStyle.firstLineHeadIndent = prevParaStyle.firstLineHeadIndent; 796 | } 797 | 798 | paragraphStyle.headIndent = expectedStringSize.width + paragraphStyle.firstLineHeadIndent; 799 | 800 | rangeOffset = rangeOffset + self.BULLET_STRING.length; 801 | isInBulletedList = YES; 802 | } 803 | [self.textStorage addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; 804 | }]; 805 | 806 | // If paragraph is empty move cursor to front of bullet, so the user can start typing right away 807 | NSRange rangeForSelection; 808 | if (rangeOfParagraphsInSelectedText.count == 1 && rangeOfCurrentParagraph.length == 0 && isInBulletedList) { 809 | rangeForSelection = NSMakeRange(rangeOfCurrentParagraph.location + self.BULLET_STRING.length, 0); 810 | } 811 | else { 812 | if (initialSelectedRange.length == 0) { 813 | rangeForSelection = NSMakeRange(initialSelectedRange.location+rangeOffset, 0); 814 | } 815 | else { 816 | NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; 817 | rangeForSelection = NSMakeRange(fullRange.location, fullRange.length+rangeOffset); 818 | } 819 | } 820 | if (mustDecreaseIndentAfterRemovingBullet) { 821 | // remove the extra indentation added by the bullet 822 | [self userSelectedParagraphIndentation:ParagraphIndentationDecrease]; 823 | } 824 | self.selectedRange = rangeForSelection; 825 | 826 | if (!self.isInTextDidChange) { 827 | [self sendDelegateTVChanged]; 828 | } 829 | } 830 | 831 | // modified from https://stackoverflow.com/a/4833778/3938401 832 | - (void)changeToFont:(NSFont*)font { 833 | NSTextStorage *textStorage = self.textStorage; 834 | [textStorage beginEditing]; 835 | [textStorage enumerateAttributesInRange: NSMakeRange(0, textStorage.length) 836 | options: 0 837 | usingBlock: ^(NSDictionary *attributesDictionary, 838 | NSRange range, 839 | BOOL *stop) { 840 | NSFont *currFont = [attributesDictionary objectForKey:NSFontAttributeName]; 841 | if (currFont) { 842 | NSFont *fontToChangeTo = [font fontWithBoldTrait:currFont.isBold andItalicTrait:currFont.isItalic]; 843 | if (fontToChangeTo) { 844 | [textStorage removeAttribute:NSFontAttributeName range:range]; 845 | [textStorage addAttribute:NSFontAttributeName value:fontToChangeTo range:range]; 846 | } 847 | } 848 | }]; 849 | [textStorage endEditing]; 850 | } 851 | 852 | #pragma mark - Private Methods - 853 | 854 | - (void)enumarateThroughParagraphsInRange:(NSRange)range withBlock:(void (^)(NSRange paragraphRange))block{ 855 | NSArray *rangeOfParagraphsInSelectedText = [self.attributedString rangeOfParagraphsFromTextRange:self.selectedRange]; 856 | 857 | for (int i = 0; i < rangeOfParagraphsInSelectedText.count; i++) { 858 | NSValue *value = rangeOfParagraphsInSelectedText[i]; 859 | NSRange paragraphRange = [value rangeValue]; 860 | block(paragraphRange); 861 | } 862 | rangeOfParagraphsInSelectedText = [self.attributedString rangeOfParagraphsFromTextRange:self.selectedRange]; 863 | NSRange fullRange = [self fullRangeFromArrayOfParagraphRanges:rangeOfParagraphsInSelectedText]; 864 | if (fullRange.location + fullRange.length > [self.attributedString length]) { 865 | fullRange.length = 0; 866 | fullRange.location = [self.attributedString length]-1; 867 | } 868 | [self setSelectedRange:fullRange]; 869 | } 870 | 871 | - (NSRange)fullRangeFromArrayOfParagraphRanges:(NSArray *)paragraphRanges { 872 | if (!paragraphRanges.count) { 873 | return NSMakeRange(0, 0); 874 | } 875 | 876 | NSRange firstRange = [paragraphRanges[0] rangeValue]; 877 | NSRange lastRange = [[paragraphRanges lastObject] rangeValue]; 878 | return NSMakeRange(firstRange.location, lastRange.location + lastRange.length - firstRange.location); 879 | } 880 | 881 | - (NSFont *)fontAtIndex:(NSInteger)index { 882 | return [[self dictionaryAtIndex:index] objectForKey:NSFontAttributeName]; 883 | } 884 | 885 | - (BOOL)hasText { 886 | return self.string.length > 0; 887 | } 888 | 889 | - (NSDictionary *)dictionaryAtIndex:(NSInteger)index { 890 | if (![self hasText] || index == self.string.length) { 891 | return self.typingAttributes; // end of string, use whatever we're currently using 892 | } 893 | else { 894 | return [self.attributedString attributesAtIndex:index effectiveRange:nil]; 895 | } 896 | } 897 | 898 | - (void)updateTypingAttributes { 899 | // http://stackoverflow.com/questions/11835497/nstextview-not-applying-attributes-to-newly-inserted-text 900 | NSArray *selectedRanges = self.selectedRanges; 901 | if (selectedRanges && selectedRanges.count > 0 && [self hasText]) { 902 | NSValue *firstSelectionRangeValue = selectedRanges[0]; 903 | if (firstSelectionRangeValue) { 904 | NSRange firstCharacterOfSelectedRange = [firstSelectionRangeValue rangeValue]; 905 | if (firstCharacterOfSelectedRange.location >= self.textStorage.length) { 906 | firstCharacterOfSelectedRange.location = self.textStorage.length - 1; 907 | } 908 | NSDictionary *attributesDictionary = [self.textStorage attributesAtIndex:firstCharacterOfSelectedRange.location effectiveRange: NULL]; 909 | [self setTypingAttributes: attributesDictionary]; 910 | } 911 | } 912 | } 913 | 914 | - (void)applyAttributeToTypingAttribute:(id)attribute forKey:(NSString *)key { 915 | NSMutableDictionary *dictionary = [self.typingAttributes mutableCopy]; 916 | [dictionary setObject:attribute forKey:key]; 917 | [self setTypingAttributes:dictionary]; 918 | } 919 | 920 | - (void)applyAttributes:(id)attribute forKey:(NSString *)key atRange:(NSRange)range { 921 | // If any text selected apply attributes to text 922 | if (range.length > 0) { 923 | // Workaround for when there is only one paragraph, 924 | // sometimes the attributedString is actually longer by one then the displayed text, 925 | // and this results in not being able to set to lef align anymore. 926 | if (range.length == self.textStorage.length - 1 && range.length == self.string.length) { 927 | ++range.length; 928 | } 929 | 930 | [self.textStorage addAttributes:[NSDictionary dictionaryWithObject:attribute forKey:key] range:range]; 931 | 932 | // Have to update typing attributes because the selection won't change after these attributes have changed. 933 | [self updateTypingAttributes]; 934 | } 935 | else { 936 | // If no text is selected apply attributes to typingAttribute 937 | self.typingAttributesInProgress = YES; 938 | [self applyAttributeToTypingAttribute:attribute forKey:key]; 939 | } 940 | } 941 | 942 | - (void)removeAttributeForKey:(NSString *)key atRange:(NSRange)range { 943 | NSRange initialRange = self.selectedRange; 944 | [self.textStorage removeAttribute:key range:range]; 945 | [self setSelectedRange:initialRange]; 946 | } 947 | 948 | - (void)removeAttributeForKeyFromSelectedRange:(NSString *)key { 949 | [self removeAttributeForKey:key atRange:self.selectedRange]; 950 | } 951 | 952 | - (void)applyAttributesToSelectedRange:(id)attribute forKey:(NSString *)key { 953 | [self applyAttributes:attribute forKey:key atRange:self.selectedRange]; 954 | } 955 | 956 | - (void)applyFontAttributesToSelectedRangeWithBoldTrait:(NSNumber *)isBold italicTrait:(NSNumber *)isItalic fontName:(NSString *)fontName fontSize:(NSNumber *)fontSize { 957 | [self applyFontAttributesWithBoldTrait:isBold italicTrait:isItalic fontName:fontName fontSize:fontSize toTextAtRange:self.selectedRange]; 958 | } 959 | 960 | - (void)applyFontAttributesWithBoldTrait:(NSNumber *)isBold italicTrait:(NSNumber *)isItalic fontName:(NSString *)fontName fontSize:(NSNumber *)fontSize toTextAtRange:(NSRange)range { 961 | // If any text selected apply attributes to text 962 | if (range.length > 0) { 963 | [self.textStorage beginEditing]; 964 | [self.textStorage enumerateAttributesInRange:range 965 | options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired 966 | usingBlock:^(NSDictionary *dictionary, NSRange range, BOOL *stop){ 967 | 968 | NSFont *newFont = [self fontwithBoldTrait:isBold 969 | italicTrait:isItalic 970 | fontName:fontName 971 | fontSize:fontSize 972 | fromDictionary:dictionary]; 973 | 974 | if (newFont) { 975 | [self.textStorage addAttributes:[NSDictionary dictionaryWithObject:newFont forKey:NSFontAttributeName] range:range]; 976 | } 977 | }]; 978 | [self.textStorage endEditing]; 979 | [self setSelectedRange:range]; 980 | [self updateTypingAttributes]; 981 | } 982 | else { 983 | // If no text is selected apply attributes to typingAttribute 984 | self.typingAttributesInProgress = YES; 985 | NSFont *newFont = [self fontwithBoldTrait:isBold 986 | italicTrait:isItalic 987 | fontName:fontName 988 | fontSize:fontSize 989 | fromDictionary:self.typingAttributes]; 990 | if (newFont) { 991 | [self applyAttributeToTypingAttribute:newFont forKey:NSFontAttributeName]; 992 | } 993 | } 994 | } 995 | 996 | -(BOOL)hasSelection { 997 | return self.selectedRange.length > 0; 998 | } 999 | 1000 | // By default, if this function is called with nothing selected, it will resize all text. 1001 | -(void)changeFontSizeWithOperation:(CGFloat(^)(CGFloat currFontSize))operation { 1002 | [self.textStorage beginEditing]; 1003 | NSRange range = self.selectedRange; 1004 | if (range.length == 0) { 1005 | range = NSMakeRange(0, self.textStorage.length); 1006 | } 1007 | [self.textStorage enumerateAttributesInRange:range 1008 | options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired 1009 | usingBlock:^(NSDictionary *dictionary, NSRange range, BOOL *stop){ 1010 | // Get current font size 1011 | NSFont *currFont = [dictionary objectForKey:NSFontAttributeName]; 1012 | if (currFont) { 1013 | CGFloat currFontSize = currFont.pointSize; 1014 | 1015 | CGFloat nextFontSize = operation(currFontSize); 1016 | if ((currFontSize < nextFontSize && nextFontSize <= self.maxFontSize) || // sizing up 1017 | (currFontSize > nextFontSize && self.minFontSize <= nextFontSize)) { // sizing down 1018 | 1019 | NSFont *newFont = [self fontwithBoldTrait:[NSNumber numberWithBool:[currFont isBold]] 1020 | italicTrait:[NSNumber numberWithBool:[currFont isItalic]] 1021 | fontName:currFont.fontName 1022 | fontSize:[NSNumber numberWithFloat:nextFontSize] 1023 | fromDictionary:dictionary]; 1024 | 1025 | if (newFont) { 1026 | [self.textStorage addAttributes:[NSDictionary dictionaryWithObject:newFont forKey:NSFontAttributeName] range:range]; 1027 | } 1028 | } 1029 | } 1030 | }]; 1031 | [self.textStorage endEditing]; 1032 | [self updateTypingAttributes]; 1033 | } 1034 | 1035 | - (void)decreaseFontSize { 1036 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeFontSize]; 1037 | if (self.selectedRange.length == 0) { 1038 | NSMutableDictionary *typingAttributes = [self.typingAttributes mutableCopy]; 1039 | NSFont *font = [typingAttributes valueForKey:NSFontAttributeName]; 1040 | CGFloat nextFontSize = font.pointSize - self.fontSizeChangeAmount; 1041 | if (nextFontSize < self.minFontSize) 1042 | nextFontSize = self.minFontSize; 1043 | NSFont *nextFont = [[NSFontManager sharedFontManager] convertFont:font toSize:nextFontSize]; 1044 | [typingAttributes setValue:nextFont forKey:NSFontAttributeName]; 1045 | self.typingAttributes = typingAttributes; 1046 | } 1047 | else { 1048 | [self changeFontSizeWithOperation:^CGFloat (CGFloat currFontSize) { 1049 | return currFontSize - self.fontSizeChangeAmount; 1050 | }]; 1051 | [self sendDelegateTVChanged]; // only send if the actual text changes -- if no text selected, no text has actually changed 1052 | } 1053 | } 1054 | 1055 | - (void)increaseFontSize { 1056 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeFontSize]; 1057 | if (self.selectedRange.length == 0) { 1058 | NSMutableDictionary *typingAttributes = [self.typingAttributes mutableCopy]; 1059 | NSFont *font = [typingAttributes valueForKey:NSFontAttributeName]; 1060 | CGFloat nextFontSize = font.pointSize + self.fontSizeChangeAmount; 1061 | if (nextFontSize > self.maxFontSize) { 1062 | nextFontSize = self.maxFontSize; 1063 | } 1064 | NSFont *nextFont = [[NSFontManager sharedFontManager] convertFont:font toSize:nextFontSize]; 1065 | [typingAttributes setValue:nextFont forKey:NSFontAttributeName]; 1066 | self.typingAttributes = typingAttributes; 1067 | } 1068 | else { 1069 | [self changeFontSizeWithOperation:^CGFloat (CGFloat currFontSize) { 1070 | return currFontSize + self.fontSizeChangeAmount; 1071 | }]; 1072 | [self sendDelegateTVChanged]; // only send if the actual text changes -- if no text selected, no text has actually changed 1073 | } 1074 | } 1075 | 1076 | // TODO: Fix this function. You can't create a font that isn't bold from a dictionary that has a bold attribute currently, since if you send isBold 0 [nil], it'll use the dictionary, which is bold! 1077 | // In other words, this function has logical errors 1078 | // Returns a font with given attributes. For any missing parameter takes the attribute from a given dictionary 1079 | - (NSFont *)fontwithBoldTrait:(NSNumber *)isBold italicTrait:(NSNumber *)isItalic fontName:(NSString *)fontName fontSize:(NSNumber *)fontSize fromDictionary:(NSDictionary *)dictionary { 1080 | NSFont *newFont = nil; 1081 | NSFont *font = [dictionary objectForKey:NSFontAttributeName]; 1082 | BOOL newBold = (isBold) ? isBold.intValue : [font isBold]; 1083 | BOOL newItalic = (isItalic) ? isItalic.intValue : [font isItalic]; 1084 | CGFloat newFontSize = (fontSize) ? fontSize.floatValue : font.pointSize; 1085 | 1086 | if (fontName) { 1087 | newFont = [NSFont fontWithName:fontName size:newFontSize boldTrait:newBold italicTrait:newItalic]; 1088 | } 1089 | else { 1090 | newFont = [font fontWithBoldTrait:newBold italicTrait:newItalic andSize:newFontSize]; 1091 | } 1092 | 1093 | return newFont; 1094 | } 1095 | 1096 | /** 1097 | * Does not allow cursor to be right beside the bullet point. This method also does not allow selection of the bullet point itself. 1098 | * It uses the previousCursorPosition property to save the previous cursor location. 1099 | * 1100 | * @param beginRange The beginning position of the paragraph 1101 | * @param previousRange The previous cursor position before the new change. Only used for keyboard change events 1102 | * @param currentRange The current cursor position after the new change 1103 | * @param isMouseClick A boolean to check whether the requested change is a mouse event or a keyboard event 1104 | */ 1105 | - (NSRange)adjustSelectedRangeForBulletsWithStart:(NSRange)beginRange Previous:(NSRange)previousRange andCurrent:(NSRange)currentRange isMouseClick:(BOOL)isMouseClick { 1106 | NSUInteger previous = self.previousCursorPosition; 1107 | NSUInteger begin = beginRange.location; 1108 | NSUInteger current = currentRange.location; 1109 | NSRange finalRange = currentRange; 1110 | if (self.justDeletedBackward) { 1111 | return finalRange; 1112 | } 1113 | 1114 | BOOL currentParagraphHasBulletInFront = [[self.string substringFromIndex:begin] hasPrefix:self.BULLET_STRING]; 1115 | if (currentParagraphHasBulletInFront) { 1116 | if (!isMouseClick && (current == begin + 1)) { // select bullet point when using keyboard arrow keys 1117 | if (previousRange.location > current) { 1118 | finalRange = NSMakeRange(begin, currentRange.length + 1); 1119 | } 1120 | else if (previousRange.location < current) { 1121 | finalRange = NSMakeRange(current + 1, currentRange.length - 1); 1122 | } 1123 | } 1124 | else { 1125 | if ((current == begin && (previous > current || previous < current)) || 1126 | (current == (begin + 1) && (previous < current || current == previous))) { // cursor moved from in bullet to front of bullet 1127 | finalRange = currentRange.length >= 1 ? NSMakeRange(begin, finalRange.length + 1) : NSMakeRange(begin + 2, 0); 1128 | } 1129 | else if (current == (begin + 1) && previous > current) { // cursor moved from in bullet to beside of bullet 1130 | BOOL isNewLocationValid = (begin - 1) > [self.string length] ? NO : YES; 1131 | finalRange = currentRange.length >= 1 ? NSMakeRange(begin, finalRange.length + 1) : NSMakeRange(isNewLocationValid ? begin - 1 : begin + 2, 0); 1132 | } 1133 | else if ((current == begin) && (begin == previous) && isMouseClick) { 1134 | finalRange = currentRange.length >= 1 ? NSMakeRange(begin, finalRange.length + 1) : NSMakeRange(begin + 2, 0); 1135 | } 1136 | } 1137 | } 1138 | if (currentRange.location > self.string.length || currentRange.location + currentRange.length > self.string.length) { 1139 | // select the very end of the string. 1140 | // there was a crash report that had an out of range error. Couldn't replicate, so trying 1141 | // to avoid future crashes. 1142 | return NSMakeRange(self.string.length, 0); 1143 | } 1144 | NSRange endingStringRange = [[self.string substringWithRange:currentRange] rangeOfString:@"\n\u2022" options:NSBackwardsSearch]; 1145 | NSUInteger currentRangeAddedProperties = currentRange.location + currentRange.length; 1146 | NSUInteger previousRangeAddedProperties = previousRange.location + previousRange.length; 1147 | BOOL currentParagraphHasBulletAtTheEnd = (endingStringRange.length + endingStringRange.location + currentRange.location) == currentRangeAddedProperties; 1148 | if (currentParagraphHasBulletAtTheEnd) { 1149 | if (isMouseClick) { 1150 | if (previousRange.length > current) { 1151 | finalRange = NSMakeRange(current, currentRange.length + 1); 1152 | } 1153 | else if (previousRange.length < current) { 1154 | finalRange = NSMakeRange(current, currentRange.length - 1); 1155 | } 1156 | } 1157 | else { 1158 | if (previousRangeAddedProperties < currentRangeAddedProperties) { 1159 | finalRange = NSMakeRange(current, currentRange.length + 1); 1160 | } 1161 | else if (previousRangeAddedProperties > currentRangeAddedProperties) { 1162 | finalRange = NSMakeRange(current, currentRange.length - 1); 1163 | } 1164 | } 1165 | } 1166 | 1167 | self.previousCursorPosition = finalRange.location; 1168 | return finalRange; 1169 | } 1170 | 1171 | - (void)applyBulletListIfApplicable { 1172 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 1173 | if (rangeOfCurrentParagraph.location == 0) { 1174 | return; // there isn't a previous paragraph, so forget it. The user isn't in a bulleted list. 1175 | } 1176 | NSRange rangeOfPreviousParagraph = [self.attributedString firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location - 1, 0)]; 1177 | //self.replacementString 1178 | BOOL previousParagraphHasBullet = [[self.string 1179 | substringFromIndex:rangeOfPreviousParagraph.location] hasPrefix:self.BULLET_STRING]; 1180 | if (!self.inBulletedList) { // fixes issue with backspacing into bullet list adding a bullet 1181 | //NSLog(@"[RTE] NOT in a bulleted list."); 1182 | BOOL currentParagraphHasBullet = [[self.string substringFromIndex:rangeOfCurrentParagraph.location] 1183 | hasPrefix:self.BULLET_STRING]; 1184 | BOOL isCurrParaBlank = [[self.string substringWithRange:rangeOfCurrentParagraph] isEqualToString:@""]; 1185 | // if we don't check to see if the current paragraph is blank, bad bugs happen with 1186 | // the current paragraph where the selected range doesn't let the user type O_o 1187 | if (previousParagraphHasBullet && !currentParagraphHasBullet && isCurrParaBlank) { 1188 | // Fix the indentation. Here is the use case for this code: 1189 | /* 1190 | --- 1191 | • bullet 1192 | 1193 | | 1194 | --- 1195 | Where | is the cursor on a blank line. User hits backspace. Without fixing the 1196 | indentation, the cursor ends up indented at the same indentation as the bullet. 1197 | */ 1198 | NSDictionary *dictionary = [self dictionaryAtIndex:rangeOfCurrentParagraph.location]; 1199 | NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 1200 | paragraphStyle.firstLineHeadIndent = 0; 1201 | paragraphStyle.headIndent = 0; 1202 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:rangeOfCurrentParagraph]; 1203 | [self setIndentationWithAttributes:dictionary paragraphStyle:paragraphStyle atRange:rangeOfCurrentParagraph]; 1204 | } 1205 | return; 1206 | } 1207 | if (rangeOfCurrentParagraph.length != 0 && !(previousParagraphHasBullet && [self.latestReplacementString isEqualToString:@"\n"])) { 1208 | return; 1209 | } 1210 | if (!self.justDeletedBackward && [[self.string substringFromIndex:rangeOfPreviousParagraph.location] hasPrefix:self.BULLET_STRING]) { 1211 | [self userSelectedBullet]; 1212 | } 1213 | } 1214 | 1215 | - (void)removeBulletIndentation:(NSRange)firstParagraphRange { 1216 | NSRange rangeOfParagraph = [self.attributedString firstParagraphRangeFromTextRange:firstParagraphRange]; 1217 | NSDictionary *dictionary = [self dictionaryAtIndex:rangeOfParagraph.location]; 1218 | NSMutableParagraphStyle *paragraphStyle = [[dictionary objectForKey:NSParagraphStyleAttributeName] mutableCopy]; 1219 | paragraphStyle.firstLineHeadIndent = 0; 1220 | paragraphStyle.headIndent = 0; 1221 | [self applyAttributes:paragraphStyle forKey:NSParagraphStyleAttributeName atRange:rangeOfParagraph]; 1222 | [self setIndentationWithAttributes:dictionary paragraphStyle:paragraphStyle atRange:firstParagraphRange]; 1223 | } 1224 | 1225 | - (void)deleteBulletListWhenApplicable { 1226 | NSRange range = self.selectedRange; 1227 | // TODO: Clean up this code since a lot of it is "repeated" 1228 | if (range.location > 0) { 1229 | NSString *checkString = self.BULLET_STRING; 1230 | if (checkString.length > 1) { 1231 | // chop off last letter and use that 1232 | checkString = [checkString substringToIndex:checkString.length - 1]; 1233 | } 1234 | //else return; 1235 | NSUInteger checkStringLength = [checkString length]; 1236 | if (![self.string isEqualToString:self.BULLET_STRING]) { 1237 | if (((int)(range.location-checkStringLength) >= 0 && 1238 | [[self.string substringFromIndex:range.location-checkStringLength] hasPrefix:checkString])) { 1239 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeBullet]; 1240 | //NSLog(@"[RTE] Getting rid of a bullet due to backspace while in empty bullet paragraph."); 1241 | // Get rid of bullet string 1242 | [self.textStorage deleteCharactersInRange:NSMakeRange(range.location-checkStringLength, checkStringLength)]; 1243 | NSRange newRange = NSMakeRange(range.location-checkStringLength, 0); 1244 | self.selectedRange = newRange; 1245 | 1246 | // Get rid of bullet indentation 1247 | [self removeBulletIndentation:newRange]; 1248 | } 1249 | else { 1250 | // User may be needing to get out of a bulleted list due to hitting enter (return) 1251 | NSRange rangeOfCurrentParagraph = [self.attributedString firstParagraphRangeFromTextRange:self.selectedRange]; 1252 | NSString *currentParagraphString = [self.string substringWithRange:rangeOfCurrentParagraph]; 1253 | NSInteger prevParaLocation = rangeOfCurrentParagraph.location-1; 1254 | // [currentParagraphString isEqualToString:self.BULLET_STRING] ==> "is the current paragraph an empty bulleted list item?" 1255 | if (prevParaLocation >= 0 && [currentParagraphString isEqualToString:self.BULLET_STRING]) { 1256 | NSRange rangeOfPreviousParagraph = [self.attributedString firstParagraphRangeFromTextRange:NSMakeRange(rangeOfCurrentParagraph.location-1, 0)]; 1257 | // If the following if statement is true, the user hit enter on a blank bullet list 1258 | // Basically, there is now a bullet ' ' \n bullet ' ' that we need to delete (' ' == space) 1259 | // Since it gets here AFTER it adds a new bullet 1260 | if ([[self.string substringWithRange:rangeOfPreviousParagraph] hasSuffix:self.BULLET_STRING]) { 1261 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeBullet]; 1262 | //NSLog(@"[RTE] Getting rid of bullets due to user hitting enter."); 1263 | NSRange rangeToDelete = NSMakeRange(rangeOfPreviousParagraph.location, rangeOfPreviousParagraph.length+rangeOfCurrentParagraph.length+1); 1264 | [self.textStorage deleteCharactersInRange:rangeToDelete]; 1265 | NSRange newRange = NSMakeRange(rangeOfPreviousParagraph.location, 0); 1266 | self.selectedRange = newRange; 1267 | // Get rid of bullet indentation 1268 | [self removeBulletIndentation:newRange]; 1269 | } 1270 | } 1271 | } 1272 | } 1273 | } 1274 | } 1275 | 1276 | - (void)mouseDown:(NSEvent *)theEvent { 1277 | _lastSingleKeyPressed = 0; 1278 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeMouseDown]; 1279 | [super mouseDown:theEvent]; 1280 | } 1281 | 1282 | - (NSString*)bulletString { 1283 | return self.BULLET_STRING; 1284 | } 1285 | 1286 | + (NSString *)convertPreviewChangeTypeToString:(RichTextEditorPreviewChange)changeType withNonSpecialChangeText:(BOOL)shouldReturnStringForNonSpecialType { 1287 | switch (changeType) { 1288 | case RichTextEditorPreviewChangeBold: 1289 | return NSLocalizedString(@"Bold", @""); 1290 | case RichTextEditorPreviewChangeCut: 1291 | return NSLocalizedString(@"Cut", @""); 1292 | case RichTextEditorPreviewChangePaste: 1293 | return NSLocalizedString(@"Paste", @""); 1294 | case RichTextEditorPreviewChangeBullet: 1295 | return NSLocalizedString(@"Bulleted List", @""); 1296 | case RichTextEditorPreviewChangeItalic: 1297 | return NSLocalizedString(@"Italic", @""); 1298 | case RichTextEditorPreviewChangeFontResize: 1299 | case RichTextEditorPreviewChangeFontSize: 1300 | return NSLocalizedString(@"Font Resize", @""); 1301 | case RichTextEditorPreviewChangeFontColor: 1302 | return NSLocalizedString(@"Font Color", @""); 1303 | case RichTextEditorPreviewChangeHighlight: 1304 | return NSLocalizedString(@"Text Highlight", @""); 1305 | case RichTextEditorPreviewChangeUnderline: 1306 | return NSLocalizedString(@"Underline", @""); 1307 | case RichTextEditorPreviewChangeIndentDecrease: 1308 | case RichTextEditorPreviewChangeIndentIncrease: 1309 | return NSLocalizedString(@"Text Indent", @""); 1310 | case RichTextEditorPreviewChangeKeyDown: 1311 | if (shouldReturnStringForNonSpecialType) 1312 | return NSLocalizedString(@"Key Down", @""); 1313 | break; 1314 | case RichTextEditorPreviewChangeEnter: 1315 | if (shouldReturnStringForNonSpecialType) 1316 | return NSLocalizedString(@"Enter [Return] Key", @""); 1317 | break; 1318 | case RichTextEditorPreviewChangeSpace: 1319 | if (shouldReturnStringForNonSpecialType) 1320 | return NSLocalizedString(@"Space", @""); 1321 | break; 1322 | case RichTextEditorPreviewChangeDelete: 1323 | if (shouldReturnStringForNonSpecialType) 1324 | return NSLocalizedString(@"Delete", @""); 1325 | break; 1326 | case RichTextEditorPreviewChangeArrowKey: 1327 | if (shouldReturnStringForNonSpecialType) 1328 | return NSLocalizedString(@"Arrow Key Movement", @""); 1329 | break; 1330 | case RichTextEditorPreviewChangeMouseDown: 1331 | if (shouldReturnStringForNonSpecialType) 1332 | return NSLocalizedString(@"Mouse Down", @""); 1333 | break; 1334 | case RichTextEditorPreviewChangeFindReplace: 1335 | return NSLocalizedString(@"Find & Replace", @""); 1336 | default: 1337 | break; 1338 | } 1339 | return @""; 1340 | } 1341 | 1342 | #pragma mark - Keyboard Shortcuts 1343 | 1344 | // http://stackoverflow.com/questions/970707/cocoa-keyboard-shortcuts-in-dialog-without-an-edit-menu 1345 | - (void)keyDown:(NSEvent*)event { 1346 | NSString *key = event.charactersIgnoringModifiers; 1347 | if (key.length > 0) { 1348 | NSUInteger enabledShortcuts = RichTextEditorShortcutAll; 1349 | if (self.rteDataSource && [self.rteDataSource respondsToSelector:@selector(enabledKeyboardShortcuts)]) { 1350 | enabledShortcuts = [self.rteDataSource enabledKeyboardShortcuts]; 1351 | } 1352 | 1353 | unichar keyChar = 0; 1354 | bool shiftKeyDown = event.modifierFlags & NSShiftKeyMask; 1355 | bool commandKeyDown = event.modifierFlags & NSCommandKeyMask; 1356 | keyChar = [key characterAtIndex:0]; 1357 | _lastSingleKeyPressed = keyChar; 1358 | if (keyChar == NSLeftArrowFunctionKey || keyChar == NSRightArrowFunctionKey || 1359 | keyChar == NSUpArrowFunctionKey || keyChar == NSDownArrowFunctionKey) { 1360 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeArrowKey]; 1361 | [super keyDown:event]; 1362 | } 1363 | else if ((keyChar == 'b' || keyChar == 'B') && commandKeyDown && !shiftKeyDown && 1364 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutBold)) { 1365 | [self userSelectedBold]; 1366 | } 1367 | else if ((keyChar == 'i' || keyChar == 'I') && commandKeyDown && !shiftKeyDown && 1368 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutItalic)) { 1369 | [self userSelectedItalic]; 1370 | } 1371 | else if ((keyChar == 'u' || keyChar == 'U') && commandKeyDown && !shiftKeyDown && 1372 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutUnderline)) { 1373 | [self userSelectedUnderline]; 1374 | } 1375 | else if (keyChar == '>' && shiftKeyDown && commandKeyDown && 1376 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutIncreaseFontSize)) { 1377 | [self increaseFontSize]; 1378 | } 1379 | else if (keyChar == '<' && shiftKeyDown && commandKeyDown && 1380 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutDecreaseFontSize)) { 1381 | [self decreaseFontSize]; 1382 | } 1383 | else if (keyChar == 'L' && shiftKeyDown && commandKeyDown && 1384 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutBulletedList)) { 1385 | [self userSelectedBullet]; 1386 | } 1387 | else if (keyChar == 'N' && shiftKeyDown && commandKeyDown && [self isInBulletedList] && 1388 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutLeaveBulletedList)) { 1389 | [self userSelectedBullet]; 1390 | } 1391 | else if (keyChar == 'T' && shiftKeyDown && commandKeyDown && 1392 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutDecreaseIndent)) { 1393 | [self userSelectedDecreaseIndent]; 1394 | } 1395 | else if (keyChar == 't' && commandKeyDown && !shiftKeyDown && 1396 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutIncreaseIndent)) { 1397 | [self userSelectedIncreaseIndent]; 1398 | } 1399 | else if (self.tabKeyAlwaysIndentsOutdents && keyChar == '\t' && !commandKeyDown && !shiftKeyDown && 1400 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutIncreaseIndent)) { 1401 | [self userSelectedIncreaseIndent]; 1402 | } 1403 | else if (self.tabKeyAlwaysIndentsOutdents && (keyChar == '\t' || keyChar == 25) && !commandKeyDown && shiftKeyDown && 1404 | (enabledShortcuts == RichTextEditorShortcutAll || enabledShortcuts & RichTextEditorShortcutIncreaseIndent)) { 1405 | [self userSelectedDecreaseIndent]; 1406 | } 1407 | else if (!([self.rteDelegate respondsToSelector:@selector(richTextEditor:keyDownEvent:)] && [self.rteDelegate richTextEditor:self keyDownEvent:event])) { 1408 | [self sendDelegatePreviewChangeOfType:RichTextEditorPreviewChangeKeyDown]; 1409 | [super keyDown:event]; 1410 | } 1411 | } 1412 | else { 1413 | [super keyDown:event]; 1414 | } 1415 | } 1416 | 1417 | @end 1418 | --------------------------------------------------------------------------------