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 |
--------------------------------------------------------------------------------