/* ** AttributesViewer.m ** ** Copyright (c) 2004 ** ** Author: Yen-Ju Chen ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "LibrarySource.h" #include "AttributesViewer.h" #include "AttributesSource.h" #include "AttributesOutlineView.h" #include "GNUstep.h" #include "Constants.h" #include "Utilities.h" #include "RegEx.h" static NSString *AttributesPboard = @"MyLibraryAttributesPboard"; /* use when copy and paste attributes */ static NSString *AttributesPboardType = @"MyLibraryAttributesPboardType"; /* use when copy and paste category */ static NSString *AttributesCategoryPboardType = @"MyLibraryAttributesCategoryPboardType"; static AttributesViewer *sharedInstance; @implementation AttributesViewer /* Not duplicated title even in different category */ - (BOOL) _isDuplicatedTitle: (NSString *) t { Node *category, *attribute; unsigned int i, icount, j, jcount; icount = [[root children] count]; for(i = 0; i < icount; i++) { category = [[root children] objectAtIndex: i]; jcount = [[category children] count]; for(j = 0; j < jcount; j++) { attribute = [[category children] objectAtIndex: j]; if ([t isEqualToString: [attribute name]]) return YES; } } return NO; } - (NSString *) _stringFromFloatNumber: (NSNumber *) number { unsigned int index = [numberPopUpButton indexOfSelectedItem]; NSString *description = [number description]; if (index == 1) { NSRange range = [description rangeOfString: @"."]; if (range.location != NSNotFound) { description = [NSString stringWithFormat: @"%@,%@", [description substringToIndex: range.location], [description substringFromIndex: range.location+1]]; } } return description; } - (void) _addNewNodeWithKey: (NSString *) key value: (id) value { unsigned int acc = 0; int order; NSString *category, *name, *type; Node *categoryNode, *nameNode; order = orderOfAttributeKey(key); category = categoryOfAttributeKey(key); name = nameOfAttributeKey(key); type = typeOfAttributeKey(key); categoryNode = [root childWithName: category]; NSString *proposed = name; while(1) { acc++; if ([self _isDuplicatedTitle: proposed]) { proposed = [name stringByAppendingFormat: @" %d", acc]; } else break; } if (categoryNode == nil) { categoryNode = [[Node alloc] init]; [categoryNode setName: category]; [root addChild: categoryNode]; RELEASE(categoryNode); } nameNode = [categoryNode childWithName: proposed]; if (nameNode == nil) { nameNode = [[Node alloc] init]; [nameNode setCategory: category]; [nameNode setName: proposed]; [nameNode setValue: value]; [nameNode setOrder: order]; [nameNode setType: type]; [categoryNode addChild: nameNode]; RELEASE(nameNode); } categoryNode = nil; nameNode = nil; } /* accessories */ #if 0 - (void) setTitleOfViewer: (NSString *) string { NSString *t = [NSString stringWithFormat: sAttributesViewer_, string]; [viewer setTitle: t]; } #endif //- (void) setTitleOfAttributes: (NSString *) string - (void) setUniqueNumber: (unsigned int) u { CREATE_AUTORELEASE_POOL(pool); unsigned int index; unsigned int i, count; id key, value; LibrarySource *library = [LibrarySource sharedSource]; AttributesSource *source= [AttributesSource sharedSource]; //ASSIGN(title, AUTORELEASE([string copy])); uid = u; NSLog(@"uid %d", u); index = [library indexOfUniqueNumber: uid]; title = [library pathOfItemAtIndex: index]; NSString *t = [NSString stringWithFormat: sAttributesViewer_, [library titleAtIndex: index]]; [viewer setTitle: t]; index = [source indexOfUniqueNumber: uid]; NSDictionary *attr = [source attributesAtIndex: index]; if (attr == nil) [attributes removeAllObjects]; else [attributes setDictionary: attr]; //NSLog(@"attributes %@", attributes); [allNamesOfAttributes removeAllObjects]; [allNamesOfAttributes setSet: [source namesOfAttributes]]; /* build node tree */ NSEnumerator *e = [[attributes allKeys] objectEnumerator]; [[root children] removeAllObjects]; while ((key = [e nextObject])) { value = [attributes objectForKey: key]; [self _addNewNodeWithKey: key value: value]; } /* Sort each children*/ [root sortChildren]; count = [[root children] count]; for(i = 0; i < count; i++) { [[[root children] objectAtIndex: i] sortChildren]; } [outlineView reloadData]; if ([[root children] count] > 0) [outlineView expandItem: [[root children] objectAtIndex: 0]]; DESTROY(pool); } - (NSDictionary *) attributes { return AUTORELEASE([attributes copy]);; } /* Deal editing */ - (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor { //NSLog(@"%@ begins editing %@ at row %d column %d", control, [fieldEditor string], [control editedRow], [control editedColumn]); editedStringLength = 0; return YES; } - (BOOL) control: (NSControl *) control textShouldEndEditing: (NSText *) fieldEditor { // NSLog(@"%@ ends editing %@", control, [fieldEditor string]); editedStringLength = 0; return YES; } - (void) controlTextDidChange: (NSNotification *) not { /* Only work for attribute name, not attribute value */ if ([not object] != outlineView) return; unsigned int row = [outlineView editedRow]; Node *item = [outlineView itemAtRow: row]; if ([item category] == nil) return; if ([outlineView editedColumn] != 0) return; NSText *fieldEditor = [[not userInfo] objectForKey: @"NSFieldEditor"]; NSString *original = [fieldEditor string]; unsigned int length = [original length]; NSString *append; NSRange selected; NSArray *names = [allNamesOfAttributes allObjects]; /* Search for the best fit */ if (editedStringLength < [original length]) { unsigned int i, count = [names count]; for (i = 0; i < count; i++) { if ([[names objectAtIndex: i] hasPrefix: original]) { append = [names objectAtIndex: i]; selected = NSMakeRange([original length], [append length]); [fieldEditor setString: append]; [fieldEditor setSelectedRange: selected]; break; } } } editedStringLength = length; } /* outline view delegate */ - (id) outlineView: (NSOutlineView *) outlineView child: (int) index ofItem: (id) item { if (item == nil) /* Root */ return [[root children] objectAtIndex: index]; /* Others */ if ([[item children] count]) return [[item children] objectAtIndex: index]; else return nil; } - (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item { if (item == nil) /* root */ { if ([[root children] count]) return YES; else return NO; } /* others */ if ([[item children] count]) return YES; else return NO; } - (int) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item { if (item == nil) /* root */ return [[root children] count]; /* others */ return [[item children] count]; } - (id) outlineView: (NSOutlineView *) view objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item { NSString *identifier = [tableColumn identifier]; if ([identifier isEqualToString: AttributeNameIdentifier]) { /* Translate file information */ if ([[(Node *)item name] isEqualToString: FileInfoCategory]) return sFileInfo; else if ([[(Node *)item name] isEqualToString: FileInfoCreationDate]) return sCreationDate; else if ([[(Node *)item name] isEqualToString: FileInfoModificationDate]) return sModificationDate; else if ([[(Node *)item name] isEqualToString: FileInfoSize]) return sFileSize; else if ([[(Node *)item name] isEqualToString: FileInfoOwner]) return sOwner; else return [(Node *)item name]; } else if ([identifier isEqualToString: AttributeValueIdentifier]) { if ([[(Node *)item type] isEqualToString: AttributeFloatType]) { return [self _stringFromFloatNumber: (NSNumber *)[item value]]; } else return [(Node *)item value]; } } - (void) outlineView: (NSOutlineView *) view setObjectValue: (id) object forTableColumn: (NSTableColumn *) column byItem: (id) item { if (object == nil) return; if ([[column identifier] isEqualToString: AttributeNameIdentifier]) { /* No change */ if ([object isEqualToString: [(Node *)item name]]) return; if ([self _isDuplicatedTitle: object]) return; if ([object hasPrefix: @"-"]) return; [(Node *)item setName: object]; if ([item category] == nil) /* Category */ { /* update all children */ NSArray *nodes = [item children]; unsigned int i, count = [nodes count]; for(i = 0; i < count; i++) { [[nodes objectAtIndex: i] setCategory: object]; } [root sortChildren]; [outlineView reloadData]; } } else { int index; NSString *testString = [object stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; unsigned dateIndex = [datePopUpButton indexOfSelectedItem]; unsigned numberIndex = [numberPopUpButton indexOfSelectedItem]; RegExFloatNumber numberType = RegExDotFloatNumber; RegExDate dateType = RegExMMDDYYYYDate; RegExType type; id returnObject; if (dateIndex == 1) dateType = RegExDDMMYYYYDate; else if (dateIndex == 2) dateType = RegExYYYYMMDDDate; if (numberIndex == 1) numberType = RegExCommaFloatNumber; NSLog(@"object %@, number %d, date %d", object, numberType, dateType); returnObject = objectFromRegExParsedString(object, numberType, dateType, &type); RETAIN(returnObject); NSLog(@"Type %d, return %@", type, returnObject); if (type == RegExCalendarDateType) [returnObject setCalendarFormat: CalendarDateDisplayFormat]; [(Node *)item setValue: returnObject]; RELEASE(returnObject); switch(type) { case RegExStringType: [(Node *)item setType: AttributeStringType]; break; case RegExCalendarDateType: [(Node *)item setType: AttributeCalendarDateType]; break; case RegExFloatNumberType: [(Node *)item setType: AttributeFloatType]; break; case RegExIntegerType: [(Node *)item setType: AttributeIntegerType]; break; } } return; } - (void) outlineView: (NSTableView *) view didClickIconOfItem: (Node *) item { unsigned int index; if ([item category] == nil) /* category */ { if ([[item name] isEqualToString: FileInfoCategory]) { /* Refresh file information */ //NSString *path = [[[LibraryLocation stringByStandardizingPath] stringByAppendingPathComponent: LibraryPath] stringByAppendingPathComponent: title]; NSString *path = [MyLibraryPath(nil) stringByAppendingPathComponent: title]; //NSLog(@"path %@", path); NSDictionary *dict = attributesFromFile(path); //NSLog(@"new %@", dict); NSArray *allKeys = [dict allKeys]; unsigned int i, count = [allKeys count]; id key, value; [[item children] removeAllObjects]; for(i = 0; i < count; i++) { key = [allKeys objectAtIndex: i]; value = [dict objectForKey: key]; [self _addNewNodeWithKey: key value: value]; } [item sortChildren]; } else { index = [[root children] indexOfObject: item]; //NSLog(@"Remove category %@", [[[root children] objectAtIndex: index] name]); [root removeChildAtIndex: index]; } } else /* attribute */ { Node *cat = [root childWithName: [item category]]; index = [[cat children] indexOfObject: item]; //NSLog(@"Remove category %@", [[[cat children] objectAtIndex: index] name]); [cat removeChildAtIndex: index]; [cat resetOrderOfChildren]; } [view reloadData]; } - (BOOL) outlineView: (NSOutlineView *) view shouldEditTableColumn: (NSTableColumn *) column item: (id) item { if ([item category] == nil) /* Category */ { if ([[(Node *)item name] isEqualToString: FileInfoCategory]) return NO; if ([[column identifier] isEqualToString: AttributeValueIdentifier]) return NO; } else if ([[item category] isEqualToString: FileInfoCategory]) { return NO; } else if ([[column identifier] isEqualToString: AttributeValueIdentifier]) { /* convert value into NSString. * After editing or cancelEditing, * setObjectValue will be called * and it will be converted into proper class */ if ([[(Node *)item type] isEqualToString: AttributeCalendarDateType]) { /* change format before editiong */ unsigned int index = [datePopUpButton indexOfSelectedItem]; NSString *format; if (index == 1) format = CalendarDateDDMMYYYYInputFormat; else if (index == 2) format = CalendarDateYYYYMMDDInputFormat; else format = CalendarDateMMDDYYYYInputFormat; NSCalendarDate *date = [[(Node *)item value] dateWithCalendarFormat: format timeZone: nil];; [(Node *)item setValue: [date description]]; } else if ([[(Node *)item type] isEqualToString: AttributeFloatType]) { /* change format before editiong */ unsigned int index = [numberPopUpButton indexOfSelectedItem]; id value = [(Node *)item value]; if ([value isKindOfClass: [NSNumber class]]) [(Node *)item setValue: [self _stringFromFloatNumber: value]]; } else if ([[(Node *)item type] isEqualToString: AttributeIntegerType]) { id value = [(Node *)item value]; [(Node *)item setValue: [value description]]; } } return YES; } - (NSImage *) outlineView: (NSOutlineView *) view iconForItem: (Node *) item { if ([item category] == nil) { if ([[item name] isEqualToString: FileInfoCategory]) { return [NSImage imageNamed: @"refresh12.png"]; } return [NSImage imageNamed: @"remove12.png"]; } else if ([[item category] isEqualToString: FileInfoCategory]) { return nil; } else { return [NSImage imageNamed: @"remove12.png"]; } } - (BOOL) outlineView: (NSOutlineView *) view writeItems: (NSArray *) items toPasteboard: (NSPasteboard *) pboard { /* Only allow one selection for drag-and-drop */ //NSLog(@"%@ write %@ to %@", view, items, pboard); if ([items count] > 1) return NO; BOOL success; NSMutableArray *array = [[NSMutableArray alloc] init]; Node *item = [items objectAtIndex: 0];; #if 0 /* Category is sorted by alphabet */ if ([item category] == nil) { /* Category */ [pboard declareTypes: [NSArray arrayWithObject: AttributesCategoryPboardType] owner: nil]; success = [pboard setString: [item name] forType: AttributesCategoryPboardType]; } else #endif { /* Attribute */ [pboard declareTypes: [NSArray arrayWithObject: AttributesPboardType] owner: nil]; success = [pboard setString: [item name] forType: AttributesPboardType]; } return success; } - (NSDragOperation) outlineView: (NSOutlineView *) view validateDrop: (id ) info proposedItem: (id) item proposedChildIndex: (int) index { if (outlineView != [info draggingSource]) { return NSDragOperationNone; } NSPasteboard *pboard = [info draggingPasteboard]; if ([[pboard types] containsObject: AttributesCategoryPboardType]) { #if 0 /* Category is sorted by alphabet */ /* Category */ if (item) return NSDragOperationNone; if (index == NSOutlineViewDropOnItemIndex) return NSDragOperationNone; return NSDragOperationMove; #endif return NSDragOperationNone; } else { /* item */ if (item == root) return NSDragOperationNone; if (index == NSOutlineViewDropOnItemIndex) return NSDragOperationNone; if ([[(Node *)item name] isEqualToString: FileInfoCategory]) return NSDragOperationNone; //NSLog(@"drop on %@ at %d", [(Node *)item name], index); return NSDragOperationMove; } } - (BOOL) outlineView: (NSOutlineView *) view acceptDrop: (id ) info item: (id) item childIndex: (int) index { if (outlineView != [info draggingSource]) { return NSDragOperationNone; } NSPasteboard *pboard = [info draggingPasteboard]; NSString *name; Node *node; unsigned int original; if ([[pboard types] containsObject: AttributesCategoryPboardType]) { #if 0 /* Category is sorted by alphabet */ /* Category */ if (item) return NO; if (index == NSOutlineViewDropOnItemIndex) return NO; name = [pboard stringForType: AttributesCategoryPboardType]; node = [root childWithName: name]; original = [[root children] indexOfObject: node]; if (original < index) { [[root children] insertObject: node atIndex: index]; [root removeChildAtIndex: original]; } else if (original > index) { [[root children] insertObject: node atIndex: index]; [root removeChildAtIndex: original+1]; } [outlineView reloadData]; return YES; #endif } else { /* Item */ name = [pboard stringForType: AttributesPboardType]; NSString *newCategoryName = [(Node *)item name]; Node *newCategory = item; /* Find the old category */ Node *oldCategory; unsigned int i, count = [[root children] count]; unsigned int original; for(i = 0; i < count; i++) { oldCategory = [[root children] objectAtIndex: i]; node = [oldCategory childWithName: name]; if (node != nil) { break; } } if (node == nil) return NO; if (oldCategory == newCategory) { original = [[oldCategory children] indexOfObject: node]; if (original < index) { [[oldCategory children] insertObject: node atIndex: index]; [oldCategory removeChildAtIndex: original]; } else if (original > index) { [[oldCategory children] insertObject: node atIndex: index]; [oldCategory removeChildAtIndex: original+1]; } [oldCategory resetOrderOfChildren]; } else { if ([newCategory nameExistsInChildren: name]) return NO; if ([[oldCategory name] isEqualToString: FileInfoCategory]) { id key = [node keyOfItem]; id value = [node value]; [self _addNewNodeWithKey: keyOfAttributes(index, newCategoryName, nameOfAttributeKey(key), typeOfAttributeKey(key)) value: value]; } else { [node setCategory: newCategoryName]; [[newCategory children] insertObject: node atIndex: index]; [[oldCategory children] removeObject: node]; } [newCategory resetOrderOfChildren]; } [outlineView reloadData]; return YES; } return NO; } /* window delegate */ - (BOOL) windowShouldClose: (id) sender { [NSApp stopModal]; return YES; } /* actions */ - (void) okAction: (id) sender { /* Generate Attributes */ [attributes removeAllObjects]; id key, value, object; NSEnumerator *e; unsigned int i, count = [[root children] count]; for(i = 0; i < count; i++) { e = [[[[root children] objectAtIndex: i] children] objectEnumerator]; while((object = [e nextObject])) { key = [object keyOfItem]; value = [object value]; [attributes setObject: value forKey: key]; } } //NSLog(@"%@", [attributes description]); AttributesSource *source = [AttributesSource sharedSource]; unsigned int index; index = [source indexOfUniqueNumber: uid]; if (index == NSNotFound) { index = [source newTitle: title]; [source setUniqueNumber: uid atIndex: index]; } [source setAttributes: attributes atIndex: index]; [viewer performClose: sender]; } - (void) cancelAction: (id) sender { [viewer performClose: sender]; } - (void) newCategoryAction: (id) sender { unsigned int acc = 0; NSString *category = @"Customized Attributes"; NSString *proposed = category; while(1) { if ([root nameExistsInChildren: proposed] == NO) break; proposed = [NSString stringWithFormat: @"%@ %d", category, acc]; acc++; } Node *new = [[Node alloc] init]; [new setName: proposed]; [root addChild: new]; RELEASE(new); [outlineView reloadData]; unsigned int row; row = [outlineView rowForItem: new]; [outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO]; } - (void) newAttributeAction: (id) sender { unsigned int acc = 0; int selectedRow; NSString *attr = @"New Attribute"; NSString *proposed = attr; Node *category, *node; if ([[root children] count] == 0) { [self newCategoryAction: sender]; category = [[root children] lastObject]; } selectedRow = [outlineView selectedRow]; if ((selectedRow == NSNotFound) || (selectedRow < 0)) { [outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0] byExtendingSelection: NO]; selectedRow = [outlineView selectedRow]; } node = [outlineView itemAtRow: selectedRow]; if ([node category] == nil) category = node; else category = [root childWithName: [node category]]; if ([[category name] isEqualToString: FileInfoCategory]) { category = [[root children] lastObject]; if ([[category name] isEqualToString: FileInfoCategory]) { [self newCategoryAction: sender]; category = [[root children] lastObject]; } } while(1) { // if ([category nameExistsInChildren: proposed] == NO) if ([self _isDuplicatedTitle: proposed] == NO) break; proposed = [NSString stringWithFormat: @"%@ %d", attr, acc]; acc++; } Node *new = [[Node alloc] init]; [new setCategory: [category name]]; [new setName: proposed]; [new setValue: @"New Value"]; [new setOrder: [[category children] count]]; [new setType: AttributeStringType]; [category addChild: new]; [outlineView reloadData]; [outlineView expandItem: category]; } - (void) popupButtonWillPop: (NSNotification *) not { #ifndef GNUSTEP [outlineView cancelOperation: self]; #endif } - (void) numberPopUpButtonAction: (id) sender { /* reload data in order to display new format */ [outlineView reloadData]; } - (void) datePopUpButtonAction: (id) sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *format; unsigned int index = [sender indexOfSelectedItem]; switch(index) { case 1: format = CalendarDateDDMMYYYYInputFormat; break; case 2: format = CalendarDateYYYYMMDDInputFormat; break; default: format = CalendarDateMMDDYYYYInputFormat; } [defaults setObject: format forKey: CalendarInputFormatDefault]; } - (void) cut: (id) sender { // NSLog(@"%@ cut", sender); } - (void) copy: (id) sender { NSPasteboard *pboard = [NSPasteboard pasteboardWithName: AttributesPboard]; [pboard declareTypes: [NSArray arrayWithObjects: AttributesCategoryPboardType, AttributesPboardType, nil] owner: nil]; NSIndexSet *indexSet = [outlineView selectedRowIndexes]; unsigned int maxCount = [indexSet count]; unsigned int *buffer = malloc(sizeof(unsigned int) * maxCount); #ifdef GNUSTEP unsigned int c = [outlineView numberOfRowsInTableView: outlineView]; NSLog(@"c %d", c); NSRange r = NSMakeRange(0, c); unsigned int result = [indexSet getIndexes: buffer maxCount: maxCount inIndexRange: &r]; #else unsigned int result = [indexSet getIndexes: buffer maxCount: maxCount inIndexRange: NULL]; #endif if (result == 0) return; unsigned int i, index; unsigned int j, jcount; id key, value; Node *item; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSMutableDictionary *cat = [[NSMutableDictionary alloc] init]; for(i = 0; i < result; i++) { index = *(buffer+i); item = [outlineView itemAtRow: index]; if ([item category] == nil) { if (([outlineView isItemExpanded: item] == NO) || (([outlineView isItemExpanded: item] == YES) && (result == 1)) ) { /* Category collapsed. Copy all. */ /* Special case: * if only one selected and it's category * copy all item under this category */ Node *node; jcount = [[item children] count]; for(j = 0; j < jcount; j++) { node = [[item children] objectAtIndex: j]; key = [node keyOfItem]; value = [node value]; [cat setObject: value forKey: key]; } } else { /* Ignore expanded item */ continue; } } else /* Copy item */ { key = [item keyOfItem]; value = [item value]; [dict setObject: value forKey: key]; } } //NSLog(@"copy %@", dict); //NSLog(@"copy %@", cat); [pboard setPropertyList: dict forType: AttributesPboardType]; [pboard setPropertyList: cat forType: AttributesCategoryPboardType]; AUTORELEASE(dict); AUTORELEASE(cat); } - (void) paste: (id) sender { NSPasteboard *pboard = [NSPasteboard pasteboardWithName: AttributesPboard]; NSDictionary *dict, *cat; if (([[pboard types] containsObject: AttributesCategoryPboardType] == NO) || ([[pboard types] containsObject: AttributesPboardType] == NO)) { return; } else { dict = [pboard propertyListForType: AttributesPboardType]; cat = [pboard propertyListForType: AttributesCategoryPboardType]; } NSArray *allKeys; unsigned int i, count; unsigned int selectedRow = [outlineView selectedRow]; Node *item = [outlineView itemAtRow: selectedRow]; Node *category; NSString *newCategory; id value; NSString *key, *newKey; if ([dict count]) { /* Substitue categoriy */ if (item == nil) /* No category */ { [self newCategoryAction: sender]; item = [[root children] lastObject]; } if ([item category] == nil) /* Category */ { newCategory = [item name]; category = item; } else { newCategory = [item category]; category = [root childWithName: newCategory]; } /* Avoid add attributes into file information */ if ([newCategory isEqualToString: FileInfoCategory]) { [self newCategoryAction: sender]; category = [[root children] lastObject]; newCategory = [category name]; } allKeys = [dict allKeys]; count = [allKeys count]; for(i = 0; i < count; i++) { key = [allKeys objectAtIndex: i]; value = [dict objectForKey: key]; /* build new key when category changed */ newKey = keyOfAttributes(i, newCategory, nameOfAttributeKey(key), typeOfAttributeKey(key)); [self _addNewNodeWithKey: newKey value: value]; } [category resetOrderOfChildren]; [outlineView reloadData]; [outlineView expandItem: category]; } if ([cat count]) { /* Without substitute category */ allKeys = [cat allKeys]; count = [allKeys count]; for(i = 0; i < count; i++) { key = [allKeys objectAtIndex: i]; value = [cat objectForKey: key]; [self _addNewNodeWithKey: key value: value]; category = [root childWithName: categoryOfAttributeKey(key)]; [outlineView reloadData]; [outlineView expandItem: category]; } } } /* basic methods */ + (AttributesViewer *) sharedViewer { if (sharedInstance == nil) { sharedInstance = [[AttributesViewer alloc] init]; } return sharedInstance; } - (void) awakeFromNib { //NSLog(@"AttributesViewer awakeFromNib"); /* Make sure PopUpButton is selected */ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *separator = [defaults stringForKey: NSDecimalSeparator]; if ([separator isEqualToString: @","]) [numberPopUpButton selectItemAtIndex: 1]; else [numberPopUpButton selectItemAtIndex: 0]; NSString *format = [defaults stringForKey: CalendarInputFormatDefault]; if ([format isEqualToString: CalendarDateDDMMYYYYInputFormat]) [datePopUpButton selectItemAtIndex: 1]; else if ([format isEqualToString: CalendarDateYYYYMMDDInputFormat]) [datePopUpButton selectItemAtIndex: 2]; else [datePopUpButton selectItemAtIndex: 0]; [outlineView registerForDraggedTypes: [NSArray arrayWithObjects: AttributesCategoryPboardType, AttributesPboardType, nil]]; } - (void) makeViewerKeyAndOrderFront: (id) sender { [viewer makeKeyAndOrderFront: sender]; [NSApp runModalForWindow: viewer]; [outlineView reloadData]; if ([[root children] count] > 0) [outlineView expandItem: [[root children] objectAtIndex: 0]]; //[outlineView setAutoresizesAllColumnsToFit: YES]; } - (id) init { self = [super init]; BOOL result = [NSBundle loadNibNamed: @"AttributesViewer" owner: self]; if (result == NO) { NSLog(@"Unable to load AttributesViewer interface"); return nil; } attributes = [[NSMutableDictionary alloc] init]; root = [[Node alloc] init]; allNamesOfAttributes = [[NSMutableSet alloc] init]; editedStringLength = 0; /* Listen to NSPopUpButtonWillPopUpNotification */ [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(popupButtonWillPop:) name: NSPopUpButtonWillPopUpNotification object: nil]; return self; } - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver: self]; RELEASE(title); RELEASE(attributes); RELEASE(allNamesOfAttributes); RELEASE(root); [super dealloc]; } @end