1/* SearchResults.m
2 *
3 * Copyright (C) 2004-2014 Free Software Foundation, Inc.
4 *
5 * Author: Enrico Sersale <enrico@imago.ro>
6 * Date: March 2004
7 *
8 * This file is part of the GNUstep GWorkspace application
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.
23 */
24
25#include <math.h>
26
27#import <Foundation/Foundation.h>
28#import <AppKit/AppKit.h>
29
30#import "SearchResults.h"
31#import "ResultsTableView.h"
32#import "FSNTextCell.h"
33#import "Finder.h"
34#import "FinderModulesProtocol.h"
35#import "FSNode.h"
36#import "FSNodeRep.h"
37#import "FSNPathComponentsViewer.h"
38#import "GWFunctions.h"
39#import "Dialogs/Dialogs.h"
40
41#define CELLS_HEIGHT (28.0)
42
43#define LSF_INFO(x) [x stringByAppendingPathComponent: @"lsf.info"]
44#define LSF_FOUND(x) [x stringByAppendingPathComponent: @"lsf.found"]
45
46static NSString *nibName = @"SearchResults";
47static NSString *lsfname = @"LiveSearch.lsf";
48
49@implementation SearchResults
50
51- (void)dealloc
52{
53  [nc removeObserver: self];
54
55  if (toolConn != nil) {
56    if (searchtool != nil) {
57      [searchtool terminate];
58    }
59    DESTROY (searchtool);
60    DESTROY (toolConn);
61  }
62
63  RELEASE (win);
64  RELEASE (searchCriteria);
65  RELEASE (foundObjects);
66  RELEASE (searchPaths);
67  RELEASE (elementsStr);
68  DESTROY (conn);
69
70  [super dealloc];
71}
72
73- (id)init
74{
75  self = [super init];
76
77  if (self) {
78    NSUserDefaults *defaults;
79    id entry;
80    NSRect r;
81
82		if ([NSBundle loadNibNamed: nibName owner: self] == NO) {
83      NSLog(@"failed to load %@!", nibName);
84      DESTROY (self);
85      return self;
86    }
87
88    [win setFrameUsingName: @"search_results"];
89    [win setAcceptsMouseMovedEvents: YES];
90    [win setDelegate: self];
91
92    progView = [[ProgressView alloc] initWithFrame: NSMakeRect(0, 0, 16, 16)
93                                   refreshInterval: 0.1];
94    [progBox setContentView: progView];
95    RELEASE (progView);
96
97    r = [[dragIconBox contentView] bounds];
98    documentIcon = [[DocumentIcon alloc] initWithFrame: r searchResult: self];
99    [dragIconBox setContentView: documentIcon];
100    RELEASE (documentIcon);
101
102    [elementsLabel setStringValue: @""];
103    ASSIGN (elementsStr, NSLocalizedString(@"elements", @""));
104
105    [stopButt setImage: [NSImage imageNamed: @"stop_small"]];
106    [stopButt setEnabled: NO];
107    [restartButt setImage: [NSImage imageNamed: @"magnify_small"]];
108    [restartButt setEnabled: NO];
109
110    [resultsScroll setBorderType: NSBezelBorder];
111    [resultsScroll setHasHorizontalScroller: YES];
112    [resultsScroll setHasVerticalScroller: YES];
113
114    r = [[resultsScroll contentView] bounds];
115
116    resultsView = [[ResultsTableView alloc] initWithFrame: r];
117    [resultsView setDrawsGrid: NO];
118    [resultsView setAllowsColumnSelection: NO];
119    [resultsView setAllowsColumnReordering: YES];
120    [resultsView setAllowsColumnResizing: YES];
121    [resultsView setAllowsEmptySelection: YES];
122    [resultsView setAllowsMultipleSelection: YES];
123    [resultsView setRowHeight: CELLS_HEIGHT];
124    [resultsView setIntercellSpacing: NSZeroSize];
125    [resultsView sizeLastColumnToFit];
126
127    nameColumn = [[NSTableColumn alloc] initWithIdentifier: @"name"];
128    [nameColumn setDataCell: AUTORELEASE ([[FSNTextCell alloc] init])];
129    [nameColumn setEditable: NO];
130    [nameColumn setResizable: YES];
131    [[nameColumn headerCell] setStringValue: NSLocalizedString(@"Name", @"")];
132    [[nameColumn headerCell] setAlignment: NSLeftTextAlignment];
133    [nameColumn setMinWidth: 80];
134    [nameColumn setWidth: 140];
135    [resultsView addTableColumn: nameColumn];
136    RELEASE (nameColumn);
137
138    parentColumn = [[NSTableColumn alloc] initWithIdentifier: @"parent"];
139    [parentColumn setDataCell: AUTORELEASE ([[FSNTextCell alloc] init])];
140    [parentColumn setEditable: NO];
141    [parentColumn setResizable: YES];
142    [[parentColumn headerCell] setStringValue: NSLocalizedString(@"Parent", @"")];
143    [[parentColumn headerCell] setAlignment: NSLeftTextAlignment];
144    [parentColumn setMinWidth: 80];
145    [parentColumn setWidth: 90];
146    [resultsView addTableColumn: parentColumn];
147    RELEASE (parentColumn);
148
149    dateColumn = [[NSTableColumn alloc] initWithIdentifier: @"date"];
150    [dateColumn setDataCell: AUTORELEASE ([[FSNTextCell alloc] init])];
151    [dateColumn setEditable: NO];
152    [dateColumn setResizable: YES];
153    [[dateColumn headerCell] setStringValue: NSLocalizedString(@"Date Modified", @"")];
154    [[dateColumn headerCell] setAlignment: NSLeftTextAlignment];
155    [dateColumn setMinWidth: 80];
156    [dateColumn setWidth: 90];
157    [resultsView addTableColumn: dateColumn];
158    RELEASE (dateColumn);
159
160    sizeColumn = [[NSTableColumn alloc] initWithIdentifier: @"size"];
161    [sizeColumn setDataCell: AUTORELEASE ([[FSNTextCell alloc] init])];
162    [sizeColumn setEditable: NO];
163    [sizeColumn setResizable: YES];
164    [[sizeColumn headerCell] setStringValue: NSLocalizedString(@"Size", @"")];
165    [[sizeColumn headerCell] setAlignment: NSLeftTextAlignment];
166    [sizeColumn setMinWidth: 50];
167    [sizeColumn setWidth: 50];
168    [resultsView addTableColumn: sizeColumn];
169    RELEASE (sizeColumn);
170
171    kindColumn = [[NSTableColumn alloc] initWithIdentifier: @"kind"];
172    [kindColumn setDataCell: AUTORELEASE ([[FSNTextCell alloc] init])];
173    [kindColumn setEditable: NO];
174    [kindColumn setResizable: YES];
175    [[kindColumn headerCell] setStringValue: NSLocalizedString(@"Type", @"")];
176    [[kindColumn headerCell] setAlignment: NSLeftTextAlignment];
177    [kindColumn setMinWidth: 80];
178    [kindColumn setWidth: 80];
179    [resultsView addTableColumn: kindColumn];
180    RELEASE (kindColumn);
181
182    [resultsScroll setDocumentView: resultsView];
183    RELEASE (resultsView);
184
185    [self setColumnsSizes];
186
187    [resultsView setDataSource: self];
188    [resultsView setDelegate: self];
189    [resultsView setTarget: self];
190    [resultsView setDoubleAction: @selector(doubleClickOnResultsView:)];
191
192    foundObjects = [NSMutableArray new];
193
194    defaults = [NSUserDefaults standardUserDefaults];
195
196    entry = [defaults stringForKey: @"sorting_order"];
197    if (entry) {
198      [self setCurrentOrder: [entry intValue]];
199    } else {
200      [self setCurrentOrder: FSNInfoNameType];
201    }
202
203    r = [[pathBox contentView] bounds];
204    pathViewer = [[FSNPathComponentsViewer alloc] initWithFrame: r];
205    [pathBox setContentView: pathViewer];
206    RELEASE (pathViewer);
207
208    finder = [Finder finder];
209    fm = [NSFileManager defaultManager];
210    ws = [NSWorkspace sharedWorkspace];
211    nc = [NSNotificationCenter defaultCenter];
212
213    [nc addObserver: self
214           selector: @selector(fileSystemDidChange:)
215               name: @"GWFileSystemDidChangeNotification"
216             object: nil];
217  }
218
219	return self;
220}
221
222- (void)activateForSelection:(NSArray *)selection
223          withSearchCriteria:(NSDictionary *)criteria
224                   recursive:(BOOL)rec
225{
226  NSString *cname;
227  NSString *cmd;
228
229  [win makeKeyAndOrderFront: nil];
230  visibleRows = (int)([resultsScroll bounds].size.height / CELLS_HEIGHT + 1);
231  ASSIGN (searchPaths, selection);
232  ASSIGN (searchCriteria, criteria);
233  recursive = rec;
234
235  cname = [NSString stringWithFormat: @"search_%lu", [self memAddress]];
236
237  if (conn == nil) {
238    conn = [[NSConnection alloc] initWithReceivePort: (NSPort *)[NSPort port]
239																			      sendPort: nil];
240    [conn setRootObject: self];
241    [conn registerName: cname];
242    [conn setDelegate: self];
243
244    [nc addObserver: self
245           selector: @selector(connectionDidDie:)
246               name: NSConnectionDidDieNotification
247             object: conn];
248  }
249
250  if (toolConn != nil) {
251    if (searchtool != nil) {
252      [searchtool terminate];
253    }
254
255    DESTROY (searchtool);
256
257    [nc removeObserver: self
258	                name: NSConnectionDidDieNotification
259                object: toolConn];
260
261    [toolConn invalidate];
262    DESTROY (toolConn);
263  }
264
265  searchtool = nil;
266  searching = YES;
267
268  [NSTimer scheduledTimerWithTimeInterval: 5.0
269						                       target: self
270                                 selector: @selector(checkSearchTool:)
271																 userInfo: nil
272                                  repeats: NO];
273
274  cmd = [NSTask launchPathForTool: @"searchtool"];
275
276  [NSTask launchedTaskWithLaunchPath: cmd
277                           arguments: [NSArray arrayWithObject: cname]];
278}
279
280- (BOOL)connection:(NSConnection *)ancestor
281								shouldMakeNewConnection:(NSConnection *)newConn
282{
283	if (ancestor == conn) {
284    ASSIGN (toolConn, newConn);
285  	[toolConn setDelegate: self];
286
287  	[nc addObserver: self
288					 selector: @selector(connectionDidDie:)
289	    				 name: NSConnectionDidDieNotification
290             object: toolConn];
291	}
292
293  return YES;
294}
295
296- (void)connectionDidDie:(NSNotification *)notification
297{
298	id diedconn = [notification object];
299
300  [nc removeObserver: self
301	              name: NSConnectionDidDieNotification
302              object: diedconn];
303
304  if ((diedconn == conn) || (toolConn && (diedconn == toolConn))) {
305    DESTROY (searchtool);
306    DESTROY (toolConn);
307
308    if (diedconn == conn) {
309      DESTROY (conn);
310    }
311
312    if (searching) {
313      [self endOfSearch];
314      NSRunAlertPanel(nil,
315                      NSLocalizedString(@"the search tool connection died!", @""),
316                      NSLocalizedString(@"Continue", @""),
317                      nil,
318                      nil);
319    }
320  }
321}
322
323- (void)checkSearchTool:(id)sender
324{
325  if (searching && (searchtool == nil)) {
326    [self endOfSearch];
327    NSRunAlertPanel(nil,
328                    NSLocalizedString(@"unable to launch the search task.", @""),
329                    NSLocalizedString(@"Continue", @""),
330                    nil,
331                    nil);
332  }
333}
334
335- (oneway void)registerSearchTool:(id)tool
336{
337  NSDictionary *srcdict = [NSDictionary dictionaryWithObjectsAndKeys:
338                    searchPaths, @"paths", searchCriteria, @"criteria",
339                     [NSNumber numberWithBool: recursive], @"recursion", nil];
340  NSData *info = [NSArchiver archivedDataWithRootObject: srcdict];
341
342  [stopButt setEnabled: YES];
343  [restartButt setEnabled: NO];
344  [progView start];
345
346  [tool setProtocolForProxy: @protocol(SearchToolProtocol)];
347  searchtool = (id <SearchToolProtocol>)[tool retain];
348  [searchtool searchWithInfo: info];
349}
350
351- (void)nextResult:(NSString *)path
352{
353  CREATE_AUTORELEASE_POOL(pool);
354  FSNode *node = [FSNode nodeWithPath: path];
355
356  [foundObjects addObject: node];
357
358  if ([foundObjects count] <= visibleRows) {
359    [resultsView noteNumberOfRowsChanged];
360  }
361
362  [elementsLabel setStringValue: [NSString stringWithFormat: @"%lu %@",
363                                           (unsigned long)[foundObjects count], elementsStr]];
364  RELEASE (pool);
365}
366
367- (void)endOfSearch
368{
369  [stopButt setEnabled: NO];
370  [restartButt setEnabled: YES];
371  [progView stop];
372  searching = NO;
373
374  if (searchtool) {
375    [nc removeObserver: self
376	                name: NSConnectionDidDieNotification
377                object: toolConn];
378    [searchtool terminate];
379    DESTROY (searchtool);
380    DESTROY (toolConn);
381  }
382
383  [self updateShownData];
384}
385
386- (BOOL)searching
387{
388  return searching;
389}
390
391- (IBAction)stopSearch:(id)sender
392{
393  if (searchtool) {
394    [searchtool stop];
395  }
396}
397
398- (IBAction)restartSearch:(id)sender
399{
400  if (searchtool == nil) {
401    [pathViewer showComponentsOfSelection: nil];
402    [foundObjects removeAllObjects];
403    [resultsView reloadData];
404    [self activateForSelection: searchPaths
405            withSearchCriteria: searchCriteria
406                     recursive: recursive];
407  }
408}
409
410- (void)updateShownData
411{
412  SEL sortingSel;
413  NSTableColumn *column;
414
415  switch(currentOrder) {
416    case FSNInfoNameType:
417      sortingSel = @selector(compareAccordingToName:);
418      column = nameColumn;
419      break;
420    case FSNInfoParentType:
421      sortingSel = @selector(compareAccordingToParent:);
422      column = parentColumn;
423      break;
424    case FSNInfoKindType:
425      sortingSel = @selector(compareAccordingToKind:);
426      column = kindColumn;
427      break;
428    case FSNInfoDateType:
429      sortingSel = @selector(compareAccordingToDate:);
430      column = dateColumn;
431      break;
432    case FSNInfoSizeType:
433      sortingSel = @selector(compareAccordingToSize:);
434      column = sizeColumn;
435      break;
436    default:
437      sortingSel = @selector(compareAccordingToName:);
438      column = nameColumn;
439      break;
440  }
441
442  [foundObjects sortUsingSelector: sortingSel];
443  [resultsView setHighlightedTableColumn: column];
444  [resultsView reloadData];
445}
446
447- (void)setCurrentOrder:(FSNInfoType)order
448{
449  currentOrder = order;
450}
451
452- (NSArray *)selectedObjects
453{
454  NSMutableArray *selected = [NSMutableArray array];
455  NSEnumerator *enumerator = [resultsView selectedRowEnumerator];
456  NSNumber *row;
457
458  while ((row = [enumerator nextObject])) {
459	  FSNode *node = [foundObjects objectAtIndex: [row intValue]];
460    if ([node isValid]) {
461      [selected addObject: node];
462    } else {
463      [foundObjects removeObject: node];
464      [resultsView noteNumberOfRowsChanged];
465    }
466  }
467
468  return selected;
469}
470
471- (void)doubleClickOnResultsView:(id)sender
472{
473  [finder openFoundSelection: [self selectedObjects]];
474}
475
476- (void)selectObjects:(NSArray *)objects
477{
478  int i;
479
480  for (i = 0; i < [objects count]; i++) {
481    FSNode *node = [objects objectAtIndex: i];
482    int index = [foundObjects indexOfObject: node];
483
484    [resultsView selectRow: index byExtendingSelection: (i != 0)];
485  }
486}
487
488- (void)fileSystemDidChange:(NSNotification *)notif
489{
490  NSDictionary *info = [notif object];
491  NSString *operation = [info objectForKey: @"operation"];
492  NSString *source = [info objectForKey: @"source"];
493  NSString *destination = [info objectForKey: @"destination"];
494  NSArray *files = [info objectForKey: @"files"];
495  NSMutableArray *deletedObjects = [NSMutableArray array];
496  NSUInteger i, j;
497
498  if ([operation isEqual: @"GWorkspaceRenameOperation"]) {
499    files = [NSArray arrayWithObject: [destination lastPathComponent]];
500  }
501
502  if ([operation isEqual: NSWorkspaceRecycleOperation]) {
503		files = [info objectForKey: @"origfiles"];
504  }
505
506  if ([operation isEqual: NSWorkspaceMoveOperation]
507        || [operation isEqual: NSWorkspaceDestroyOperation]
508				|| [operation isEqual: NSWorkspaceRecycleOperation]
509				|| [operation isEqual: @"GWorkspaceRecycleOutOperation"]
510				|| [operation isEqual: @"GWorkspaceEmptyRecyclerOperation"]) {
511    for (i = 0; i < [files count]; i++) {
512      NSString *fname = [files objectAtIndex: i];
513      NSString *fullPath = [source stringByAppendingPathComponent: fname];
514
515      for (j = 0; j < [foundObjects count]; j++) {
516        FSNode *node = [foundObjects objectAtIndex: j];
517        NSString *path = [node path];
518
519        if ([fullPath isEqual: path]) {
520          [deletedObjects addObject: node];
521        }
522      }
523    }
524
525  } else if ([operation isEqual: @"GWorkspaceRenameOperation"]) {
526    for (i = 0; i < [foundObjects count]; i++) {
527      FSNode *node = [foundObjects objectAtIndex: i];
528      NSString *path = [node path];
529
530      if ([source isEqual: path]) {
531        [deletedObjects addObject: node];
532      }
533    }
534  }
535
536  if ([deletedObjects count]) {
537    for (i = 0; i < [deletedObjects count]; i++) {
538      [foundObjects removeObject: [deletedObjects objectAtIndex: i]];
539    }
540
541    [resultsView deselectAll: self];
542    [self updateShownData];
543  }
544}
545
546- (void)setColumnsSizes
547{
548  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
549  NSDictionary *columnsDict = [defaults objectForKey: @"columns_sizes"];
550
551  if (columnsDict) {
552    NSArray *columns = [resultsView tableColumns];
553    NSMutableArray *sortedCols = [NSMutableArray array];
554    NSArray *keys = [columnsDict keysSortedByValueUsingSelector: @selector(compareColInfo:)];
555    int i;
556
557    for (i = 0; i < [keys count]; i++) {
558      NSString *identifier = [keys objectAtIndex: i];
559      int col = [resultsView columnWithIdentifier: identifier];
560      NSTableColumn *column = [columns objectAtIndex: col];
561      NSDictionary *cdict = [columnsDict objectForKey: identifier];
562      float width = [[cdict objectForKey: @"width"] floatValue];
563
564      [column setWidth: width];
565      [sortedCols insertObject: column atIndex: [sortedCols count]];
566    }
567
568    for (i = 0; i < [sortedCols count]; i++) {
569      [resultsView removeTableColumn: [sortedCols objectAtIndex: i]];
570    }
571
572    for (i = 0; i < [sortedCols count]; i++) {
573      [resultsView addTableColumn: [sortedCols objectAtIndex: i]];
574    }
575  }
576}
577
578- (void)saveColumnsSizes
579{
580  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
581  NSMutableDictionary *columnsDict = [NSMutableDictionary dictionary];
582  NSArray *columns = [resultsView tableColumns];
583  int i;
584
585  for (i = 0; i < [columns count]; i++) {
586    NSTableColumn *column = [columns objectAtIndex: i];
587    NSString *identifier = [column identifier];
588    NSNumber *cwidth = [NSNumber numberWithFloat: [column width]];
589    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
590
591    [dict setObject: [NSNumber numberWithInt: i] forKey: @"position"];
592    [dict setObject: cwidth forKey: @"width"];
593
594    [columnsDict setObject: dict forKey: identifier];
595  }
596
597  [defaults setObject: columnsDict forKey: @"columns_sizes"];
598  [defaults synchronize];
599}
600
601- (NSWindow *)win
602{
603  return win;
604}
605
606- (unsigned long)memAddress
607{
608  return (unsigned long)self;
609}
610
611- (void)createLiveSearchFolderAtPath:(NSString *)path
612{
613  SympleDialog *dialog;
614  NSString *folderName;
615  NSArray *contents;
616  int result;
617
618  dialog = [[SympleDialog alloc] initWithTitle: NSLocalizedString(@"New Live Search", @"")
619                                      editText: lsfname
620                                   switchTitle: nil];
621  AUTORELEASE (dialog);
622  [dialog center];
623  [dialog makeKeyWindow];
624  [dialog orderFrontRegardless];
625
626  result = [dialog runModal];
627  if (result != NSAlertDefaultReturn) {
628    return;
629  }
630
631  folderName = [dialog getEditFieldText];
632
633  if ([folderName length] == 0) {
634		NSString *msg = NSLocalizedString(@"No name supplied!", @"");
635		NSString *buttstr = NSLocalizedString(@"Continue", @"");
636    NSRunAlertPanel(nil, msg, buttstr, nil, nil);
637    return;
638  }
639
640  contents = [fm directoryContentsAtPath: path];
641
642  if ([contents containsObject: folderName] == NO) {
643    NSNotificationCenter *dnc = [NSDistributedNotificationCenter defaultCenter];
644    NSMutableDictionary *notifDict = [NSMutableDictionary dictionary];
645    NSString *lsfpath = [path stringByAppendingPathComponent: folderName];
646    BOOL lsfdone = YES;
647
648    [notifDict setObject: @"GWorkspaceCreateDirOperation"
649                  forKey: @"operation"];
650    [notifDict setObject: path forKey: @"source"];
651    [notifDict setObject: path forKey: @"destination"];
652    [notifDict setObject: [NSArray arrayWithObject: folderName]
653                  forKey: @"files"];
654
655	  [dnc postNotificationName: @"GWFileSystemWillChangeNotification"
656	 								     object: nil
657                     userInfo: notifDict];
658
659    if ([fm createDirectoryAtPath: lsfpath attributes: nil]) {
660      NSMutableArray *foundPaths = [NSMutableArray array];
661      NSMutableDictionary *lsfdict = [NSMutableDictionary dictionary];
662      int i;
663
664      for (i = 0; i < [foundObjects count]; i++) {
665        [foundPaths addObject: [[foundObjects objectAtIndex: i] path]];
666      }
667
668      [lsfdict setObject: searchPaths forKey: @"searchpaths"];
669      [lsfdict setObject: searchCriteria forKey: @"criteria"];
670      [lsfdict setObject: [NSNumber numberWithBool: recursive]
671                  forKey: @"recursion"];
672      [lsfdict setObject: [[NSDate date] description] forKey: @"lastupdate"];
673
674      lsfdone = [lsfdict writeToFile: LSF_INFO(lsfpath) atomically: YES];
675      lsfdone = [foundPaths writeToFile: LSF_FOUND(lsfpath) atomically: YES];
676    } else {
677      lsfdone = NO;
678    }
679
680    if (lsfdone) {
681      [finder addLiveSearchFolderWithPath: lsfpath createIndex: YES];
682    } else {
683      NSString *msg = NSLocalizedString(@"can't create the Live Search folder", @"");
684      NSRunAlertPanel(NULL, msg, NSLocalizedString(@"Ok", @""), NULL, NULL);
685    }
686
687	  [dnc postNotificationName: @"GWFileSystemDidChangeNotification"
688	 						         object: nil
689                     userInfo: notifDict];
690  } else {
691    NSString *msg = [NSString stringWithFormat: @"a file named \"%@\" already exists.\nPlease rename it.", folderName];
692    NSRunAlertPanel(NULL, msg, NSLocalizedString(@"Ok", @""), NULL, NULL);
693  }
694}
695
696- (BOOL)windowShouldClose:(id)sender
697{
698	return !searching;
699}
700
701- (void)windowDidMove:(NSNotification *)aNotification
702{
703  [win saveFrameUsingName: @"search_results"];
704}
705
706- (void)windowDidBecomeKey:(NSNotification *)aNotification
707{
708  NSArray *selected = [self selectedObjects];
709
710  if ([selected count]) {
711    [finder foundSelectionChanged: selected];
712  }
713}
714
715- (void)windowWillClose:(NSNotification *)aNotification
716{
717  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
718
719  [defaults setObject: [NSNumber numberWithInt: currentOrder]
720               forKey: @"sorting_order"];
721  [defaults synchronize];
722
723  [self saveColumnsSizes];
724
725  [win saveFrameUsingName: @"search_results"];
726  [finder resultsWindowWillClose: self];
727}
728
729
730//
731// NSTableDataSource protocol
732//
733- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
734{
735  return [foundObjects count];
736}
737
738- (id)tableView:(NSTableView *)aTableView
739          objectValueForTableColumn:(NSTableColumn *)aTableColumn
740                                row:(NSInteger)rowIndex
741{
742  FSNode *node = [foundObjects objectAtIndex: rowIndex];
743
744  if (aTableColumn == nameColumn) {
745    return [node name];
746  } else if (aTableColumn == parentColumn) {
747    return [[node parentPath] lastPathComponent];
748  } else if (aTableColumn == dateColumn) {
749    return [node modDateDescription];
750  } else if (aTableColumn == sizeColumn) {
751    return [node sizeDescription];
752  } else if (aTableColumn == kindColumn) {
753    return [node typeDescription];
754  }
755
756  return [NSString string];
757}
758
759- (BOOL)tableView:(NSTableView *)aTableView
760	      writeRows:(NSArray *)rows
761     toPasteboard:(NSPasteboard *)pboard
762{
763  NSMutableArray *paths = [NSMutableArray array];
764  NSMutableArray *parentPaths = [NSMutableArray array];
765  int i;
766
767  for (i = 0; i < [rows count]; i++) {
768    int index = [[rows objectAtIndex: i] intValue];
769    FSNode *node = [foundObjects objectAtIndex: index];
770    NSString *parentPath = [node parentPath];
771
772    if (([parentPaths containsObject: parentPath] == NO) && (i != 0)) {
773      NSString *msg = NSLocalizedString(@"You can't move objects with multiple parent paths!", @"");
774      NSRunAlertPanel(nil, msg, NSLocalizedString(@"Continue", @""), nil, nil);
775      return NO;
776    }
777
778    if ([node isValid]) {
779      [paths addObject: [node path]];
780      [parentPaths addObject: parentPath];
781    }
782  }
783
784  [pboard declareTypes: [NSArray arrayWithObject: NSFilenamesPboardType]
785                                           owner: nil];
786  [pboard setPropertyList: paths forType: NSFilenamesPboardType];
787
788  return YES;
789}
790
791
792//
793// NSTableView delegate methods
794//
795- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
796{
797  NSArray *selected = [self selectedObjects];
798
799  [pathViewer showComponentsOfSelection: selected];
800
801  if ([selected count]) {
802    [finder foundSelectionChanged: selected];
803  }
804}
805
806- (void)tableView:(NSTableView *)aTableView
807  willDisplayCell:(id)aCell
808   forTableColumn:(NSTableColumn *)aTableColumn
809              row:(NSInteger)rowIndex
810{
811  if (aTableColumn == nameColumn) {
812    FSNTextCell *cell = (FSNTextCell *)[nameColumn dataCell];
813    FSNode *node = [foundObjects objectAtIndex: rowIndex];
814
815    [cell setIcon: [[FSNodeRep sharedInstance] iconOfSize: 24 forNode: node]];
816
817  } else if (aTableColumn == dateColumn) {
818    [(FSNTextCell *)[dateColumn dataCell] setDateCell: YES];
819  }
820}
821
822- (void)tableView:(NSTableView *)tableView
823            mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn
824{
825  NSString *newOrderStr = [tableColumn identifier];
826  FSNInfoType newOrder = FSNInfoNameType;
827
828  if ([newOrderStr isEqual: @"name"]) {
829    newOrder = FSNInfoNameType;
830  } else if ([newOrderStr isEqual: @"parent"]) {
831    newOrder = FSNInfoParentType;
832  } else if ([newOrderStr isEqual: @"kind"]) {
833    newOrder = FSNInfoKindType;
834  } else if ([newOrderStr isEqual: @"date"]) {
835    newOrder = FSNInfoDateType;
836  } else if ([newOrderStr isEqual: @"size"]) {
837    newOrder = FSNInfoSizeType;
838  }
839
840  if (newOrder != currentOrder) {
841    currentOrder = newOrder;
842
843    [self updateShownData];
844  }
845
846  [tableView setHighlightedTableColumn: tableColumn];
847}
848
849// ResultsTableView
850- (NSImage *)tableView:(NSTableView *)tableView
851      dragImageForRows:(NSArray *)dragRows
852{
853  if ([dragRows count] > 1) {
854    return [[FSNodeRep sharedInstance] multipleSelectionIconOfSize: 24];
855  } else {
856    int index = [[dragRows objectAtIndex: 0] intValue];
857    FSNode *node = [foundObjects objectAtIndex: index];
858
859    return [[FSNodeRep sharedInstance] iconOfSize: 24 forNode: node];
860  }
861
862  return nil;
863}
864
865@end
866
867
868@implementation ProgressView
869
870#define IMAGES 8
871
872- (void)dealloc
873{
874  RELEASE (images);
875  [super dealloc];
876}
877
878- (id)initWithFrame:(NSRect)frameRect
879    refreshInterval:(NSTimeInterval)refresh
880{
881  self = [super initWithFrame: frameRect];
882
883  if (self) {
884    unsigned i;
885
886    images = [NSMutableArray new];
887
888    for (i = 0; i < IMAGES; i++) {
889      NSString *imname = [NSString stringWithFormat: @"anim-logo-%d.tiff", i];
890      [images addObject: [NSImage imageNamed: imname]];
891    }
892
893    rfsh = refresh;
894    animating = NO;
895  }
896
897  return self;
898}
899
900- (void)start
901{
902  index = 0;
903  animating = YES;
904  progTimer = [NSTimer scheduledTimerWithTimeInterval: rfsh
905		       target: self selector: @selector(animate:)
906		       userInfo: nil repeats: YES];
907}
908
909- (void)stop
910{
911  animating = NO;
912  if (progTimer && [progTimer isValid]) {
913    [progTimer invalidate];
914  }
915  [self setNeedsDisplay: YES];
916}
917
918- (void)animate:(id)sender
919{
920  [self setNeedsDisplay: YES];
921  index++;
922  if (index == [images count]) {
923    index = 0;
924  }
925}
926
927- (void)drawRect:(NSRect)rect
928{
929  [super drawRect: rect];
930
931  if (animating) {
932    [[images objectAtIndex: index] compositeToPoint: NSMakePoint(0, 0)
933                                          operation: NSCompositeSourceOver];
934  }
935}
936
937@end
938
939
940
941@implementation DocumentIcon
942
943- (void)dealloc
944{
945  RELEASE (icon);
946  [super dealloc];
947}
948
949- (id)initWithFrame:(NSRect)frameRect
950       searchResult:(id)sres
951{
952  self = [super initWithFrame: frameRect];
953
954  if (self) {
955    ASSIGN (icon, [NSImage imageNamed: @"DragableDocument"]);
956    searchResult = sres;
957  }
958
959  return self;
960}
961
962- (void)mouseDown:(NSEvent *)theEvent
963{
964  NSEvent *nextEvent;
965  BOOL startdnd = NO;
966  int dragdelay = 0;
967
968
969  if ([theEvent clickCount] == 1)
970    {
971      while (1)
972	{
973	  nextEvent = [[self window] nextEventMatchingMask:
974				       NSLeftMouseUpMask | NSLeftMouseDraggedMask];
975
976	  if ([nextEvent type] == NSLeftMouseUp)
977	    {
978	      [[self window] postEvent: nextEvent atStart: NO];
979	      break;
980
981	    }
982	  else if ([nextEvent type] == NSLeftMouseDragged)
983	    {
984	      if (dragdelay < 5)
985		{
986		  dragdelay++;
987		}
988	      else
989		{
990		  startdnd = YES;
991		  break;
992		}
993	    }
994	}
995
996      if (startdnd == YES)
997	{
998	[self startExternalDragOnEvent: theEvent];
999	}
1000    }
1001}
1002
1003- (void)drawRect:(NSRect)rect
1004{
1005  [super drawRect: rect];
1006  [icon compositeToPoint: NSMakePoint(2, 2)
1007               operation: NSCompositeSourceOver];
1008}
1009
1010- (void)startExternalDragOnEvent:(NSEvent *)event
1011{
1012  NSPasteboard *pb = [NSPasteboard pasteboardWithName: NSDragPboard];
1013  NSArray *dndtypes = [NSArray arrayWithObject: @"GWLSFolderPboardType"];
1014  NSMutableDictionary *pbDict = [NSMutableDictionary dictionary];
1015  NSData *pbData = nil;
1016
1017  [pb declareTypes: dndtypes owner: nil];
1018
1019  [pbDict setObject: [NSArray arrayWithObject: lsfname]
1020             forKey: @"paths"];
1021  [pbDict setObject: [NSNumber numberWithUnsignedLong: [searchResult memAddress]]
1022             forKey: @"sender"];
1023
1024  pbData = [NSArchiver archivedDataWithRootObject: pbDict];
1025  [pb setData: pbData forType: @"GWLSFolderPboardType"];
1026
1027  [self dragImage: icon
1028               at: NSZeroPoint
1029           offset: NSZeroSize
1030            event: event
1031       pasteboard: pb
1032           source: self
1033        slideBack: YES];
1034}
1035
1036- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
1037{
1038  return NSDragOperationAll;
1039}
1040
1041- (BOOL)ignoreModifierKeysWhileDragging
1042{
1043  return YES;
1044}
1045
1046- (void)draggedImage:(NSImage *)anImage
1047	     endedAt:(NSPoint)aPoint
1048           deposited:(BOOL)flag
1049{
1050  [self setNeedsDisplay: YES];
1051}
1052
1053@end
1054
1055