-
Notifications
You must be signed in to change notification settings - Fork 55
/
Copy pathPGInspectorPanelController.m
362 lines (306 loc) · 16.1 KB
/
PGInspectorPanelController.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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/* Copyright © 2007-2009, The Sequential Project
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.
* Neither the name of the the Sequential Project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE SEQUENTIAL PROJECT ''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 SEQUENTIAL PROJECT 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 "PGInspectorPanelController.h"
// Models
#import "PGNode.h"
#import "PGResourceAdapter.h"
// Controllers
#import "PGDocumentController.h"
#import "PGDisplayController.h"
// Other Sources
#import "PGFoundationAdditions.h"
#import "PGGeometry.h"
#import "PGZooming.h"
@interface NSObject(PGAdditions)
- (id)PG_replacementUsingObject:(id)replacement preserveUnknown:(BOOL)preserve getTopLevelKey:(out id *)outKey;
@end
@interface NSDictionary(PGAdditions)
- (NSDictionary *)PG_flattenedDictionary;
@end
@interface PGInspectorPanelController(Private)
- (void)_updateColumnWidths;
- (NSDictionary *)_humanReadablePropertiesWithDictionary:(NSDictionary *)dict;
- (NSString *)_stringWithDateTime:(NSString *)dateTime subsecTime:(NSString *)subsecTime;
@end
@implementation PGInspectorPanelController
#pragma mark -PGInspectorPanelController
- (IBAction)changeSearch:(id)sender
{
NSMutableDictionary *const matchingProperties = [NSMutableDictionary dictionary];
NSArray *const terms = [[searchField stringValue] PG_searchTerms];
for(NSString *const label in _properties) {
NSString *const value = [_properties objectForKey:label];
if([label PG_matchesSearchTerms:terms] || [[value description] PG_matchesSearchTerms:terms]) {
[matchingProperties setObject:value forKey:label];
}
}
[_matchingProperties release];
_matchingProperties = [matchingProperties copy];
[_matchingLabels release];
_matchingLabels = [[[matchingProperties allKeys] sortedArrayUsingSelector:@selector(compare:)] copy];
[propertiesTable reloadData];
[self _updateColumnWidths];
}
- (IBAction)copy:(id)sender
{
NSMutableString *const string = [NSMutableString string];
NSIndexSet *const indexes = [propertiesTable selectedRowIndexes];
NSUInteger i = [indexes firstIndex];
for(; NSNotFound != i; i = [indexes indexGreaterThanIndex:i]) {
NSString *const label = [_matchingLabels objectAtIndex:i];
[string appendFormat:@"%@: %@\n", label, [_matchingProperties objectForKey:label]];
}
NSPasteboard *const pboard = [NSPasteboard generalPasteboard];
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
[pboard setString:string forType:NSStringPboardType];
}
#pragma mark -
- (void)displayControllerActiveNodeWasRead:(NSNotification *)aNotif
{
[_properties release];
_properties = [[self _humanReadablePropertiesWithDictionary:[[[[self displayController] activeNode] resourceAdapter] imageProperties]] copy];
[self changeSearch:nil];
}
#pragma mark -PGInspectorPanelController(Private)
- (void)_updateColumnWidths
{
[labelColumn setWidth:[labelColumn PG_zoomedWidth]];
[valueColumn setWidth:NSWidth([propertiesTable bounds]) - [labelColumn width]];
}
- (NSDictionary *)_humanReadablePropertiesWithDictionary:(NSDictionary *)dict
{
NSDictionary *const keyLabels = [NSDictionary dictionaryWithObjectsAndKeys:
@"Color Model", (NSString *)kCGImagePropertyColorModel,
@"Profile Name", (NSString *)kCGImagePropertyProfileName,
[NSDictionary dictionaryWithObjectsAndKeys:
@"TIFF", @".",
@"Compression", (NSString *)kCGImagePropertyTIFFCompression,
@"Photometric Interpretation", (NSString *)kCGImagePropertyTIFFPhotometricInterpretation,
@"Document Name", (NSString *)kCGImagePropertyTIFFDocumentName,
@"Image Description", (NSString *)kCGImagePropertyTIFFImageDescription,
@"Make", (NSString *)kCGImagePropertyTIFFMake,
@"Model", (NSString *)kCGImagePropertyTIFFModel,
@"Software", (NSString *)kCGImagePropertyTIFFSoftware,
@"Transfer Function", (NSString *)kCGImagePropertyTIFFTransferFunction,
@"Artist", (NSString *)kCGImagePropertyTIFFArtist,
@"Host Computer", (NSString *)kCGImagePropertyTIFFHostComputer,
@"Copyright", (NSString *)kCGImagePropertyTIFFCopyright,
@"White Point", (NSString *)kCGImagePropertyTIFFWhitePoint,
@"Primary Chromaticities", (NSString *)kCGImagePropertyTIFFPrimaryChromaticities,
nil], (NSString *)kCGImagePropertyTIFFDictionary,
[NSDictionary dictionaryWithObjectsAndKeys:
@"JFIF", @".",
@"Progressive", (NSString *)kCGImagePropertyJFIFIsProgressive,
nil], (NSString *)kCGImagePropertyJFIFDictionary,
[NSDictionary dictionaryWithObjectsAndKeys:
@"Exif", @".",
@"Exposure Time", (NSString *)kCGImagePropertyExifExposureTime,
@"F Number", (NSString *)kCGImagePropertyExifFNumber,
@"Exposure Program", (NSString *)kCGImagePropertyExifExposureProgram,
@"Spectral Sensitivity", (NSString *)kCGImagePropertyExifSpectralSensitivity,
@"OECF", (NSString *)kCGImagePropertyExifOECF,
@"Components Configuration", (NSString *)kCGImagePropertyExifComponentsConfiguration,
@"Compressed BPP", (NSString *)kCGImagePropertyExifCompressedBitsPerPixel,
@"Shutter Speed", (NSString *)kCGImagePropertyExifShutterSpeedValue,
@"Aperture", (NSString *)kCGImagePropertyExifApertureValue,
@"Brightness", (NSString *)kCGImagePropertyExifBrightnessValue,
@"Exposure Bias", (NSString *)kCGImagePropertyExifExposureBiasValue,
@"Max Aperture", (NSString *)kCGImagePropertyExifMaxApertureValue,
@"Subject Distance", (NSString *)kCGImagePropertyExifSubjectDistance,
@"Metering Mode", (NSString *)kCGImagePropertyExifMeteringMode,
@"Light Source", (NSString *)kCGImagePropertyExifLightSource,
@"Flash", (NSString *)kCGImagePropertyExifFlash,
@"Focal Length", (NSString *)kCGImagePropertyExifFocalLength,
@"Subject Area", (NSString *)kCGImagePropertyExifSubjectArea,
@"Maker Note", (NSString *)kCGImagePropertyExifMakerNote,
@"User Comment", (NSString *)kCGImagePropertyExifUserComment,
@"Color Space", (NSString *)kCGImagePropertyExifColorSpace,
@"Related Sound File", (NSString *)kCGImagePropertyExifRelatedSoundFile,
@"Flash Energy", (NSString *)kCGImagePropertyExifFlashEnergy,
@"Spatial Frequency Response", (NSString *)kCGImagePropertyExifSpatialFrequencyResponse,
@"Focal Plane X Resolution", (NSString *)kCGImagePropertyExifFocalPlaneXResolution,
@"Focal Plane Y Resolution", (NSString *)kCGImagePropertyExifFocalPlaneYResolution,
@"Focal Plane Resolution Unit", (NSString *)kCGImagePropertyExifFocalPlaneResolutionUnit,
@"Subject Location", (NSString *)kCGImagePropertyExifSubjectLocation,
@"Exposure Index", (NSString *)kCGImagePropertyExifExposureIndex,
@"Sensing Method", (NSString *)kCGImagePropertyExifSensingMethod,
@"File Source", (NSString *)kCGImagePropertyExifFileSource,
@"Scene Type", (NSString *)kCGImagePropertyExifSceneType,
@"Custom Rendered", (NSString *)kCGImagePropertyExifCustomRendered,
@"Exposure Mode", (NSString *)kCGImagePropertyExifExposureMode,
@"White Balance", (NSString *)kCGImagePropertyExifWhiteBalance,
@"Digital Zoom Ratio", (NSString *)kCGImagePropertyExifDigitalZoomRatio,
@"Focal Length (35mm Film)", (NSString *)kCGImagePropertyExifFocalLenIn35mmFilm,
@"Scene Capture Type", (NSString *)kCGImagePropertyExifSceneCaptureType,
@"Gain Control", (NSString *)kCGImagePropertyExifGainControl,
@"Contrast", (NSString *)kCGImagePropertyExifContrast,
@"Saturation", (NSString *)kCGImagePropertyExifSaturation,
@"Sharpness", (NSString *)kCGImagePropertyExifSharpness,
@"Device Setting Description", (NSString *)kCGImagePropertyExifDeviceSettingDescription,
@"Subject Dist Range", (NSString *)kCGImagePropertyExifSubjectDistRange,
@"Image Unique ID", (NSString *)kCGImagePropertyExifImageUniqueID,
@"Gamma", (NSString *)kCGImagePropertyExifGamma,
nil], (NSString *)kCGImagePropertyExifDictionary,
[NSDictionary dictionaryWithObjectsAndKeys:
@"Exif (Aux)", @".",
@"Lens Model", (NSString *)kCGImagePropertyExifAuxLensModel,
@"Serial Number", (NSString *)kCGImagePropertyExifAuxSerialNumber,
@"Lens ID", (NSString *)kCGImagePropertyExifAuxLensID,
@"Lens Serial Number", (NSString *)kCGImagePropertyExifAuxLensSerialNumber,
@"Image Number", (NSString *)kCGImagePropertyExifAuxImageNumber,
@"Flash Compensation", (NSString *)kCGImagePropertyExifAuxFlashCompensation,
@"Owner Name", (NSString *)kCGImagePropertyExifAuxOwnerName,
@"Firmware", (NSString *)kCGImagePropertyExifAuxFirmware,
nil], (NSString *)kCGImagePropertyExifAuxDictionary,
nil];
NSMutableDictionary *const properties = [[[[dict PG_replacementUsingObject:keyLabels preserveUnknown:NO getTopLevelKey:NULL] PG_flattenedDictionary] mutableCopy] autorelease];
// TODO: Create special formatters for certain properties.
/*
kCGImagePropertyExifFNumber (?)
kCGImagePropertyExifExposureProgram (?)
kCGImagePropertyExifISOSpeedRatings (?)
Check other properties as well.
*/
NSNumber *const depth = [dict objectForKey:(NSString *)kCGImagePropertyDepth];
if(depth) [properties setObject:[NSString stringWithFormat:@"%lu bits per sample", [depth unsignedLongValue]] forKey:@"Depth"];
NSNumber *const pixelWidth = [dict objectForKey:(NSString *)kCGImagePropertyPixelWidth];
NSNumber *const pixelHeight = [dict objectForKey:(NSString *)kCGImagePropertyPixelHeight];
if(pixelWidth || pixelHeight) [properties setObject:[NSString stringWithFormat:@"%lux%lu", [pixelWidth unsignedLongValue], [pixelHeight unsignedLongValue]] forKey:@"Pixel Count"];
NSNumber *const densityWidth = [dict objectForKey:(NSString *)kCGImagePropertyDPIWidth];
NSNumber *const densityHeight = [dict objectForKey:(NSString *)kCGImagePropertyDPIHeight];
if(densityWidth || densityHeight) [properties setObject:[NSString stringWithFormat:PGEqualObjects(densityWidth, densityHeight) ? @"%lux%lu DPI" : @"%lux%lu DPI", (unsigned long)round([densityWidth doubleValue]), (unsigned long)round([densityHeight doubleValue])] forKey:@"Pixel Density"];
if([[dict objectForKey:(NSString *)kCGImagePropertyHasAlpha] boolValue]) [properties setObject:@"Yes" forKey:@"Alpha"];
PGOrientation const orientation = PGOrientationWithTIFFOrientation([[dict objectForKey:(NSString *)kCGImagePropertyOrientation] unsignedIntegerValue]);
if(PGUpright != orientation) [properties setObject:PGLocalizedStringWithOrientation(orientation) forKey:@"Orientation"];
NSDictionary *const TIFFDict = [dict objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
NSDictionary *const exifDict = [dict objectForKey:(NSString *)kCGImagePropertyExifDictionary];
NSString *const dateTime = [self _stringWithDateTime:[TIFFDict objectForKey:(NSString *)kCGImagePropertyTIFFDateTime] subsecTime:[exifDict objectForKey:(NSString *)kCGImagePropertyExifSubsecTime]];
[properties PG_setObject:dateTime forKey:@"Date/Time"];
NSString *const dateTimeOriginal = [self _stringWithDateTime:[exifDict objectForKey:(NSString *)kCGImagePropertyExifDateTimeOriginal] subsecTime:[exifDict objectForKey:(NSString *)kCGImagePropertyExifSubsecTimeOrginal]];
if(!PGEqualObjects(dateTime, dateTimeOriginal)) [properties PG_setObject:dateTimeOriginal forKey:@"Date/Time (Original)"];
NSString *const dateTimeDigitized = [self _stringWithDateTime:[exifDict objectForKey:(NSString *)kCGImagePropertyExifDateTimeDigitized] subsecTime:[exifDict objectForKey:(NSString *)kCGImagePropertyExifSubsecTimeDigitized]];
if(!PGEqualObjects(dateTime, dateTimeDigitized)) [properties PG_setObject:dateTimeDigitized forKey:@"Date/Time (Digitized)"];
return properties;
}
- (NSString *)_stringWithDateTime:(NSString *)dateTime subsecTime:(NSString *)subsecTime
{
if(!dateTime) return nil;
if(!subsecTime) return dateTime;
return [NSString stringWithFormat:@"%@.%@", dateTime, subsecTime];
}
#pragma mark -PGFloatingPanelController
- (NSString *)nibName
{
return @"PGInspector";
}
- (BOOL)setDisplayController:(PGDisplayController *)controller
{
PGDisplayController *const oldController = [self displayController];
if(![super setDisplayController:controller]) return NO;
[oldController PG_removeObserver:self name:PGDisplayControllerActiveNodeWasReadNotification];
[[self displayController] PG_addObserver:self selector:@selector(displayControllerActiveNodeWasRead:) name:PGDisplayControllerActiveNodeWasReadNotification];
[self displayControllerActiveNodeWasRead:nil];
return YES;
}
#pragma mark -NSWindowController
- (void)windowDidLoad
{
[super windowDidLoad];
[self _updateColumnWidths];
}
#pragma mark -NSObject
- (void)dealloc
{
[propertiesTable setDelegate:nil];
[propertiesTable setDataSource:nil];
[_properties release];
[_matchingProperties release];
[_matchingLabels release];
[super dealloc];
}
#pragma mark -NSObject(NSMenuValidation)
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
SEL const action = [anItem action];
if(@selector(copy:) == action && ![[propertiesTable selectedRowIndexes] count]) return NO;
return [super validateMenuItem:anItem];
}
#pragma mark -<NSTableDataSource>
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [_matchingLabels count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *const label = [_matchingLabels objectAtIndex:row];
if(tableColumn == labelColumn) {
return label;
} else if(tableColumn == valueColumn) {
return [_matchingProperties objectForKey:label];
}
return nil;
}
#pragma mark -<NSTableViewDelegate>
- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if(tableColumn == labelColumn) {
[cell setAlignment:NSRightTextAlignment];
[cell setFont:[[NSFontManager sharedFontManager] convertFont:[cell font] toHaveTrait:NSBoldFontMask]];
}
}
@end
@implementation NSObject(PGAdditions)
#pragma mark -NSObject(PGAdditions)
- (id)PG_replacementUsingObject:(id)replacement preserveUnknown:(BOOL)preserve getTopLevelKey:(out id *)outKey
{
if(!replacement) return preserve ? self : nil;
if(outKey) *outKey = replacement;
return self;
}
@end
@implementation NSDictionary(PGAdditions)
#pragma mark -NSDictionary(PGAdditions)
- (NSDictionary *)PG_flattenedDictionary
{
NSMutableDictionary *const results = [NSMutableDictionary dictionary];
for(id const key in self) {
id const obj = [self objectForKey:key];
if([obj isKindOfClass:[NSDictionary class]]) [results addEntriesFromDictionary:obj];
else [results setObject:obj forKey:key];
}
return results;
}
#pragma mark -NSObject(PGAdditions)
- (id)PG_replacementUsingObject:(id)replacement preserveUnknown:(BOOL)preserve getTopLevelKey:(out id *)outKey
{
if(![replacement isKindOfClass:[NSDictionary class]]) return [super PG_replacementUsingObject:replacement preserveUnknown:preserve getTopLevelKey:outKey];
NSMutableDictionary *const result = [NSMutableDictionary dictionary];
for(id const key in self) {
id replacementKey = key;
id const replacementObj = [[self objectForKey:key] PG_replacementUsingObject:[(NSDictionary *)replacement objectForKey:key] preserveUnknown:preserve getTopLevelKey:&replacementKey];
if(replacementObj) [result setObject:replacementObj forKey:replacementKey];
}
if(outKey) *outKey = [(NSDictionary *)replacement objectForKey:@"."];
return result;
}
@end