/* MDKWindow.m * * Copyright (C) 2006 Free Software Foundation, Inc. * * Author: Enrico Sersale * Date: December 2006 * * This file is part of the GNUstep GWorkspace application * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #include #include "MDKWindow.h" #include "MDKTableView.h" #include "MDKAttribute.h" #include "MDKAttributeView.h" #include "MDKAttributeEditor.h" #include "MDKFSFilter.h" #include "MDKAttributeChooser.h" #include "MDKQuery.h" #include "MDKResultsCategory.h" #include "DBKPathsTree.h" #include "FSNodeRep.h" #include "FSNPathComponentsViewer.h" #include "MDKResultCell.h" #define CHECKDELEGATE(s) \ (delegate && [delegate respondsToSelector: @selector(s)]) #define WORD_MAX 40 #define WORD_MIN 3 #define CELLS_HEIGHT (28.0) #define ICNSIZE 24 #define OPEN_MAX 10 BOOL isDotFile(NSString *path); NSString *pathSeparator(void); typedef BOOL (*boolIMP)(id, SEL, Class); static SEL memberSel = NULL; static boolIMP isMember = NULL; static Class FSNodeClass = Nil; static NSString *nibName = @"MDKWindow"; @implementation MDKWindow + (void)initialize { static BOOL initialized = NO; if (initialized == NO) { FSNodeClass = [FSNode class]; memberSel = @selector(isMemberOfClass:); isMember = (boolIMP)[FSNodeClass instanceMethodForSelector: memberSel]; initialized = YES; } } - (void)dealloc { [dnc removeObserver: self]; DESTROY (win); DESTROY (attributes); DESTROY (attrViews); DESTROY (chooser); DESTROY (onImage); if (includePathsTree != NULL) { freeTree(includePathsTree); freeTree(excludedPathsTree); } DESTROY (excludedSuffixes); DESTROY (queryEditors); DESTROY (searchPaths); DESTROY (textContentEditor); DESTROY (currentQuery); DESTROY (categoryNames); DESTROY (resultCategories); DESTROY (savepath); [super dealloc]; } - (id)initWithContentsOfFile:(NSString *)path windowRect:(NSRect)wrect delegate:(id)adelegate { self = [super init]; if (self) { NSDictionary *info = nil; if (path) { info = [self savedInfoAtPath: path]; if (info == nil) { DESTROY (self); return self; } } if ([NSBundle loadNibNamed: nibName owner: self] == NO) { NSLog(@"failed to load %@!", nibName); DESTROY (self); return self; } delegate = adelegate; if (info) { NSString *str = [info objectForKey: @"window_frame"]; if (str) { [win setFrame: NSRectFromString([info objectForKey: @"window_frame"]) display: NO]; } else { [win setFrameUsingName: @"mdkwindow"]; } } else { if (NSEqualRects(wrect, NSZeroRect) == NO) { [win setFrame: wrect display: NO]; } else { [win setFrameUsingName: @"mdkwindow"]; } } if (path) { [self setSavePath: path]; } else { [win setTitle: NSLocalizedString(@"Untitled", @"")]; } fm = [NSFileManager defaultManager]; nc = [NSNotificationCenter defaultCenter]; dnc = [NSDistributedNotificationCenter defaultCenter]; fsnodeRep = [FSNodeRep sharedInstance]; loadingAttributes = YES; [self prepareInterface]; [self prepareQueries: info]; [self prepareResults]; [self loadAttributes: info]; loadingAttributes = NO; includePathsTree = newTreeWithIdentifier(@"included"); excludedPathsTree = newTreeWithIdentifier(@"excluded"); excludedSuffixes = [[NSMutableSet alloc] initWithCapacity: 1]; [self setSearcheablePaths]; [dnc addObserver: self selector: @selector(searcheablePathsDidChange:) name: @"GSMetadataIndexedDirectoriesChanged" object: nil]; chooser = nil; closing = NO; [self setSaved: YES]; if (info) { NSNumber *num = [info objectForKey: @"attributes_visible"]; if (num) { [attributesButt setState: [num intValue]]; [self attributesButtAction: attributesButt]; } } [self startSearchButtAction: startSearchButt]; } return self; } - (NSDictionary *)savedInfoAtPath:(NSString *)path { NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: path]; id entry; #define CHECK_ENTRY(e, c) do { \ if (dict) { \ entry = [dict objectForKey: e]; \ if ((entry == nil) || ([entry isKindOfClass: c] == NO)) dict = nil; \ } \ } while (0) if (dict) { CHECK_ENTRY (@"editors", [NSArray class]); CHECK_ENTRY (@"text_content_words", [NSArray class]); CHECK_ENTRY (@"window_frame", [NSString class]); CHECK_ENTRY (@"search_places", [NSArray class]); } return dict; } - (void)loadAttributes:(NSDictionary *)info { unsigned mask = MDKAttributeSearchable | MDKAttributeUserSet; NSDictionary *attrdict = [MDKQuery attributesWithMask: mask]; NSArray *attrnames = [attrdict allKeys]; MDKAttribute *attribute; MDKAttributeView *attrview; BOOL addenabled; int i; attributes = [NSMutableArray new]; attrViews = [NSMutableArray new]; attrnames = [attrnames sortedArrayUsingSelector: @selector(compare:)]; for (i = 0; i < [attrnames count]; i++) { NSDictionary *attrinfo = [attrdict objectForKey: [attrnames objectAtIndex: i]]; attribute = [[MDKAttribute alloc] initWithAttributeInfo: attrinfo forWindow: self]; [attributes addObject: attribute]; RELEASE (attribute); } if (info) { NSArray *editorsInfo = [info objectForKey: @"editors"]; NSArray *words = [info objectForKey: @"text_content_words"]; if (words && [words count]) { [textContentEditor setTextContentWords: words]; } if (editorsInfo && [editorsInfo count]) { for (i = 0; i < [editorsInfo count]; i++) { NSDictionary *edinfo = [editorsInfo objectAtIndex: i]; NSString *attrname = [edinfo objectForKey: @"attrname"]; MDKAttributeEditor *editor; attribute = [self attributeWithName: attrname]; [attribute setInUse: YES]; attrview = [[MDKAttributeView alloc] initInWindow: self]; [attrview setAttribute: attribute]; [[attrBox contentView] addSubview: [attrview mainBox]]; [attrViews addObject: attrview]; RELEASE (attrview); editor = [attribute editor]; [editor restoreSavedState: edinfo]; [queryEditors addObject: editor]; } } else { attribute = nil; } } else { attribute = nil; } if (attribute == nil) { attribute = [self attributeWithName: @"GSMDItemFSName"]; [attribute setInUse: YES]; attrview = [[MDKAttributeView alloc] initInWindow: self]; [attrview setAttribute: attribute]; [[attrBox contentView] addSubview: [attrview mainBox]]; [attrViews addObject: attrview]; RELEASE (attrview); } if ([[self usedAttributes] count] == [attributes count]) { for (i = 0; i < [attrViews count]; i++) { [[attrViews objectAtIndex: i] setAddEnabled: NO]; } } addenabled = ([[self usedAttributes] count] < [attributes count]); for (i = 0; i < [attrViews count]; i++) { attrview = [attrViews objectAtIndex: i]; [attrview setAddEnabled: addenabled]; [attrview updateMenuForAttributes: attributes]; } } - (void)prepareInterface { NSBundle *bundle = [NSBundle bundleForClass: [self class]]; NSString *impath; NSImage *image; NSString *ttstr; NSRect r; onImage = [NSImage imageNamed: @"common_2DCheckMark"]; RETAIN (onImage); ttstr = NSLocalizedString(@"Restrict the search to choosen places.", @""); [placesPopUp setTitle: NSLocalizedString(@"Search in...", @"")]; [placesPopUp setToolTip: ttstr]; impath = [bundle pathForResource: @"switchOff" ofType: @"tiff"]; image = [[NSImage alloc] initWithContentsOfFile: impath]; [caseSensButt setImage: image]; RELEASE (image); impath = [bundle pathForResource: @"switchOn" ofType: @"tiff"]; image = [[NSImage alloc] initWithContentsOfFile: impath]; [caseSensButt setAlternateImage: image]; RELEASE (image); [caseSensButt setState: NSOnState]; [caseSensButt setToolTip: NSLocalizedString(@"Case sensitive switch", @"")]; ttstr = NSLocalizedString(@"Saves the query as a Live Search Folder.", @""); [saveButt setTitle: NSLocalizedString(@"Save", @"")]; [saveButt setToolTip: ttstr]; ttstr = NSLocalizedString(@"Type terms to search into the text contents.", @""); [searchField setToolTip: ttstr]; ttstr = NSLocalizedString(@"Starts a search if no term has been\nentered into the search field.", @""); [startSearchButt setToolTip: ttstr]; ttstr = NSLocalizedString(@"Stops a running query.", @""); [stopSearchButt setToolTip: ttstr]; ttstr = NSLocalizedString(@"Show a list of attributes to search.", @""); [attributesButt setToolTip: ttstr]; [elementsLabel setStringValue: NSLocalizedString(@"0 elements", @"")]; [resultsScroll setBorderType: NSBezelBorder]; [resultsScroll setHasHorizontalScroller: NO]; [resultsScroll setHasVerticalScroller: YES]; r = [[resultsScroll contentView] bounds]; resultsView = [[MDKTableView alloc] initWithFrame: r]; [resultsView setDrawsGrid: NO]; [resultsView setHeaderView: nil]; [resultsView setCornerView: nil]; [resultsView setAllowsColumnSelection: NO]; [resultsView setAllowsColumnReordering: NO]; [resultsView setAllowsColumnResizing: NO]; [resultsView setAllowsEmptySelection: YES]; [resultsView setAllowsMultipleSelection: YES]; [resultsView setRowHeight: CELLS_HEIGHT]; [resultsView setIntercellSpacing: NSZeroSize]; [resultsView setAutoresizesAllColumnsToFit: YES]; nameColumn = [[NSTableColumn alloc] initWithIdentifier: @"name"]; [nameColumn setDataCell: AUTORELEASE ([[MDKResultCell alloc] init])]; [nameColumn setEditable: NO]; [nameColumn setResizable: YES]; [resultsView addTableColumn: nameColumn]; RELEASE (nameColumn); attrColumn = [[NSTableColumn alloc] initWithIdentifier: @"attribute"]; [attrColumn setDataCell: AUTORELEASE ([[MDKResultCell alloc] init])]; [attrColumn setEditable: NO]; [attrColumn setResizable: NO]; [attrColumn setWidth: 120]; [resultsView addTableColumn: attrColumn]; RELEASE (attrColumn); [resultsScroll setDocumentView: resultsView]; RELEASE (resultsView); [resultsView setDataSource: self]; [resultsView setDelegate: self]; [resultsView setTarget: self]; [resultsView setDoubleAction: @selector(doubleClickOnResultsView:)]; r = [[pathBox contentView] bounds]; pathViewer = [[FSNPathComponentsViewer alloc] initWithFrame: r]; [pathBox setContentView: pathViewer]; RELEASE (pathViewer); [self setContextHelp]; } - (void)setSearcheablePaths { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; id entry; unsigned i; [defaults synchronize]; entry = [defaults arrayForKey: @"GSMetadataIndexablePaths"]; if (entry) { for (i = 0; i < [entry count]; i++) { insertComponentsOfPath([entry objectAtIndex: i], includePathsTree); } } entry = [defaults arrayForKey: @"GSMetadataExcludedPaths"]; if (entry) { for (i = 0; i < [entry count]; i++) { insertComponentsOfPath([entry objectAtIndex: i], excludedPathsTree); } } entry = [defaults arrayForKey: @"GSMetadataExcludedSuffixes"]; if (entry == nil) { entry = [NSArray arrayWithObjects: @"a", @"d", @"dylib", @"er1", @"err", @"extinfo", @"frag", @"la", @"log", @"o", @"out", @"part", @"sed", @"so", @"status", @"temp", @"tmp", nil]; } [excludedSuffixes addObjectsFromArray: entry]; } - (void)searcheablePathsDidChange:(NSNotification *)notif { NSDictionary *info = [notif userInfo]; NSArray *included = [info objectForKey: @"GSMetadataIndexablePaths"]; NSArray *excluded = [info objectForKey: @"GSMetadataExcludedPaths"]; NSArray *suffixes = [info objectForKey: @"GSMetadataExcludedSuffixes"]; NSArray *items = [placesPopUp itemArray]; int count = [items count]; unsigned i; emptyTreeWithBase(includePathsTree); for (i = 0; i < [included count]; i++) { insertComponentsOfPath([included objectAtIndex: i], includePathsTree); } emptyTreeWithBase(excludedPathsTree); for (i = 0; i < [excluded count]; i++) { insertComponentsOfPath([excluded objectAtIndex: i], excludedPathsTree); } [excludedSuffixes removeAllObjects]; [excludedSuffixes addObjectsFromArray: suffixes]; for (i = 3; i < count -1; i++) { NSString *path = [[items objectAtIndex: i] representedObject]; NSString *ext = [[path pathExtension] lowercaseString]; if ([excludedSuffixes containsObject: ext] || isDotFile(path) || (inTreeFirstPartOfPath(path, includePathsTree) == NO) || inTreeFirstPartOfPath(path, excludedPathsTree)) { [placesPopUp removeItemAtIndex: i]; items = [placesPopUp itemArray]; count--; i--; } } [[placesPopUp menu] update]; } - (NSArray *)attributes { return attributes; } - (NSArray *)usedAttributes { NSMutableArray *used = [NSMutableArray array]; int i; for (i = 0; i < [attributes count]; i++) { MDKAttribute *attribute = [attributes objectAtIndex: i]; if ([attribute inUse]) { [used addObject: attribute]; } } return used; } - (MDKAttribute *)firstUnusedAttribute { int i; for (i = 0; i < [attributes count]; i++) { MDKAttribute *attribute = [attributes objectAtIndex: i]; if ([attribute inUse] == NO) { return attribute; } } return nil; } - (MDKAttribute *)attributeWithName:(NSString *)name { int i; for (i = 0; i < [attributes count]; i++) { MDKAttribute *attribute = [attributes objectAtIndex: i]; if ([[attribute name] isEqual: name]) { return attribute; } } return nil; } - (MDKAttribute *)attributeWithMenuName:(NSString *)mname { int i; for (i = 0; i < [attributes count]; i++) { MDKAttribute *attribute = [attributes objectAtIndex: i]; if ([[attribute menuName] isEqual: mname]) { return attribute; } } return nil; } - (void)insertAttributeViewAfterView:(MDKAttributeView *)view { NSArray *usedAttributes = [self usedAttributes]; if ([usedAttributes count] < [attributes count]) { int index = [attrViews indexOfObjectIdenticalTo: view]; MDKAttribute *attr = [self firstUnusedAttribute]; MDKAttributeView *attrview = [[MDKAttributeView alloc] initInWindow: self]; int count; int attrcount; int i; [attr setInUse: YES]; [attrview setAttribute: attr]; [[attrBox contentView] addSubview: [attrview mainBox]]; [attrViews insertObject: attrview atIndex: index + 1]; RELEASE (attrview); count = [attrViews count]; attrcount = [attributes count]; for (i = 0; i < count; i++) { attrview = [attrViews objectAtIndex: i]; [attrview updateMenuForAttributes: attributes]; if (count == attrcount) { [attrview setAddEnabled: NO]; } if (count > 1) { [attrview setRemoveEnabled: YES]; } } [self tile]; } } - (void)removeAttributeView:(MDKAttributeView *)view { if ([attrViews count] > 1) { MDKAttribute *attribute = [view attribute]; int count; int i; [attribute setInUse: NO]; [[view mainBox] removeFromSuperview]; [attrViews removeObject: view]; count = [attrViews count]; for (i = 0; i < count; i++) { MDKAttributeView *attrview = [attrViews objectAtIndex: i]; [attrview updateMenuForAttributes: attributes]; [attrview setAddEnabled: YES]; if (count == 1) { [attrview setRemoveEnabled: NO]; } } [self tile]; [self editorStateDidChange: [attribute editor]]; } } - (void)attributeView:(MDKAttributeView *)view changeAttributeTo:(NSString *)menuname { MDKAttribute *attribute = [self attributeWithMenuName: menuname]; MDKAttribute *oldattribute = [view attribute]; if (attribute && (oldattribute != attribute)) { unsigned i; [oldattribute setInUse: NO]; [self editorStateDidChange: [oldattribute editor]]; [attribute setInUse: YES]; [view setAttribute: attribute]; /* notification sent by MDKAttributeView */ for (i = 0; i < [attrViews count]; i++) { [[attrViews objectAtIndex: i] updateMenuForAttributes: attributes]; } } } - (void)activate { [win makeKeyAndOrderFront: nil]; [self tile]; } - (NSDictionary *)statusInfo { NSMutableDictionary *info = [NSMutableDictionary dictionary]; NSMutableArray *editorsInfo = [NSMutableArray array]; NSArray *items = [placesPopUp itemArray]; NSMutableArray *paths = [NSMutableArray array]; int index; int i; for (i = 0; i < [attrViews count]; i++) { MDKAttributeView *attrview = [attrViews objectAtIndex: i]; MDKAttribute *attr = [attrview attribute]; MDKAttributeEditor *editor = [attr editor]; if ([editor hasValidValues]) { [editorsInfo addObject: [editor editorInfo]]; } } [info setObject: editorsInfo forKey: @"editors"]; [info setObject: [textContentEditor textContentWords] forKey: @"text_content_words"]; [info setObject: NSStringFromRect([win frame]) forKey: @"window_frame"]; [info setObject: [NSNumber numberWithInt: [attributesButt state]] forKey: @"attributes_visible"]; /* We must start at 2 because [items objectAtIndex: 0] is the title */ /* of the popup, [items objectAtIndex: 1] is "Computer" */ /* and [items objectAtIndex: 2] is "Home". */ /* The upper limit is [items count] -1 because the last item */ /* is the "Add..." item */ for (i = 3; i < [items count] -1; i++) { [paths addObject: [[items objectAtIndex: i] representedObject]]; } [info setObject: paths forKey: @"search_places"]; index = [placesPopUp indexOfSelectedItem]; if ((index > 0) && (index < [items count] -1)) { [info setObject: [NSNumber numberWithInt: index] forKey: @"selected_search_place"]; } return info; } - (void)setSaved:(BOOL)value { saved = value; [saveButt setEnabled: (saved == NO)]; } - (BOOL)isSaved { return saved; } - (void)setSavePath:(NSString *)path { ASSIGN (savepath, path); [win setTitle: [savepath lastPathComponent]]; } - (NSString *)savePath { return savepath; } #define ATBOXH (30.0) #define ATVIEWH (26.0) #define RESLIMH (70.0) - (void)tile { NSView *view = [win contentView]; NSRect abr = [attributesButt frame]; float ylim = abr.origin.y + abr.size.height; NSRect atr = [attrBox frame]; NSRect elr = [elementsLabel frame]; NSRect rsr = [resultsScroll frame]; if ([attributesButt state] == NSOffState) { atr.origin.y = ylim; atr.size.height = 0; [attrBox setFrame: atr]; } else { unsigned count = [attrViews count]; float hspace = ATBOXH + ((count - 1) * ATVIEWH); float posy; unsigned i; atr.origin.y = ylim - hspace; atr.size.height = hspace; [attrBox setFrame: atr]; posy = [[attrBox contentView] bounds].size.height; for (i = 0; i < count; i++) { MDKAttributeView *attrview = [attrViews objectAtIndex: i]; NSBox *atbox = [attrview mainBox]; NSRect attbr = [atbox frame]; posy -= ATVIEWH; attbr.origin.y = posy; [atbox setFrame: attbr]; } } atr = [attrBox frame]; ylim = (atr.size.height == 0) ? (atr.origin.y - abr.size.height) : atr.origin.y; elr.origin.y = ylim - elr.size.height; [elementsLabel setFrame: elr]; rsr.size.height = elr.origin.y - rsr.origin.y; if (rsr.size.height <= RESLIMH) { NSRect wrect = [win frame]; float inc = RESLIMH - rsr.size.height + ATVIEWH; wrect.size.height += inc; wrect.origin.y -= inc; [win setFrame: wrect display: NO]; /* setting the window frame will cause */ /* a NSWindowDidResizeNotification */ /* so we must return to avoid recursion */ return; } [resultsScroll setFrame: rsr]; [view setNeedsDisplay: YES]; } - (NSWindow *)window { return win; } - (IBAction)placesPopUpdAction:(id)sender { NSArray *items = [sender itemArray]; int count = [items count]; int index = [sender indexOfSelectedItem]; int i; [searchPaths removeAllObjects]; if ((index != 0) && (index != count-1)) { NSMenuItem *item = [sender selectedItem]; NSString *path = [item representedObject]; for (i = 1; i < count -1; i++) { item = [items objectAtIndex: i]; if (i == index) { [item setImage: onImage]; } else { [item setImage: nil]; } } if ([path isEqual: pathSeparator()] == NO) { [searchPaths addObject: path]; } if (loadingAttributes == NO) { [self setSaved: NO]; [self startSearchButtAction: startSearchButt]; } } else if (index == count-1) { NSOpenPanel *openPanel = [NSOpenPanel openPanel]; int result; [openPanel setTitle: NSLocalizedString(@"Choose search place", @"")]; [openPanel setAllowsMultipleSelection: NO]; [openPanel setCanChooseFiles: NO]; [openPanel setCanChooseDirectories: YES]; result = [openPanel runModalForDirectory: nil file: nil types: nil]; if (result == NSOKButton) { NSString *path = [openPanel filename]; NSString *name = [path lastPathComponent]; NSString *ext = [[path pathExtension] lowercaseString]; if (([excludedSuffixes containsObject: ext] == NO) && (isDotFile(path) == NO) && inTreeFirstPartOfPath(path, includePathsTree) && (inTreeFirstPartOfPath(path, excludedPathsTree) == NO)) { BOOL duplicate = NO; for (i = 1; i < [items count] -1; i++) { if ([[[items objectAtIndex: i] representedObject] isEqual: path]) { duplicate = YES; break; } } if (duplicate == NO) { [placesPopUp insertItemWithTitle: name atIndex: count-1]; [[placesPopUp itemAtIndex: count-1] setRepresentedObject: path]; [[placesPopUp menu] update]; } else { NSRunAlertPanel(nil, NSLocalizedString(@"This path is already in the menu!", @""), NSLocalizedString(@"Ok", @""), nil, nil); } } else { NSRunAlertPanel(nil, NSLocalizedString(@"This path is not searchable!", @""), NSLocalizedString(@"Ok", @""), nil, nil); } } } } - (IBAction)startSearchButtAction:(id)sender { [self stopSearchButtAction: nil]; if ([[textContentEditor textContentWords] count] || [queryEditors count]) { [self newQuery]; } } - (IBAction)caseSensButtAction:(id)sender { [self editorStateDidChange: caseSensButt]; } - (IBAction)attributesButtAction:(id)sender { if ([sender state] == NSOnState) { [attributesButt setImage: [NSImage imageNamed: @"common_ArrowDown"]]; } else { [attributesButt setImage: [NSImage imageNamed: @"common_ArrowRight"]]; } [self tile]; } - (IBAction)saveButtAction:(id)sender { if (saved == NO) { if (CHECKDELEGATE (saveQuery:)) { [delegate saveQuery: nil]; } } } - (void)showAttributeChooser:(MDKAttributeView *)sender { MDKAttribute *attr; if (chooser == nil) { chooser = [[MDKAttributeChooser alloc] initForWindow: self]; } attr = [chooser chooseNewAttributeForView: sender]; if (attr) { MDKAttribute *oldattribute = [sender attribute]; unsigned i; [oldattribute setInUse: NO]; [self editorStateDidChange: [oldattribute editor]]; [attr setInUse: YES]; [attributes addObject: attr]; for (i = 0; i < [attrViews count]; i++) { [[attrViews objectAtIndex: i] attributesDidChange: attributes]; } [sender setAttribute: attr]; /* notification sent by MDKAttributeView */ for (i = 0; i < [attrViews count]; i++) { MDKAttributeView *attrview = [attrViews objectAtIndex: i]; [attrview updateMenuForAttributes: attributes]; [attrview setAddEnabled: YES]; } } } - (void)setContextHelp { NSString *bpath = [[NSBundle bundleForClass: [self class]] bundlePath]; NSString *resPath = [bpath stringByAppendingPathComponent: @"Resources"]; NSArray *languages = [NSUserDefaults userLanguages]; unsigned i; for (i = 0; i < [languages count]; i++) { NSString *language = [languages objectAtIndex: i]; NSString *langDir = [NSString stringWithFormat: @"%@.lproj", language]; NSString *helpPath = [langDir stringByAppendingPathComponent: @"Help.rtfd"]; helpPath = [resPath stringByAppendingPathComponent: helpPath]; if ([fm fileExistsAtPath: helpPath]) { NSAttributedString *help = [[NSAttributedString alloc] initWithPath: helpPath documentAttributes: NULL]; if (help) { [[NSHelpManager sharedHelpManager] setContextHelp: help forObject: [win contentView]]; RELEASE (help); } } } } // // NSWindow delegate methods // - (void)windowDidBecomeKey:(NSNotification *)aNotification { if (CHECKDELEGATE (setActiveWindow:)) { [delegate setActiveWindow: self]; } } - (void)windowDidResize:(NSNotification *)notif { if ([notif object] == win) { [self tile]; } } - (BOOL)windowShouldClose:(id)sender { BOOL canclose = YES; if ([currentQuery isGathering] || [currentQuery waitingStart]) { closing = YES; [self stopCurrentQuery]; canclose = NO; } if ((savepath != nil) && (saved == NO)) { canclose = !(NSRunAlertPanel(nil, NSLocalizedString(@"The query is unsaved", @""), NSLocalizedString(@"Cancel", @""), NSLocalizedString(@"Close Anyway", @""), nil)); } return canclose; } - (void)windowWillClose:(NSNotification *)aNotification { if (currentQuery) { [self stopCurrentQuery]; [win saveFrameUsingName: @"mdkwindow"]; if (CHECKDELEGATE (mdkwindowWillClose:)) { [delegate mdkwindowWillClose: self]; } } } @end @implementation MDKWindow (queries) - (void)prepareQueries:(NSDictionary *)info { ASSIGN (currentQuery, [MDKQuery query]); queryEditors = [NSMutableArray new]; textContentEditor = [[MDKTextContentEditor alloc] initWithSearchField: searchField inWindow: self]; rowsCount = 0; globalCount = 0; [dnc addObserver: self selector: @selector(queryCategoriesDidChange:) name: @"MDKQueryCategoriesDidChange" object: nil]; searchPaths = [NSMutableArray new]; while ([[placesPopUp itemArray] count] > 1) { [placesPopUp removeItemAtIndex: 1]; } [placesPopUp addItemWithTitle: NSLocalizedString(@"Computer", @"")]; [[placesPopUp lastItem] setRepresentedObject: pathSeparator()]; [placesPopUp addItemWithTitle: NSLocalizedString(@"Home", @"")]; [[placesPopUp lastItem] setRepresentedObject: NSHomeDirectory()]; if (info) { NSArray *places = [info objectForKey: @"search_places"]; int index = [[info objectForKey: @"selected_search_place"] intValue]; BOOL canselect = YES; int i; for (i = 0; i < [places count]; i++) { NSString *place = [places objectAtIndex: i]; if ([fm fileExistsAtPath: place] && inTreeFirstPartOfPath(place, includePathsTree) && (inTreeFirstPartOfPath(place, excludedPathsTree) == NO)) { NSString *name = [place lastPathComponent]; [placesPopUp addItemWithTitle: name]; [[placesPopUp lastItem] setRepresentedObject: place]; } else { canselect = NO; } } if (canselect) { [placesPopUp selectItemAtIndex: index]; } } else { [placesPopUp selectItemAtIndex: 1]; } [placesPopUp addItemWithTitle: NSLocalizedString(@"Add...", @"")]; [self placesPopUpdAction: placesPopUp]; } - (void)prepareResults { NSDictionary *categoryInfo = [MDKQuery categoryInfo]; int i; ASSIGN (categoryNames, [MDKQuery categoryNames]); DESTROY (resultCategories); resultCategories = [NSMutableDictionary new]; for (i = 0; i < [categoryNames count]; i++) { NSString *catname = [categoryNames objectAtIndex: i]; NSDictionary *catinfo = [categoryInfo objectForKey: catname]; NSString *catmenu = [catinfo objectForKey: @"menu_name"]; MDKResultsCategory *rescat; rescat = [[MDKResultsCategory alloc] initWithCategoryName: catname menuName: catmenu inWindow: self]; [resultCategories setObject: rescat forKey: catname]; RELEASE (rescat); if (i > 0) { NSString *prevname = [categoryNames objectAtIndex: i-1]; MDKResultsCategory *prevcat = [resultCategories objectForKey: prevname]; [rescat setPrev: prevcat]; [prevcat setNext: rescat]; } } catlist = [resultCategories objectForKey: [categoryNames objectAtIndex: 0]]; } - (void)editorStateDidChange:(id)sender { if (loadingAttributes == NO) { BOOL newquery = NO; if (sender == caseSensButt) { if ([[textContentEditor textContentWords] count]) { newquery = YES; } } else if (sender == textContentEditor) { newquery = YES; } else { MDKAttribute *attribute = [sender attribute]; if ([attribute inUse]) { if ([sender hasValidValues]) { if ([queryEditors containsObject: sender] == NO) { [queryEditors addObject: sender]; } newquery = YES; } else { if ([queryEditors containsObject: sender]) { [queryEditors removeObject: sender]; newquery = YES; } } } else { if ([queryEditors containsObject: sender]) { [queryEditors removeObject: sender]; newquery = YES; } } } if (newquery) { [self setSaved: NO]; [self newQuery]; } } } - (void)newQuery { CREATE_AUTORELEASE_POOL(arp); NSArray *words; MDKCompoundOperator operator; BOOL casesens; NSMutableArray *fsfilters; BOOL onlyfilters; int i, j; [currentQuery setUpdatesEnabled: NO]; [currentQuery stopQuery]; [progView stop]; [self updateElementsLabel: 0]; [pathViewer showComponentsOfSelection: nil]; rowsCount = 0; globalCount = 0; [self updateCategoryControls: NO removeSubviews: YES]; [resultsView noteNumberOfRowsChanged]; [resultsView setNeedsDisplayInRect: [resultsView visibleRect]]; ASSIGN (currentQuery, [MDKQuery query]); [currentQuery setUpdatesEnabled: YES]; [currentQuery setDelegate: self]; casesens = ([caseSensButt state] == NSOnState); operator = MDKCompoundOperatorNone; onlyfilters = YES; words = [textContentEditor textContentWords]; for (i = 0; i < [words count]; i++) { [currentQuery appendSubqueryWithCompoundOperator: operator attribute: @"GSMDItemTextContent" searchValue: [words objectAtIndex: i] operatorType: MDKEqualToOperatorType caseSensitive: casesens]; operator = GMDAndCompoundOperator; onlyfilters = NO; } fsfilters = [NSMutableArray array]; for (i = 0; i < [queryEditors count]; i++) { MDKAttributeEditor *editor = [queryEditors objectAtIndex: i]; MDKAttribute *attribute = [editor attribute]; NSDictionary *edinfo = [editor editorInfo]; NSString *name = [edinfo objectForKey: @"attrname"]; MDKOperatorType type = [[edinfo objectForKey: @"optype"] intValue]; NSArray *values = [edinfo objectForKey: @"values"]; BOOL fsfilter = [attribute isFsattribute]; if (fsfilter == NO) { BOOL csens = [[edinfo objectForKey: @"casesens"] boolValue]; if ([attribute type] != ARRAY) { [currentQuery appendSubqueryWithCompoundOperator: operator attribute: name searchValue: [values objectAtIndex: 0] operatorType: type caseSensitive: csens]; operator = GMDAndCompoundOperator; } else { for (j = 0; j < [values count]; j++) { [currentQuery appendSubqueryWithCompoundOperator: operator attribute: name searchValue: [values objectAtIndex: j] operatorType: type caseSensitive: csens]; operator = GMDAndCompoundOperator; } } onlyfilters = NO; } else { MDKFSFilter *filter = [MDKFSFilter filterForAttribute: attribute operatorType: type searchValue: [values objectAtIndex: 0]]; if (filter) { [fsfilters addObject: filter]; } } } [currentQuery closeSubqueries]; if ([searchPaths count]) { [currentQuery setSearchPaths: searchPaths]; } if ([currentQuery buildQuery] == NO) { NSLog(@"unable to build \"%@\"", [currentQuery description]); [NSApp terminate: self]; } [currentQuery setFSFilters: fsfilters]; [self prepareResultCategories]; if (onlyfilters == NO) { closing = NO; [currentQuery startGathering]; } else { // } RELEASE (arp); } - (void)prepareResultCategories { int i; for (i = 0; i < [categoryNames count]; i++) { NSString *catname = [categoryNames objectAtIndex: i]; MDKResultsCategory *rescat = [resultCategories objectForKey: catname]; NSArray *nodes = [currentQuery resultNodesForCategory: catname]; [rescat setResults: nodes]; } } - (void)queryDidStartGathering:(MDKQuery *)query { [progView start]; } - (void)appendRawResults:(NSArray *)lines { } - (void)queryDidUpdateResults:(MDKQuery *)query forCategories:(NSArray *)catnames { [self updateCategoryControls: YES removeSubviews: NO]; [self updateElementsLabel: globalCount]; } - (void)queryDidEndGathering:(MDKQuery *)query { if (query == currentQuery) { [progView stop]; [self updateElementsLabel: globalCount]; if (closing) { [win close: nil]; } } } - (void)queryDidStartUpdating:(MDKQuery *)query { if (query == currentQuery) { [progView start]; } } - (void)queryDidEndUpdating:(MDKQuery *)query { if (query == currentQuery) { [progView stop]; [self updateElementsLabel: globalCount]; } } - (IBAction)stopSearchButtAction:(id)sender { [self stopCurrentQuery]; rowsCount = 0; globalCount = 0; [self updateCategoryControls: NO removeSubviews: YES]; [resultsView noteNumberOfRowsChanged]; [resultsView setNeedsDisplayInRect: [resultsView visibleRect]]; [pathViewer showComponentsOfSelection: nil]; [self updateElementsLabel: 0]; } - (void)stopCurrentQuery { if (currentQuery) { [currentQuery setUpdatesEnabled: NO]; [currentQuery stopQuery]; [progView stop]; } } - (void)updateElementsLabel:(int)n { NSString *elemstr = NSLocalizedString(@"elements", @""); NSString *str = [NSString stringWithFormat: @"%i %@", n, elemstr]; [elementsLabel setStringValue: str]; } - (void)queryCategoriesDidChange:(NSNotification *)notif { [self prepareResults]; } - (MDKQuery *)currentQuery { return currentQuery; } @end @implementation MDKWindow (TableView) // // NSTableDataSource protocol // - (int)numberOfRowsInTableView:(NSTableView *)aTableView { return rowsCount; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { id nd = [catlist resultAtIndex: rowIndex]; if ((*isMember)(nd, memberSel, FSNodeClass)) { if (aTableColumn == nameColumn) { return [nd name]; } else if (aTableColumn == attrColumn) { return [nd modDateDescription]; } } return [NSString string]; } - (BOOL)tableView:(NSTableView *)aTableView writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard { NSMutableArray *paths = [NSMutableArray array]; NSMutableArray *parentPaths = [NSMutableArray array]; int i; for (i = 0; i < [rows count]; i++) { int index = [[rows objectAtIndex: i] intValue]; id nd = [catlist resultAtIndex: index]; if ((*isMember)(nd, memberSel, FSNodeClass) && [nd isValid]) { NSString *parentPath = [nd parentPath]; if (([parentPaths containsObject: parentPath] == NO) && (i != 0)) { NSString *msg = NSLocalizedString(@"You can't move objects with multiple parent paths!", @""); NSRunAlertPanel(nil, msg, NSLocalizedString(@"Continue", @""), nil, nil); return NO; } [paths addObject: [nd path]]; [parentPaths addObject: parentPath]; } } if ([paths count]) { [pboard declareTypes: [NSArray arrayWithObject: NSFilenamesPboardType] owner: nil]; [pboard setPropertyList: paths forType: NSFilenamesPboardType]; return YES; } return NO; } // // NSTableView delegate methods // - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { NSArray *selected = [self selectedObjects]; [pathViewer showComponentsOfSelection: selected]; if (CHECKDELEGATE (window:didChangeSelection:)) { [delegate window: self didChangeSelection: selected]; } } - (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { id nd = [catlist resultAtIndex: rowIndex]; if ((*isMember)(nd, memberSel, FSNodeClass)) { [aCell setHeadCell: NO]; if (aTableColumn == nameColumn) { [aCell setIcon: [fsnodeRep iconOfSize: ICNSIZE forNode: nd]]; } else if (aTableColumn == attrColumn) { } } else { MDKResultsCategory *rescat = [nd objectForKey: @"category"]; BOOL ishead = [[nd objectForKey: @"head"] boolValue]; NSView *controls = (ishead ? [rescat headControls] : [rescat footControls]); [aCell setHeadCell: YES]; [controls setFrame: [resultsView rectOfRow: rowIndex]]; } } // // other methods // - (void)updateCategoryControls:(BOOL)newranges removeSubviews:(BOOL)remove { NSArray *rviews = [resultsView subviews]; int i; if (newranges) { [catlist calculateRanges]; } for (i = 0; i < [categoryNames count]; i++) { NSString *catname = [categoryNames objectAtIndex: i]; MDKResultsCategory *rescat = [resultCategories objectForKey: catname]; NSView *headControls = [rescat headControls]; NSView *footControls = [rescat footControls]; if (remove == NO) { if ([rescat hasResults]) { if ([rviews containsObject: headControls] == NO) { [resultsView addControlView: headControls]; } if ([rescat showFooter]) { if ([rviews containsObject: footControls] == NO) { [resultsView addControlView: footControls]; } } else { if ([rviews containsObject: footControls]) { [resultsView removeControlView: footControls]; } } } else { if ([rviews containsObject: headControls]) { [resultsView removeControlView: headControls]; } if ([rviews containsObject: footControls]) { [resultsView removeControlView: footControls]; } } } else { if ([rviews containsObject: headControls]) { [resultsView removeControlView: headControls]; } if ([rviews containsObject: footControls]) { [resultsView removeControlView: footControls]; } } } if (newranges) { MDKResultsCategory *last = [catlist last]; NSRange range = [last range]; rowsCount = range.location + range.length; globalCount = [last globalCount]; [resultsView noteNumberOfRowsChanged]; [resultsView setNeedsDisplayInRect: [resultsView visibleRect]]; } } - (void)doubleClickOnResultsView:(id)sender { NSWorkspace *ws = [NSWorkspace sharedWorkspace]; NSArray *selected = [self selectedObjects]; int count = [selected count]; int i; if (count > OPEN_MAX) { NSString *msg1 = NSLocalizedString(@"Are you sure you want to open", @""); NSString *msg2 = NSLocalizedString(@"items?", @""); if (NSRunAlertPanel(nil, [NSString stringWithFormat: @"%@ %i %@", msg1, count, msg2], NSLocalizedString(@"Cancel", @""), NSLocalizedString(@"Yes", @""), nil)) { return; } } for (i = 0; i < count; i++) { FSNode *nd = [selected objectAtIndex: i]; if ([nd hasValidPath]) { NSString *path = [nd path]; NS_DURING { if ([nd isDirectory]) { if ([nd isPackage]) { if ([nd isApplication] == NO) { [ws openFile: path]; } else { [ws launchApplication: path]; } } else { [ws selectFile: path inFileViewerRootedAtPath: path]; } } else if ([nd isPlain]) { [ws openFile: path]; } } NS_HANDLER { NSRunAlertPanel(NSLocalizedString(@"error", @""), [NSString stringWithFormat: @"%@ %@!", NSLocalizedString(@"Can't open ", @""), [nd name]], NSLocalizedString(@"OK", @""), nil, nil); } NS_ENDHANDLER } } } - (NSArray *)selectedObjects { NSMutableArray *selected = [NSMutableArray array]; NSEnumerator *enumerator = [resultsView selectedRowEnumerator]; NSNumber *row; while ((row = [enumerator nextObject])) { id nd = [catlist resultAtIndex: [row intValue]]; if ((*isMember)(nd, memberSel, FSNodeClass) && [nd isValid]) { [selected addObject: nd]; } } return selected; } - (NSArray *)selectedPaths { NSArray *selnodes = [self selectedObjects]; NSMutableArray *selpaths = [NSMutableArray array]; int i; for (i = 0; i < [selnodes count]; i++) { [selpaths addObject: [[selnodes objectAtIndex: i] path]]; } return [selpaths makeImmutableCopyOnFail: NO]; } - (NSImage *)tableView:(NSTableView *)tableView dragImageForRows:(NSArray *)dragRows { if ([dragRows count] > 1) { return [fsnodeRep multipleSelectionIconOfSize: ICNSIZE]; } else { int index = [[dragRows objectAtIndex: 0] intValue]; FSNode *nd = [catlist resultAtIndex: index]; if ((*isMember)(nd, memberSel, FSNodeClass) && [nd isValid]) { return [fsnodeRep iconOfSize: ICNSIZE forNode: nd]; } } return nil; } @end @implementation ProgrView #define IMAGES 8 - (void)dealloc { RELEASE (images); [super dealloc]; } - (id)initWithFrame:(NSRect)frameRect { self = [super initWithFrame: frameRect]; if (self) { NSBundle *bundle = [NSBundle bundleForClass: [self class]]; int i; images = [NSMutableArray new]; for (i = 0; i < IMAGES; i++) { NSString *imname = [NSString stringWithFormat: @"anim-logo-%d", i]; NSString *impath = [bundle pathForResource: imname ofType: @"tiff"]; NSImage *image = [[NSImage alloc] initWithContentsOfFile: impath]; if (image) { [images addObject: image]; RELEASE (image); } } animating = NO; } return self; } - (void)start { if (animating == NO) { index = 0; animating = YES; progTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(animate:) userInfo: nil repeats: YES]; } } - (void)stop { if (animating) { animating = NO; if (progTimer && [progTimer isValid]) { [progTimer invalidate]; } [self setNeedsDisplay: YES]; } } - (void)animate:(id)sender { [self setNeedsDisplay: YES]; index++; if (index == [images count]) { index = 0; } } - (void)drawRect:(NSRect)rect { [super drawRect: rect]; if (animating) { [[images objectAtIndex: index] compositeToPoint: NSMakePoint(0, 0) operation: NSCompositeSourceOver]; } } @end BOOL isDotFile(NSString *path) { int len = ([path length] - 1); static unichar sep = 0; unichar c; int i; if (sep == 0) { #if defined(__MINGW32__) sep = '\\'; #else sep = '/'; #endif } for (i = len; i >= 0; i--) { c = [path characterAtIndex: i]; if (c == '.') { if ((i > 0) && ([path characterAtIndex: (i - 1)] == sep)) { return YES; } } } return NO; } NSString *pathSeparator(void) { static NSString *separator = nil; if (separator == nil) { #if defined(__MINGW32__) separator = @"\\"; #else separator = @"/"; #endif RETAIN (separator); } return separator; }