-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathNSButton+Dejal.m
276 lines (209 loc) · 8.99 KB
/
NSButton+Dejal.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
//
// NSButton+Dejal.m
// Dejal Open Source Categories
//
// Created by David Sinclair on 2014-10-04.
// Copyright (c) 2014-2015 Dejal Systems, LLC. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#import "NSButton+Dejal.h"
@implementation NSButton (Dejal)
/**
Returns the color of the receiver's text.
@author DJS 2014-10, based on Apple's Popover sample code.
*/
- (NSColor *)dejal_textColor;
{
NSAttributedString *attrTitle = self.attributedTitle;
NSColor *textColor = [NSColor controlTextColor];
if (attrTitle.length)
{
NSDictionary *attrs = [attrTitle fontAttributesInRange:NSMakeRange(0, 1)];
if (attrs)
{
textColor = [attrs objectForKey:NSForegroundColorAttributeName];
}
}
return textColor;
}
/**
Sets the receiver's text to the specified color.
@author DJS 2014-10, based on Apple's Popover sample code.
*/
- (void)dejal_setTextColor:(NSColor *)textColor;
{
NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedTitle];
NSRange range = NSMakeRange(0, attrTitle.length);
if (textColor)
{
[attrTitle addAttribute:NSForegroundColorAttributeName value:textColor range:range];
}
else
{
[attrTitle removeAttribute:NSForegroundColorAttributeName range:range];
}
[attrTitle fixAttributesInRange:range];
self.attributedTitle = attrTitle;
}
/**
Displays a menu below the receiver.
@param menu The menu to display.
@author DJS 2015-04.
*/
- (void)dejal_displayMenu:(NSMenu *)menu;
{
[self dejal_displayMenu:menu withOffset:4];
}
/**
Displays a menu below the receiver.
@param menu The menu to display.
@param verticalOffset the offset to adjust vertically.
@author DJS 2014-10.
@version DJS 2015-04: Changed to use -popUpMenuPositioningItem::: instead of +popUpContextMenu:menu::.
*/
- (void)dejal_displayMenu:(NSMenu *)menu withOffset:(CGFloat)verticalOffset;
{
[self dejal_displayMenu:menu withHorizontalOffset:0.0 verticalOffset:verticalOffset];
}
/**
Displays a menu below the receiver.
@param menu The menu to display.
@param horizontalOffset the offset to adjust horizontally.
@param verticalOffset the offset to adjust vertically.
@author DJS 2014-10.
@version DJS 2015-04: Changed to use -popUpMenuPositioningItem::: instead of +popUpContextMenu:menu::, and add a horizontal offset.
*/
- (void)dejal_displayMenu:(NSMenu *)menu withHorizontalOffset:(CGFloat)horizontalOffset verticalOffset:(CGFloat)verticalOffset;
{
NSPoint location = self.frame.origin;
location.x += horizontalOffset;
location.y -= verticalOffset;
[menu popUpMenuPositioningItem:nil atLocation:location inView:self.superview];
}
/*
- (void)dejal_displayMenu:(NSMenu *)menu withOffset:(CGFloat)verticalOffset;
{
NSRect frame = self.frame;
NSPoint location = [self convertPoint:NSMakePoint(NSMinX(frame) - 5.0, NSMaxY(frame) + verticalOffset) toView:nil];
NSEvent *event = [NSApp currentEvent];
event = [NSEvent mouseEventWithType:event.type location:location modifierFlags:event.modifierFlags timestamp:event.timestamp windowNumber:event.windowNumber context:event.context eventNumber:event.eventNumber clickCount:event.clickCount pressure:event.pressure];
[NSMenu popUpContextMenu:menu withEvent:event forView:self];
}
*/
@end
// ----------------------------------------------------------------------------------------
#pragma mark -
// ----------------------------------------------------------------------------------------
@implementation NSButton (DejalRadios)
/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and selects the one with the specified tag. Invoke this on any of the radios in the group. A replacement for -[NSMatrix selectCellWithTag:].
@param tag The tag value to select.
@author DJS 2015-01.
*/
- (void)dejal_selectRadioWithTag:(NSInteger)tag;
{
[self dejal_enumerateRadiosUsingBlock:^(NSButton *radio, BOOL *stop)
{
radio.state = radio.tag == tag;
}];
}
/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and returns the tag value of the selected radio. Invoke this on any of the radios in the group. A replacement for -[NSMatrix selectedTag].
@returns A tag value integer.
@author DJS 2015-01.
*/
- (NSInteger)dejal_selectedRadioTag;
{
NSButton *foundRadio = [self dejal_radioPassingTest:^BOOL(NSButton *radio, BOOL *stop)
{
return radio.state;
}];
return foundRadio.tag;
}
/**
Returns YES if the radio group is enabled, or NO if not. Simply returns the state of the receiver; the others are assumed to be the same. (If you want to know if they are all enabled or disabled, probably best to use -dejal_enumerateRadiosUsingBlock: to scan the group, and handle a mixed case as needed.)
@author DJS 2015-01.
*/
- (BOOL)dejal_radiosEnabled;
{
return self.enabled;
}
/**
Sets all of the radios in the group to be enabled or disabled. A replacement for -[NSMatrix setEnabled:].
@author DJS 2015-01.
*/
- (void)dejal_setRadiosEnabled:(BOOL)enabled;
{
[self dejal_enumerateRadiosUsingBlock:^(NSButton *radio, BOOL *stop)
{
radio.enabled = enabled;
}];
}
/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and performs the block for each of them, passing the radio to the block. Returns the one that returns YES, or nil if the block requests to stop before completion, or it completes without the block returning YES. Invoke this on any of the radios in the group.
@param block A block that takes a radio button and stop boolean reference as parameters and returns a boolean.
@returns The found radio button, or nil if none is found.
@author DJS 2015-01.
*/
- (NSButton *)dejal_radioPassingTest:(BOOL (^)(NSButton *radio, BOOL *stop))predicate;
{
for (NSButton *radio in self.superview.subviews)
{
// There's no reliable way to determine if a button is actually a radio button, but it's reasonable to assume that no non-radio will have the same action (and having the same action is what makes it a member of the group):
if ([radio isKindOfClass:[NSButton class]] && radio.action == self.action && predicate)
{
BOOL stop = NO;
if (predicate(radio, &stop))
{
return radio;
}
if (stop)
{
return nil;
}
}
}
return nil;
}
/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and performs the block for each of them, passing the radio to the block. Invoke this on any of the radios in the group.
@param block A block that takes a radio button and stop boolean reference as parameters and returns void.
@author DJS 2015-01.
*/
- (void)dejal_enumerateRadiosUsingBlock:(void (^)(NSButton *radio, BOOL *stop))block;
{
for (NSButton *radio in self.superview.subviews)
{
// There's no reliable way to determine if a button is actually a radio button, but it's reasonable to assume that no non-radio will have the same action (and having the same action is what makes it a member of the group):
if ([radio isKindOfClass:[NSButton class]] && radio.action == self.action && block)
{
BOOL stop = NO;
block(radio, &stop);
if (stop)
{
return;
}
}
}
}
@end