/*
**  MyLibrary.m
**
**  Copyright (c) 2004
**
**  Author: Yen-Ju Chen <yjchenx@hotmail.com>
**
**  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 "MyLibrary.h"
#include "GNUstep.h"
#include "Constants.h"
#include "GeneralContainer+Additions.h"
#include "Sources.h"
#include "LibrarySource.h"
#include "FolderSource.h"
#include "SmartFolderSource.h"
#include "SmartFolderEditor.h"
#include "SearchSource.h"
#include "AttributesSource.h"
#include "AttributesViewer.h"
#include "IconTableView.h"
#include "ItemTableView.h"
#include "NoteTextView.h"
#include "CriterionView.h"
#include "Criterion.h"
#include "ImportFilePanel.h"
#include "SummaryViewer.h"
#include "ContentIndexer.h"
#include "LibraryLocationPanel.h"
#include "TimeStamp.h"

#define MakeTimeStamp \
  [[TimeStamp timeStampWithIdentifier: LibraryEditedTimeStamp] setTime: [NSDate date]]

@implementation MyLibrary

- (SourceType) _typeOfSelectedSource
{
  return [sources typeOfSourceAtIndex: [sourceTableView selectedRow]];
}

- (void) _removeItemWithUniqueNumber: (unsigned int) uid
{
  MakeTimeStamp;

  unsigned int index;
 
  index = [librarySource indexOfUniqueNumber: uid];
  if (index != NSNotFound) 
    {
      [librarySource removeObjectAtIndex: index];
    }

  index = [sources indexOfUniqueNumber: uid];
  if (index != NSNotFound) 
    {
      [sources removeObjectAtIndex: index];
    }

  /* replace title in sources */
  unsigned int i, icount = [sources count];
  unsigned int j;
  SourceType type;
  id container;
  for(i = 0; i < icount; i++)
    {
      type = [sources typeOfSourceAtIndex: i];
      switch (type)
        {
	  case SourceFolderType:
	    {
	      container = [sources containerAtIndex: i];
	      j = [container indexOfUniqueNumber: uid];
	      [container removeObjectAtIndex: j];
	      continue;
	    }
	}
    }
}

/* This methods will change the order of sources
 * because it is automatically sorted by name.
 * Only for replace item title, not other sources.
 */
- (void) _replaceTitle: (NSString *) new
          forItemWithUniqueNumber: (unsigned int) uid
{
  unsigned int index;
  MakeTimeStamp;
 
  index = [librarySource indexOfUniqueNumber: uid];
  NSString *old = [librarySource titleAtIndex: index];
  if (index != NSNotFound) 
    {
      [librarySource setTitle: new atIndex: index];
    }

  index = [sources indexOfUniqueNumber: uid];
  if (index != NSNotFound) 
    {
      [sources setTitle: new atIndex: index];
    }

  /* replace title in sources */
  unsigned int i, icount = [sources count];
  unsigned int j;
  SourceType type;
  id container;
  for(i = 0; i < icount; i++)
    {
      type = [sources typeOfSourceAtIndex: i];
      switch (type)
        {
	  case SourceFolderType:
	    {
	      //NSLog(@"replace title in Folder %@", old);
	      container = [sources containerAtIndex: i];
	      j = [container indexOfUniqueNumber: uid];
	      [container setTitle: new atIndex: j];
	      continue;
	    }
	}
    }

  /* replace all links */
  [librarySource replaceContentOfLink: old withString: new];
}

- (void) _selectItemWithUniqueNumber: (unsigned int) uid
{
  unsigned int index = [librarySource indexOfUniqueNumber: uid];
  if (index == NSNotFound) return;

  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
	         byExtendingSelection: NO];
}

- (void) _displaySelectedNote
{
  if ([itemTableView numberOfSelectedRows] != 1)
    {
      [noteTextView setString: @""];
      return;
    }

  unsigned int index = [itemTableView selectedRow];
  SourceType type = [self _typeOfSelectedSource];
  if ((type == SourceFolderType) ||
      (type == SourceSmartFolderType) ||
      (type == SourceSearchType))
    {
      id container = [sources containerAtIndex: [sourceTableView selectedRow]];
      index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
    }

  if ([noteTextView isNoteTextViewEditable] == YES)
    {
      NSString *note = [librarySource noteAtIndex: index];
      if (note)
        [noteTextView setString: note];
      else
       [noteTextView setString: @""];
    }
  else
    {
      NSAttributedString *as;
      NS_DURING 
        as = [librarySource renderedNoteAtIndex: index];
      NS_HANDLER
      // Should handle exception
        as = nil;
      NS_ENDHANDLER
      if (as)
        [[noteTextView textStorage] setAttributedString: as];
      else
        {
          /* Try summary */
          as = [librarySource summaryAtIndex: index
  	                   maximalAttributes: -1
	 	 	               style: SummaryBriefStyle];
          if (as)
	    [[noteTextView textStorage] setAttributedString: as];
          else
            [noteTextView setString: @""];
	}
    }
}

- (void) _saveSelectedNote
{
  if ([itemTableView numberOfSelectedRows] != 1)
    return;

  if ([noteTextView isNoteTextViewEditable] == NO)
    return;
  
  unsigned int index = [itemTableView selectedRow];
  SourceType type = [self _typeOfSelectedSource];
  if ((type == SourceFolderType) ||
      (type == SourceSmartFolderType) ||
      (type == SourceSearchType))
    {
      id container = [sources containerAtIndex: [sourceTableView selectedRow]];
      index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
    }

  //NSLog(@"_saveSelectedNote **%@**", [noteTextView string]);
  MakeTimeStamp;
  [librarySource setNote: [noteTextView string]
	                        atIndex: index];
}

- (void) _updateAvailableSelectionsForSource
{
  unsigned int i, count = [sources count];
  SourceType sourceType;
  NSString *title;
  NSMutableArray *selections = [[NSMutableArray alloc] init];
  /* go through all sources */
  for(i = 0; i < count; i++)
    {
      sourceType = [sources typeOfSourceAtIndex: i];
      switch(sourceType)
        {
  	  case SourceSmartFolderType:
	  case SourceFolderType:
	    title = [sources titleAtIndex: i];
	    [selections addObject: title];
	    break;
	  case SourceLibraryType:
	  case SourceSearchType:
	    /* Ignore these type */
  	    break;
        }
    }
  [searchCriterion setAvailableSelections: selections
	                       forSubject: sSource];
  RELEASE(selections);
  [searchView reloadSelections];
}

- (void) _updateAllAvailableSubjectsInSearchCriterion
{
  addAvailableSubjectsIntoCriterion (searchCriterion);
  [self _updateAvailableSelectionsForSource];
  [searchView reloadSubjects];
}

- (BOOL) _loadLibraryAtPath: (NSString *) path
{
  if ([[LibrarySource sharedSource] loadSourceAtPath: [path stringByAppendingPathComponent: LibraryPlist]] == NO)
    {
      return NO;
    }

  if ([[AttributesSource sharedSource] loadSourceAtPath: [path stringByAppendingPathComponent: AttributesPlist]] == NO)
    {
      return NO;
    }

  if ([[Sources sharedSources] loadSourceAtPath: [path stringByAppendingPathComponent: SourcePlist]] == NO)
    {
      return NO;
    }

  MakeTimeStamp;

  /* Refresh table view */
  [sourceTableView reloadData];
  [itemTableView reloadData];
  [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
	           byExtendingSelection: NO];
  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
	         byExtendingSelection: NO];
  
  /* back and forward stack */
  [stack removeAllObjects];
  [stack addObject: [NSNumber numberWithUnsignedInt: [librarySource uniqueNumberAtIndex: 0]]];
  stack_index = [stack count]-1;

  /* display note */
  [self _displaySelectedNote];
  /* update all attributes */
  [self _updateAllAvailableSubjectsInSearchCriterion];

  NSString *em = [NSString stringWithUTF8String: "\xe2\x80\x94"];
  NSString *title = [NSString stringWithFormat: @"%@  %@  %@",
                          [path lastPathComponent], em,
			  [path stringByDeletingLastPathComponent]];
  [mainWindow setTitle: title];

  return YES;
}

- (BOOL) _unloadLibrary
{
  /* Save library */
  if ([[AttributesSource sharedSource] unloadSourceAtPath: nil] == NO)
    {
      return NO;
    }
  if ([[LibrarySource sharedSource] unloadSourceAtPath: nil] == NO)
    {
      return NO;
    }
  if ([[Sources sharedSources] unloadSourceAtPath: nil] == NO)
    {
      return NO;
    }
  return YES;
}

/* Summary view */

- (void) styleDidChangeInSummaryViewer: (id) sender
{
  [self summaryViewerAction: self];
}

/* Criterion View */

- (void) criterionDidChangeInView: (CriterionView *) view
{
  //NSLog(@"Criterion did change %@", view);
  unsigned int index, count, total;
  MakeTimeStamp;
  if (searchSource == nil)
    {
      ASSIGN(searchSource, AUTORELEASE([[SearchSource alloc] init]));
      /* add into sources */
      [sources newSource: sSearch ofType: SourceSearchType];
      /* Create folder container */
      index = [sources indexOfTitle: sSearch];
      [sources setContainer: searchSource atIndex: index];
      [sourceTableView reloadData];
    }
  else
    {
      index = [sources indexOfTitle: sSearch];
    }
  [searchSource setCriterion: searchCriterion];
  [searchSource refreshAll];
  count = [searchSource count];
  total = [librarySource count];
  if ((count > 0) && (count < total))
    {
      [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
                        byExtendingSelection: NO];
    }
  [itemTableView reloadData];
  [searchToolbarItem setLabel: [NSString stringWithFormat: @"%d of %d", count, total]];
}

- (void) criterionViewProposeResizing: (CriterionView *) view
                               toSize: (NSSize) proposedSize
{
  [searchToolbarItem setMinSize: proposedSize];
  [searchToolbarItem setMaxSize: proposedSize];
}


/* Toolbar */

- (void) toolbarWillAddItem: (NSNotification *) not
{
  if ([[not userInfo] objectForKey: @"item"] == searchToolbarItem)
    {
      NSSize size = [searchToolbarItem minSize];
      [searchToolbarItem setMinSize: NSMakeSize(500, size.height)];
      [searchToolbarItem setMaxSize: NSMakeSize(2048, size.height)];
      //[(CriterionView *)[searchToolbarItem view] reloadData];
    }
}

- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) bar
{
  return [NSArray arrayWithObjects: SearchToolbarItemIdentifier,
                                    nil];
}

- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) bar
{
  return [NSArray arrayWithObjects: SearchToolbarItemIdentifier,
                                    nil];
}

- (NSToolbarItem *) toolbar: (NSToolbar *) bar
                    itemForItemIdentifier: (NSString *) itemIdentifier
		    willBeInsertedIntoToolbar: (BOOL) flag
{
  float optimal_height;
  //NSLog(@"%@, item %@, inserted %d", bar, itemIdentifier, flag);
  if ([itemIdentifier isEqualToString: SearchToolbarItemIdentifier])
    {
      searchToolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier: SearchToolbarItemIdentifier];
      searchView = [[CriterionView alloc] initWithFrame: NSMakeRect(0, 0, 400, 64)];
      [searchView setBordered: NO];
      [searchView setSmallControlSize: YES];
#ifdef GNUSTEP
      [searchView setMaxCriterions: 1];
#else
      [searchView setMaxCriterions: 10];
#endif
      [searchView setDelegate: self];
      [searchView setDataSource: searchCriterion];
      [searchView reloadData];
      optimal_height = [searchView optimalHeight];
      //NSLog(@"optimal height %f", optimal_height);
      [searchView setFrame: NSMakeRect(0, 0, 400, optimal_height)];
      [searchToolbarItem setMinSize: NSMakeSize(400, optimal_height)];
      [searchToolbarItem setMaxSize: NSMakeSize(800, optimal_height)];
      [searchToolbarItem setLabel: sSearch];
      [searchToolbarItem setPaletteLabel: sSearch];
      [searchToolbarItem setView: searchView];
      RELEASE(searchView);
      return AUTORELEASE(searchToolbarItem);
    }

  return nil;
}

/* text view delegate */
- (BOOL) textView: (NSTextView *) textView clickedOnLink: (id) link
                                           atIndex: (unsigned int) charIndex
{
  NSRange range;
  NSString *string;
  id attribute =[[textView textStorage] attribute: NSLinkAttributeName
                                          atIndex: charIndex
                                   effectiveRange: &range];
  if (attribute == nil)
    {
      NSLog(@"Error: no link");      
      return NO;
    }
  else if ([attribute isKindOfClass: [NSURL class]])
    {
      string = [[[textView string] substringWithRange: range] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
      return [[NSWorkspace sharedWorkspace] openURL: attribute];
    }
  else if ([attribute isKindOfClass: [NSString class]])
    {
      if ([attribute isEqualToString: @"Link"])
        {
          string = [[[textView string] substringWithRange: range] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
	  // Create new page if not existed
	  unsigned int index = [librarySource indexOfTitle: string];
	  if (index == NSNotFound)
	    {
	      /* Create new note */
      	      //NSLog(@"Create page \"%@\"", string);
	      index = [librarySource newItemWithTitle: string];
	      [itemTableView reloadData];
	    }
	  unsigned int uid = [librarySource uniqueNumberAtIndex: index];
	  [self _selectItemWithUniqueNumber: uid];
	  return YES;
	}
      else if ([attribute hasPrefix: @"<"])
        {
          NSLog(@"Email to %@", attribute);
	  return YES;
        }
      else /* Unknown link */
        return NO;
    }  
  return NO;
}

/* table view delegate */

- (int) numberOfRowsInTableView: (NSTableView *) tableView
{
  if (tableView == sourceTableView)
    {
      return [sources count];
    }
  else /* itemTableView */
    {
      unsigned int index = [sourceTableView selectedRow];
      SourceType type = [sources typeOfSourceAtIndex: index];
      switch (type)
        {
  	  case SourceLibraryType:
  	  case SourceNoteType:
            {
              return [librarySource count];
	    }
	  case SourceFolderType:
	  case SourceSmartFolderType:
	  case SourceSearchType:
	    {
	      return [[sources containerAtIndex: index] count];
	    }
	  default: 
	    return 0;
	}
    }
}

- (id) tableView: (NSTableView *) tableView
       objectValueForTableColumn: (NSTableColumn *) column
       row: (int) row
{
  if (tableView == sourceTableView)
    {
      return [sources titleAtIndex: row];
    }
  else /* itemTableView */
    {
      unsigned int index = [sourceTableView selectedRow];
      SourceType type = [sources typeOfSourceAtIndex: index];
      switch (type)
        {
	  case SourceLibraryType:
	  case SourceNoteType:
            {
              if ([[column identifier] isEqualToString: ItemIdentifier])
                {
	          return [librarySource titleAtIndex: row];
                }
              else if ([[column identifier] isEqualToString: TypeIdentifier])
                {
                  return [librarySource typeStringOfItemAtIndex: row];
                }
	    }
	  case SourceFolderType:
	  case SourceSmartFolderType:
	  case SourceSearchType:
	    {
	      id container = [sources containerAtIndex: index];
	      unsigned int uid = [container uniqueNumberAtIndex: row];
              if ([[column identifier] isEqualToString: ItemIdentifier])
                {
	          return [container titleAtIndex: row];
                }
              else if ([[column identifier] isEqualToString: TypeIdentifier])
                {
		  unsigned int index = [librarySource indexOfUniqueNumber: uid];
                  return [librarySource typeStringOfItemAtIndex: index];
                }
	    }
	}
    }
  return @"";
}

- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object
         forTableColumn: (NSTableColumn *) column row: (int) rowIndex
{
  if (tableView == itemTableView)
    {
      if ([[column identifier] isEqualToString: TypeIdentifier]) return;

      unsigned int itemIndex, sourceIndex;
      unsigned int uid;
      id container;
      SourceType type = [self _typeOfSelectedSource];
      if ((type == SourceFolderType) ||
	  (type == SourceSmartFolderType) ||
	  (type == SourceSearchType))
        {
	  container = [sources containerAtIndex: [sourceTableView selectedRow]];
        }
      else
        {
	  container = librarySource;
	}
      uid = [container uniqueNumberAtIndex: rowIndex];
      NSString *originalTitle = [container titleAtIndex: rowIndex];
      if ([originalTitle isEqualToString: object]) return;

      itemIndex = [librarySource indexOfUniqueNumber: uid];
      sourceIndex = [sources indexOfTitle: originalTitle];
      /* Check duplication */
      if (([librarySource isDuplicatedTitle: object skipIndex: itemIndex]) ||
	  ([sources isDuplicatedTitle: object skipIndex: sourceIndex]))
        {
	  int columnIndex = [itemTableView columnWithIdentifier: [column identifier]];
	  NSRunAlertPanel(sTitleError, sDuplicatedTitle, sOK, nil, nil, nil);
	  [itemTableView editColumn: columnIndex row: rowIndex
		          withEvent: nil select: YES];
	  return;
        }

      /* Change title */
      [self _replaceTitle: object forItemWithUniqueNumber: uid];
      if ((type == SourceSmartFolderType) ||
	  (type == SourceSearchType))
        {
	  if (container)
	    [container refreshAll];
	}
      [sourceTableView reloadData];
      [itemTableView reloadData];
    }
  else if (tableView == sourceTableView)
    {
      NSString *originalTitle = [sources titleAtIndex: rowIndex];
      if ([originalTitle isEqualToString: object]) return;

      /* Check duplication */
      unsigned int uid = [sources uniqueNumberAtIndex: rowIndex];
      unsigned int itemIndex;
      if (uid != NSNotFound)
        {
          itemIndex = [librarySource indexOfUniqueNumber: uid];
	}
      else
        {
	  /* Not an item. Should not exist in library */
	  itemIndex = NSNotFound;
        }
      //NSLog(@"New Title \"%@\", %d %d", object, rowIndex, itemIndex);
      if (([sources isDuplicatedTitle: object skipIndex: rowIndex]) ||
          ([librarySource isDuplicatedTitle: object skipIndex: itemIndex]))
        {
	  NSRunAlertPanel(sTitleError, sDuplicatedTitle, sOK, nil, nil, nil);
	  [sourceTableView editColumn: 0 row: rowIndex
		          withEvent: nil select: YES];
	  return;
        }

      /* Change title */
      if (uid != NSNotFound)
        {
          [self _replaceTitle: object forItemWithUniqueNumber: uid];
	}
      else
        {
	  MakeTimeStamp;
          [sources setTitle: object atIndex: rowIndex];
        }
      /* _replaceTitle change the order of sources.
       * Need to know the new index
       */
      unsigned int index = [sources indexOfTitle: object];
      SourceType sourceType = [sources typeOfSourceAtIndex: index];
      switch(sourceType)
        {
	  case SourceFolderType:
	  case SourceSmartFolderType:
	    /* update resource, too */
	    [sources setResourcePath: [object stringByAppendingPathExtension: @"plist"]
	  	     atIndex: index];
	    /* for criterion */
            [self _updateAvailableSelectionsForSource];
	    break;
	  case SourceSearchType:
	    /* Becomre SmartFolder */
	    [searchSource becomeSmartFolder: YES];
	    [sources setType: SourceSmartFolderType atIndex: index];
	    [sources setResourcePath: [object stringByAppendingPathExtension: @"plist"]
	  	     atIndex: index];
	    DESTROY(searchSource);
	    break;
	}
      [sourceTableView reloadData];
      [itemTableView reloadData];
    }
}

- (id) tableView: (NSTableView *) tableView
       iconForTableColumn: (NSTableColumn *) column
       row: (int) row
       size: (NSSize) size
{
  if ([tableView tag] == SourceTableViewTag)
    {
      int iconSize = 12;;
      SourceType type = [sources typeOfSourceAtIndex: row];
      if (size.height >= 14) iconSize = 14;;

      switch (type)
        {
	  case SourceLibraryType:
            return [NSImage imageNamed: [NSString stringWithFormat: @"Home%d.png", iconSize]];
	  case SourceNoteType:
	    return [NSImage imageNamed: [NSString stringWithFormat: @"Page%d.png", iconSize]];
	  case SourceFolderType:
	    return [NSImage imageNamed: [NSString stringWithFormat: @"Folder%d.png", iconSize]];
	  case SourceSmartFolderType:
	    return [NSImage imageNamed: [NSString stringWithFormat: @"SmartFolder%d.png", iconSize]];
	  case SourceSearchType:
	    return [NSImage imageNamed: [NSString stringWithFormat: @"Search%d.png", iconSize]];
	  default:
	    return nil;
	}
    }
#ifndef GNUSTEP
  else if ([tableView tag] == ItemTableViewTag)
    {
      SourceType sourceType = [self _typeOfSelectedSource];
      id container;
      unsigned int index = row;
      if ((sourceType == SourceFolderType) ||
	  (sourceType == SourceSmartFolderType) ||
	  (sourceType == SourceSearchType))
        {
	  container = [sources containerAtIndex: [sourceTableView selectedRow]];
	  index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: row]];
        }
      ItemType type = [librarySource typeOfItemAtIndex: index];
      switch (type)
        {
	  case ItemFileType:
	    {
	      NSString *path = [libraryPath stringByAppendingPathComponent: [librarySource pathOfItemAtIndex: index]];
	      NSImage *img = [[NSWorkspace sharedWorkspace] iconForFile: path];
	      return img;
	    }
        }
    }
#endif
  return nil;
}

- (void) tableViewSelectionDidChange: (NSNotification *) not
{
  if ([not object] == itemTableView)
    {
      if ([itemTableView numberOfSelectedRows] != 1)
        {
	  [noteTextView setNoteTextViewEditable: NO];
	  [noteTextView setString: @""];
	  return;
        }
      //NSLog(@"selection did change %d", [itemTableView selectedRow]);
      SourceType type = [self _typeOfSelectedSource];
      if ([mainWindow firstResponder] == itemTableView)
        {
	  if (type == SourceNoteType)
	    {
	      /* Move selection in source table into library */
              [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
   	                   byExtendingSelection: NO];
	    }
        }
      
      unsigned int index = [itemTableView selectedRow];;
      [itemTableView scrollRowToVisible: index];

      if ((type == SourceFolderType) ||
	  (type == SourceSmartFolderType) ||
	  (type == SourceSearchType))
        {
	  id container = [sources containerAtIndex: [sourceTableView selectedRow]];
	  index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
	}

      [self _displaySelectedNote];

      if (stackAction == NO)
        {
          /* handle back & forward stack */
          /* clean record after stack_index */
          if ( (stack_index != ([stack count]-1)) && (stack_index != NSNotFound))
            {
              NSRange range = NSMakeRange(stack_index+1, 
	 		                  [stack count]-stack_index-1);
	      [stack removeObjectsInRange: range];
 	    }
          /* add new record */
          [stack addObject: [NSNumber numberWithUnsignedInt: [librarySource uniqueNumberAtIndex: index]]];
          stack_index = [stack count]-1;
	}
    }
  else if ([not object] == sourceTableView)
    {
      unsigned int selectedItemRow = [itemTableView selectedRow];
      SourceType type = [self _typeOfSelectedSource];
      switch(type) 
        {
	  case SourceNoteType:
	    {
              [itemTableView reloadData];
	      unsigned int uid = [sources uniqueNumberAtIndex: [sourceTableView selectedRow]];
              [self _selectItemWithUniqueNumber: uid];
	      break;
	    }
	  case SourceLibraryType:
	    {
              [itemTableView reloadData];
	      if (selectedItemRow < [librarySource count])
	        {
                  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: selectedItemRow]
	                     byExtendingSelection: NO];
		}
	      else
	        {
                  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
	                     byExtendingSelection: NO];
		}
	      break;
	    }
	  case SourceFolderType:
	  case SourceSmartFolderType:
	  case SourceSearchType:
	    {
	      /* Select first item */
              id container = [sources containerAtIndex: [sourceTableView selectedRow]];
	      if (container == nil)
	        {
		  NSLog(@"Internal Error: container cannot be found");
	        }

              if (type == SourceSmartFolderType)
                [container refreshAll];

              [itemTableView reloadData];

	      if ([container count] > -1)
                {
                  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
	                     byExtendingSelection: NO];
                }
	      break;
	    }
        }
      [self _displaySelectedNote];
    }
}

- (BOOL) selectionShouldChangeInTableView: (NSTableView *) tableView
{
  if (tableView == itemTableView)
    {
      //NSLog(@"should change selection %d", [tableView selectedRow]);
      /* If the note change, refresh Smart Folder */
      if ([itemTableView numberOfSelectedRows] != 1)
        return YES;

      if ([noteTextView isNoteTextViewEditable] == NO)
        return YES;

      SourceType type = [self _typeOfSelectedSource];
      if ((type == SourceSmartFolderType) ||
          (type == SourceSearchType))
        {
          unsigned int index = [itemTableView selectedRow];
          id container = [sources containerAtIndex: [sourceTableView selectedRow]];
          index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
          NSString *oldNote = [librarySource noteAtIndex: index];
          [self _saveSelectedNote];
          NSString *newNote = [librarySource noteAtIndex: index];
          if ([oldNote isEqualToString: newNote] == NO)
	    {
	      [container refreshAll];
	      [itemTableView reloadData];
	    }
        }
      else
        {
          [self _saveSelectedNote];
	}
    }
  return YES;
}

- (void) tableView: (NSTableView *) tableView
         didDoubleClickIconAtRow: (int) row
{
  if (tableView == itemTableView)
    {
      unsigned int index = row;
      id container;
      SourceType sourceType = [self _typeOfSelectedSource];
      if ((sourceType == SourceFolderType) ||
          (sourceType == SourceSmartFolderType) ||
	  (sourceType == SourceSearchType))
        {
          container = [sources containerAtIndex: [sourceTableView selectedRow]];
	  index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: row]];
        }
      ItemType type = [librarySource typeOfItemAtIndex: index];
      if (type == ItemFileType)
        {
	  NSString *path = [librarySource fullPathOfItemAtIndex: index];
          if ([[NSWorkspace sharedWorkspace] openFile: path] == NO)
	    {
	      NSRunAlertPanel(sFileError, [NSString stringWithFormat: sCannotOpenFile_, path], sOK, nil, nil, nil);
	    }
        }
    }
}

- (BOOL) tableView: (NSTableView *) view
         shouldEditTableColumn: (NSTableColumn *) column
	 row: (int) row
{
  if (view == sourceTableView)
    {
      SourceType type = [sources typeOfSourceAtIndex: row];
      switch (type)
        {
	  case SourceLibraryType:
	  case SourceErrorType:
	  case SourceUnknownType:
	    return NO;
	  case SourceFolderType:
	  case SourceSmartFolderType:
	  case SourceNoteType:
	  case SourceSearchType:
	    return YES;
        }
    }
  return YES;
}

- (void) tableView: (NSTableView *) tableView
         mouseDownInHeaderOfTableColumn: (NSTableColumn *) column
{
  id container;

  if (tableView == itemTableView)
    {
      SourceType type = [self _typeOfSelectedSource];
      switch (type)
        {
          case SourceLibraryType:
	  case SourceNoteType:
	    container = librarySource;
	    break;
          case SourceFolderType:
          case SourceSmartFolderType:
          case SourceSearchType:
	    container = [sources containerAtIndex: [sourceTableView selectedRow]];
	    break;
	}

      if ([[column identifier] isEqualToString: ItemIdentifier])
        {
          if (titleSortingOrder == NSOrderedDescending)
            {
              [container sortTitle: NSOrderedAscending];
              titleSortingOrder = NSOrderedAscending;
            }
          else
            {
              [container sortTitle: NSOrderedDescending];
              titleSortingOrder = NSOrderedDescending;
            }
	}
      else if ([[column identifier] isEqualToString: TypeIdentifier])
        {
          if (typeSortingOrder == NSOrderedDescending)
            {
              [container sortType: NSOrderedAscending];
              typeSortingOrder = NSOrderedAscending;
            }
          else
            {
              [container sortType: NSOrderedDescending];
              typeSortingOrder = NSOrderedDescending;
            }
	}
      [itemTableView reloadData];
      [self _displaySelectedNote];
    }
}


/* Table view drag and drop */
- (NSDragOperation) tableView: (NSTableView *) tableView
                    validateDrop: (id <NSDraggingInfo>) info
		    proposedRow: (int) row
		    proposedDropOperation: (NSTableViewDropOperation) operation
{
  NSDragOperation sourceMask = [info draggingSourceOperationMask];
  
  if (nil == [info draggingSource]) // From other application
    {
      if ([tableView tag] == SourceTableViewTag) 
        {
	  /* Only allow to library */
	  SourceType type = [sources typeOfSourceAtIndex: row];
	  switch(type)
	    {
	      case SourceLibraryType:
                [sourceTableView setDropRow: row
                                 dropOperation: NSTableViewDropOn];
		break;
	      case SourceNoteType:
	      case SourceSearchType:
	      case SourceSmartFolderType:
                [sourceTableView setDropRow: row
                                 dropOperation: NSTableViewDropAbove];
		break;
	      case SourceFolderType:
		break;
	      default:
                [sourceTableView setDropRow: row
                                 dropOperation: NSTableViewDropAbove];
	    }

          if (sourceMask & NSDragOperationCopy)
            return NSDragOperationCopy;
#ifdef GNUSTEP // FIXME, weird, but work for GWorkspace
          else if (sourceMask & NSDragOperationPrivate)
            return NSDragOperationPrivate;
#endif
          else
            return NSDragOperationNone;
	}
      else
        {
          /* go to library source */
          [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
	               byExtendingSelection: NO];

          [itemTableView setDropRow: [librarySource count]
                      dropOperation: NSTableViewDropAbove];

          if (sourceMask & NSDragOperationCopy)
            return NSDragOperationCopy;
#ifdef GNUSTEP // FIXME, weird, but work for GWorkspace
          else if (sourceMask & NSDragOperationPrivate)
            return NSDragOperationPrivate;
#endif
          else
            return NSDragOperationNone;
	}
    }
  else if (itemTableView == [info draggingSource]) // From ItemTableView
    {
      if ([tableView tag] == ItemTableViewTag)
        return NSDragOperationNone;

      if (sourceMask & NSDragOperationCopy)
        {
	  if (operation == NSTableViewDropOn)
	    {
	      /* Check whether source accept drop */
	      SourceType type = [sources typeOfSourceAtIndex: row];
	      switch(type) 
	        {
		  case SourceFolderType: 
		    return NSDragOperationCopy;
		  default:
		    return NSDragOperationNone;
		}
	    }
	  else /* NSTableViewDropAbove */
	    {
              return NSDragOperationCopy; 
	    }
        }
      else
        return NSDragOperationNone;
    }
  else if (sourceTableView == [info draggingSource]) // From SourceTableView
    {
      /* only allow from note to folder */
      if ([tableView tag] == ItemTableViewTag)
        return NSDragOperationNone;

      if (sourceMask & NSDragOperationCopy)
        {
	  if (operation == NSTableViewDropOn)
	    {
	      /* Check whether source accept drop */
	      SourceType type = [sources typeOfSourceAtIndex: row];
	      switch(type) 
	        {
		  case SourceFolderType: 
		    return NSDragOperationCopy;
		  default:
		    return NSDragOperationNone;
		}
	    }
	  else /* NSTableViewDropAbove */
	    {
              return NSDragOperationNone; 
	    }
        }
      else
        return NSDragOperationNone;
    }
  else
    { 
      return NSDragOperationNone;
    }
}

- (BOOL) tableView: (NSTableView *) tableView
         acceptDrop: (id <NSDraggingInfo>) info
	 row: (int) row
	 dropOperation: (NSTableViewDropOperation) operation
{
  NSPasteboard *pboard = [info draggingPasteboard];
  NSDragOperation sourceMask = [info draggingSourceOperationMask];

  MakeTimeStamp;
  if (nil == [info draggingSource]) // From other application
    {
      NSArray *paths = [pboard propertyListForType: NSFilenamesPboardType];

#ifdef GNUSTEP // FIXME, weird, but work for GWorkspace
      if ((sourceMask & NSDragOperationCopy) ||
          (sourceMask & NSDragOperationPrivate))
#else
      if (sourceMask & NSDragOperationCopy)
#endif
        {
          /* Put files into library */
          int i, count = [paths count];
	  unsigned int index, uid;
	  NSString *path, *ratio, *file, *title;
          ImportFilePanel *panel = [ImportFilePanel sharedPanel];
          [panel setProgress: 0];
          float *progress;
	  SourceType type;
          NSModalSession session = [NSApp beginModalSessionForWindow: [panel panel]];

	  for (i = 0; i < count; i++)
	    {
	      [NSApp runModalSession: session];
	      file = [paths objectAtIndex: i];
	      [panel setRatio: [NSString stringWithFormat: @"(%d out of %d)", (i+1), count]];
	      [panel setFilename: file];
	      [panel setProgress: ((float)(i+1)/count)];
	      [[panel panel] display];
	      index = [librarySource newItemWithFile: file];

	      if (index == NSNotFound)
	        {
		   [NSApp endModalSession: session];
	           NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCorrupted_, [paths objectAtIndex: i]], sOK, nil, nil, nil);
		   session = [NSApp beginModalSessionForWindow: [panel panel]];
	           continue;
	        }

              if ([tableView tag] == SourceTableViewTag)
                { 
	          /* Only allow to library */
		  title = [librarySource titleAtIndex: index];
		  uid = [librarySource uniqueNumberAtIndex: index];
		  if (operation == NSTableViewDropAbove)
		    {
		      /* make a copy in sources */
	              if (row == [sources count])
	                {
		          [sources newSource: title ofType: SourceNoteType];
	  	        }
	              else
	                {
		          [sources newSource: title ofType: SourceNoteType
		                                   atIndex: row];
	                }
		      unsigned int sourceIndex = [sources indexOfTitle: title];
		      [sources setUniqueNumber: uid atIndex: sourceIndex];
		    }
		  else
		    {
	              type = [sources typeOfSourceAtIndex: row];
	              switch(type)
	                {
	                  case SourceFolderType:
			    {
			      /* Makd a copy in folder */
                              id container = [sources containerAtIndex: row];
		              index = [container newTitle: title];
			      [container setUniqueNumber: uid
				                 atIndex: index];
		              break;
			    }
	                  default:
			    break;
			}
	            }
	        }
	    }
	  [NSApp endModalSession: session];
	  [[panel panel] performClose: self];
          [sources sort];
          [sourceTableView reloadData];
	  [itemTableView reloadData];
	  if (index != NSNotFound)
	    {
	      [itemTableView scrollRowToVisible: index];
              [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
                             byExtendingSelection: NO];
	    }

  	  return YES;
        }
      else
        return NO;
    }
  else if (itemTableView == [info draggingSource]) // From self
    {
      /* from itemTableView to sourceTableView */
      if ([tableView tag] == ItemTableViewTag)
	return NO;

      /* use multipleItemsPboardType to test multiple items */
      NSArray *numbers = [pboard propertyListForType: MultipleItemsPboardType];
      unsigned int index;
#if 0
      if (titles == nil)
        {
	  NSString *title = [pboard stringForType: NSStringPboardType];
	  titles = [NSArray arrayWithObject: title];
        }
#endif

      if (sourceMask & NSDragOperationCopy)
        {
	  if (operation == NSTableViewDropAbove)
	    {
	      NSNumber *number;
	      NSString *title;
	      unsigned int itemIndex, uid;
	      NSEnumerator *e = [numbers objectEnumerator];
	      while ((number = [e nextObject]))
	        {
	          /* Check whether item is exist */
		  uid = [number unsignedIntValue];
		  itemIndex = [librarySource indexOfUniqueNumber: uid];
		  title = [librarySource titleAtIndex: itemIndex];
	          if ([sources isDuplicatedTitle: title] == YES)
		    continue;

	          if (row == [sources count])
	            {
		      [sources newSource: title ofType: SourceNoteType];
	  	    }
	          else
	            {
		      [sources newSource: title ofType: SourceNoteType
		                               atIndex: row];
	            }
	          index = [sources indexOfTitle: title];
		  [sources setUniqueNumber: uid atIndex: index];
		}
	    }
	  else
	    {
	      SourceType type = [sources typeOfSourceAtIndex: row];
	      id container;
	      if (type == SourceFolderType)
	        {
		  NSNumber *number;
		  NSString *title;
		  unsigned int itemIndex, uid;
	          NSEnumerator *e = [numbers objectEnumerator];

                  container = [sources containerAtIndex: row];
	          while ((number = [e nextObject]))
	            {
		      uid = [number unsignedIntValue];
		      itemIndex = [librarySource indexOfUniqueNumber: uid];
		      title = [librarySource titleAtIndex: itemIndex];

		      if ([container isDuplicatedTitle: title] == NO)
		        {
		          index = [container newTitle: title];
			  [container setUniqueNumber: uid atIndex: index];
			}
		    }
	        }
	      else
	        return NO; 
	    }
	}
      [sources sort];
      [sourceTableView reloadData];
      return YES;
    }
  else if (sourceTableView == [info draggingSource]) 
    {
      /* from note to folder in sourceTableView */
      /* single selection */
      if ([tableView tag] == ItemTableViewTag)
	return NO;

      //NSString *title = [pboard stringForType: NSStringPboardType];
      NSNumber *number = [[pboard propertyListForType: MultipleItemsPboardType] objectAtIndex: 0];
      unsigned int uid = [number unsignedIntValue];
      unsigned int itemIndex = [librarySource indexOfUniqueNumber: uid];
      unsigned int index;
      NSString *title = [librarySource titleAtIndex: itemIndex];
      if (sourceMask & NSDragOperationCopy)
        {
	  if (operation == NSTableViewDropOn)
	    {
	      SourceType type = [sources typeOfSourceAtIndex: row];
	      id container = [sources containerAtIndex: row];
	      if (type == SourceFolderType)
	        {
	          if ([container isDuplicatedTitle: title] == NO)
		    {
		      index = [container newTitle: title];
		      [container setUniqueNumber: uid atIndex: index];
                      [sources sort];
                      [sourceTableView reloadData];
		      [itemTableView reloadData];
		      return YES;
		    }
	        }
	    }
	}
      return NO;
    }
  else
    { 
      return NO;
    }
}

- (BOOL) tableView: (NSTableView *) tableView 
         writeRows: (NSArray *) rows
      toPasteboard: (NSPasteboard *) pboard
{
  unsigned int index;

  if (tableView == itemTableView)
    {
      switch ([rows count]) 
        {
          case 0: return NO;
          case 1:
            {
	      /* Single selection */
	      index = [[rows objectAtIndex: 0] intValue];  
	      id container;
	      SourceType sourceType = [self _typeOfSelectedSource];
	      ItemType type;
	      NSString *title;
	      NSString *path;
	      NSString *fullPath;
	      unsigned int itemIndex;
	      unsigned int uid;

	      if ((sourceType == SourceFolderType) ||
  	          (sourceType == SourceSmartFolderType) ||
		  (sourceType == SourceSearchType))
	        {
		  container = [sources containerAtIndex: [sourceTableView selectedRow]];
	        }
	      else
	        {
		  container = librarySource;
	        }
	      title = [container titleAtIndex: index];
	      uid = [container uniqueNumberAtIndex: index];
	      itemIndex = [librarySource indexOfUniqueNumber: uid];
	      type = [librarySource typeOfItemAtIndex: itemIndex];

	      if (type == ItemFileType)
	        {
	          path  = [librarySource pathOfItemAtIndex: itemIndex];
	          fullPath = [librarySource fullPathOfItemAtIndex: itemIndex];
	          [pboard declareTypes: [NSArray arrayWithObjects: 
		                             NSStringPboardType,
	                                     NSFilenamesPboardType,
					     MultipleItemsPboardType,
					     nil]
		             owner: nil];

	          if ([pboard setPropertyList: [NSArray arrayWithObject: fullPath]
	                          forType: NSFilenamesPboardType] == NO)
	            return NO;
	        }
	      else
	        {
	          [pboard declareTypes: [NSArray arrayWithObjects: 
			                        NSStringPboardType,
		  				MultipleItemsPboardType,
						nil] 
				 owner: nil];
	        }

	      if ([pboard setString: title forType: NSStringPboardType] == NO)
	        return NO;
	      if ([pboard setPropertyList: [NSArray arrayWithObject: [NSNumber numberWithUnsignedInt: uid]]
			          forType: MultipleItemsPboardType] == NO)
		return YES;

	      return YES;
            }
          default:
	    {
	      /* Multiple selection */
	      unsigned int index;
	      ItemType type;
	      id object;
	      NSEnumerator *e = [rows objectEnumerator];
	      NSMutableString *titles = [[NSMutableString alloc] init];
	      NSMutableArray *paths = [[NSMutableArray alloc] init];
	      //NSMutableArray *itemTitles = [[NSMutableArray alloc] init];
	      NSMutableArray *itemNumbers = [[NSMutableArray alloc] init];
	      NSString *title, *path, *fullPath;
	      SourceType sourceType = [self _typeOfSelectedSource];
	      id container;
	      unsigned int uid, itemIndex;

	      while ((object = [e nextObject]))
	        {
	          index = [object intValue];
		  if ((sourceType == SourceFolderType) ||
		      (sourceType == SourceSmartFolderType) ||
		      (sourceType == SourceSearchType))
		    {
		      container = [sources containerAtIndex: [sourceTableView selectedRow]];
		    }
		  else
		    {
		      container = librarySource;
		    }
		  title = [container titleAtIndex: index];
		  uid = [container uniqueNumberAtIndex: index];
		  itemIndex = [librarySource indexOfUniqueNumber: uid];
		  type = [librarySource typeOfItemAtIndex: itemIndex];

	          [titles appendFormat: @"\"%@\",", title];
	          //[itemTitles addObject: title];
		  [itemNumbers addObject: [NSNumber numberWithUnsignedInt: uid]];
	          if (type == ItemFileType)
	            {
	              path = [librarySource pathOfItemAtIndex: itemIndex];
	              fullPath = [librarySource fullPathOfItemAtIndex: itemIndex];

	              [paths addObject: fullPath];
	  	    }
	        }

	      if ([paths count])
	        {
	          [pboard declareTypes: [NSArray arrayWithObjects: 
		                             NSStringPboardType,
	                                     NSFilenamesPboardType,
					     MultipleItemsPboardType,
					     nil]
	 	                 owner: nil];
                  if ([pboard setPropertyList: AUTORELEASE(paths)
  	                      forType: NSFilenamesPboardType] == NO)
	            {
	              /* Clean up */
	              RELEASE(titles);
		      //RELEASE(itemTitles);
		      RELEASE(itemNumbers);
                      return NO;
	            }
	        }
	      else
	        {
	          [pboard declareTypes: [NSArray arrayWithObjects: 
		                             NSStringPboardType,
	      				     MultipleItemsPboardType,
					     nil]
		  	         owner: nil];
	        }

             if ([pboard setString: AUTORELEASE(titles) 
	 	           forType: NSStringPboardType] == NO)
	       {
 	         //RELEASE(itemTitles);
		 RELEASE(itemNumbers);
                 return NO;
	       }
	     if ([pboard setPropertyList: itemNumbers /*itemTitles  */
			     forType: MultipleItemsPboardType] == NO)
	       {
                 return NO;
	       }
             return YES;
	    }
	}
    }
  else if (tableView == sourceTableView)
    {
      /* Single selection */
      index = [[rows objectAtIndex: 0] intValue];  
      SourceType type;
      NSString *title;
      unsigned int uid;

      title = [sources titleAtIndex: index];
      uid = [sources uniqueNumberAtIndex: index];
      type = [sources typeOfSourceAtIndex: index];

      if ((type == SourceLibraryType) ||
          (type == SourceFolderType) ||
	  (type == SourceSmartFolderType) ||
	  (type == SourceSearchType))
        {
          return NO;
	}
      [pboard declareTypes: [NSArray arrayWithObjects: NSStringPboardType,
      						       MultipleItemsPboardType,
						       nil] 
	             owner: nil];

      if ([pboard setString: title forType: NSStringPboardType] == NO)
        return NO;
      if ([pboard setPropertyList: [NSArray arrayWithObject: [NSNumber numberWithUnsignedInt: uid]]
		      forType: MultipleItemsPboardType] == NO)
        return NO;
      return YES;
    }
}

/* Split limit */
- (float) splitView: (NSSplitView *) sender
          constrainMinCoordinate: (float) proposedMin
	  ofSubviewAt: (int) offset
{
  if ((sender == sourceSplitView) && (proposedMin < 200.0))
    return 100.0;
  return proposedMin;
}

/* application delegate */
- (BOOL) validateMenuItem: (NSMenuItem *) item
{
  SourceType type = [self _typeOfSelectedSource];
  NSString *title = [item title];
  if (([title isEqualToString: sIndexingMenu]) &&
      (type != SourceLibraryType))
    return NO;

  if (([title isEqualToString: sEditSmartFolderMenu]) &&
      (type != SourceSmartFolderType))
    return NO;

  if (([title isEqualToString: sRemoveMenu]) &&
      (type == SourceSmartFolderType) &&
      ([mainWindow firstResponder] != sourceTableView))
    return NO;

  return YES;
}

- (void) awakeFromNib
{
  ASSIGN(sources, [Sources sharedSources]);
  ASSIGN(librarySource, [LibrarySource sharedSource]);
  ASSIGN(attributesSource, [AttributesSource sharedSource]);

  [sourceTableView setTag: SourceTableViewTag];
  [itemTableView setTag: ItemTableViewTag];
  [noteTextView setTag: NoteTextViewTag];

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  BOOL editable = [defaults boolForKey: NoteTextViewEditable];
  [noteTextView setNoteTextViewEditable: editable];

#ifdef GNUSTEP
  [noteTextView registerForDraggedTypes: [NSArray arrayWithObjects: NSStringPboardType, nil]];
#endif
  [sourceTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSStringPboardType, MultipleItemsPboardType, nil]];
  [itemTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];

  titleSortingOrder = NSOrderedDescending;
  typeSortingOrder = NSOrderedDescending;

  /* set up toolbar */
  toolbar = [(NSToolbar *)[NSToolbar alloc] initWithIdentifier: SearchToolbarIdentifier];
  [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
  [toolbar setAllowsUserCustomization: NO];
  [toolbar setDelegate: self];
  [mainWindow setToolbar: toolbar];

  BOOL isVisible = [defaults boolForKey: SearchBarIsVisible];
  [toolbar setVisible: isVisible];
}

- (void) applicationWillFinishLaunching: (NSNotification *) not
{
  /* if not terminate normally, don't save library */
  terminateNormally = YES;

  /* Check library location */
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  NSString *path = [defaults stringForKey: LibraryLocationPath];

  stack_index = NSNotFound;

  /* set up library path */
  ASSIGN(libraryPath, MyLibraryPath(path));

  /* make sure library directory is properly set up */
  BOOL isDir;

  if ([fm fileExistsAtPath: libraryPath isDirectory: &isDir])
    {
      if (isDir == NO)
        {
	  NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCannotBeCreated_, libraryPath], sQuit, nil, nil, nil);
          terminateNormally = NO;
          [NSApp terminate: self];
        }
    }
  else
    {
      int result = NSRunAlertPanel(sCreateLibrary, [NSString stringWithFormat: sLibraryWillBeCreated_, libraryPath], sOK, sCancelAndQuit, nil, nil);
      if (result != NSAlertDefaultReturn)
        {
          terminateNormally = NO;
          [NSApp terminate: self];
        }
      if ([fm createDirectoryAtPath: libraryPath attributes: nil] == NO)
        {
	  NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCannotBeCreated_, libraryPath], sQuit, nil, nil, nil);
          terminateNormally = NO;
          [NSApp terminate: self];
        }
    }
}

- (void) applicationDidFinishLaunching: (NSNotification *) not
{
  if ([self _loadLibraryAtPath: libraryPath] == NO)
    {
      terminateNormally = NO;
      [NSApp terminate: self];
    }
}

- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
{
  if (terminateNormally == YES)
    {
      [self _saveSelectedNote];

      if ([self _unloadLibrary] == NO)
        {
	  return NSTerminateCancel;
        }
    }

  /* syn user defaults */
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  [defaults setObject: MyLibraryPath(nil) forKey: LibraryLocationPath];
  [defaults synchronize];

  return NSTerminateNow;
}

- (void) applicationWillTerminate: (NSNotification *) not
{
}

/* menu actions */

- (void) mainLibraryAction: (id) sender
{
  [mainWindow makeKeyAndOrderFront: sender];
}

- (void) attributesViewerAction: (id) sender
{
  id container = nil;
  SourceType type = [self _typeOfSelectedSource];
  unsigned int index = [itemTableView selectedRow];
  if (index == NSNotFound) return;
  /* Only select one item */
  [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
	         byExtendingSelection: NO];
  if ((type == SourceFolderType) ||
      (type == SourceSmartFolderType) ||
      (type == SourceSearchType))
    {
      container = [sources containerAtIndex: [sourceTableView selectedRow]];
      index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
    }
  NSString *path = [librarySource pathOfItemAtIndex: index];
  if (path == nil) return;
  AttributesViewer *viewer = [AttributesViewer sharedViewer];
#if 0
  [viewer setTitleOfViewer: [librarySource titleAtIndex: index]];
  [viewer setTitleOfAttributes: path];
#endif
  [viewer setUniqueNumber: [librarySource uniqueNumberAtIndex: index]];
  [viewer makeViewerKeyAndOrderFront: sender];

  MakeTimeStamp;
  /* refresh source */
  if (container)
    {
      [container refreshAll];
      [itemTableView reloadData];
    }
  /* Update all attributes */
  [self _updateAllAvailableSubjectsInSearchCriterion];
}

- (void) newFolderAction: (id) sender
{
  unsigned int acc = 0;
  NSString *title = @"New Folder";
  NSString *proposedTitle = @"New Folder";
  while(1)
    {
      acc++;
      if (([sources isDuplicatedTitle: proposedTitle] == YES) ||
	  ([librarySource isDuplicatedTitle: proposedTitle] == YES))
	proposedTitle = [title stringByAppendingFormat: @" %d", acc];
      else
	break;
    }
  MakeTimeStamp;
  [sources newSource: proposedTitle ofType: SourceFolderType];
  /* Create folder container */
  unsigned int index = [sources indexOfTitle: proposedTitle];
  FolderSource *folder = [[FolderSource alloc] init];
  [sources setContainer: AUTORELEASE(folder) atIndex: index];
  [sources setResourcePath: [proposedTitle stringByAppendingPathExtension: @"plist"]
	           atIndex: index];
  [sourceTableView reloadData];

  [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
	           byExtendingSelection: NO];
  [sourceTableView editColumn: 0 row: index
                   withEvent: nil select: YES];
  [self _updateAvailableSelectionsForSource];
}	

- (void) newSmartFolderAction: (id) sender
{
  unsigned int acc = 0;
  NSString *title = @"New Smart Folder";
  NSString *proposedTitle = @"New SmartFolder";
  while(1)
    {
      acc++;
      if (([sources isDuplicatedTitle: proposedTitle] == YES) ||
	  ([librarySource isDuplicatedTitle: proposedTitle] == YES))
	proposedTitle = [title stringByAppendingFormat: @" %d", acc];
      else
	break;
    }
  MakeTimeStamp;
  [sources newSource: proposedTitle ofType: SourceSmartFolderType];
  /* Create folder container */
  unsigned int index = [sources indexOfTitle: proposedTitle];
  SmartFolderSource *folder = [[SmartFolderSource alloc] init];
  [sources setContainer: AUTORELEASE(folder) atIndex: index];
  [sources setResourcePath: [proposedTitle stringByAppendingPathExtension: @"plist"]
	           atIndex: index];
  [sourceTableView reloadData];

  /* Automatically edit smart folder */
  [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
	           byExtendingSelection: NO];
  [self editSmartFolderAction: self];

  [sourceTableView editColumn: 0 row: index
                   withEvent: nil select: YES];

  [self _updateAvailableSelectionsForSource];
}

- (void) editSmartFolderAction: (id) sender
{
  unsigned int index = [sourceTableView selectedRow];
  SourceType type = [sources typeOfSourceAtIndex: index];
  if (type != SourceSmartFolderType)
    return;

  MakeTimeStamp;
  NSString *title = [sources titleAtIndex: index];
  SmartFolderSource *source = [sources containerAtIndex: index];
  SmartFolderEditor *editor = [SmartFolderEditor sharedEditor];
  [editor setTitle: title];
  [editor setCriterion: [source criterion]];
  [editor makeEditorKeyAndOrderFront: self];

  /* Update smart folder */
  //NSLog(@"Update smart folder");
  [source refreshAll];
  [itemTableView reloadData];
}

- (void) newNoteAction: (id) sender
{
  MakeTimeStamp;
  unsigned int index = [librarySource newItem];
  [sourceTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: 0]
 	           byExtendingSelection: NO];
  [itemTableView reloadData];
  if (index != NSNotFound)
    {
      [itemTableView scrollRowToVisible: index];
      [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
 	             byExtendingSelection: NO];
    }
}

- (void) editNoteAction: (id) sender
{
  if ([itemTableView numberOfSelectedRows] != 1)
    return; // Multiple selection or null selection

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

  if ([noteTextView isNoteTextViewEditable] == YES)
    {
      /* Save note first */
      [self _saveSelectedNote];
      [noteTextView setNoteTextViewEditable: NO];
      [defaults setBool: NO forKey: NoteTextViewEditable];
    }
  else
    {
      [noteTextView setNoteTextViewEditable: YES];
      [defaults setBool: YES forKey: NoteTextViewEditable];
    }
  [self _displaySelectedNote];
}

- (void) removeItemAction: (id) sender
{
  //NSLog(@"firstResponder %@", [mainWindow firstResponder]);
  MakeTimeStamp;
  if ([mainWindow firstResponder] == itemTableView)
    {
      SourceType type = [self _typeOfSelectedSource];
      switch(type)
        {
 	  case SourceLibraryType:
	  case SourceNoteType:
	    {
	      BOOL result = NSRunAlertPanel(sWarning, sSelectedItemRemoveFromHD, sOK, sCancel, nil, nil);
              if (result != NSAlertDefaultReturn)
	        {
		  return;
	        }
	    }
	}
      NSIndexSet *selectedRowIndexes = [itemTableView selectedRowIndexes];
      unsigned int maxCount = [selectedRowIndexes count];
      unsigned int *buffer = malloc(sizeof(unsigned int) * maxCount);
      unsigned int index;
      int i;
      id container;
#ifdef GNUSTEP
      container = [sources containerAtIndex: [sourceTableView selectedRow]];
      if (container == nil)
        {
          container = librarySource;
        }
      NSRange r = NSMakeRange(0, [container count]);
      unsigned int result = [selectedRowIndexes getIndexes: buffer maxCount: maxCount inIndexRange: &r];
#else
      unsigned int result = [selectedRowIndexes getIndexes: buffer maxCount: maxCount inIndexRange: NULL];
#endif
      unsigned int sourceIndex, attrIndex;
      unsigned int uid;
      NSString *title, *path, *fullPath;
      for(i = maxCount-1; i > -1; i--)
        {
          index = *(buffer+i);
          // NSLog(@"index %d", index);
	  switch (type)
	    {
	      case SourceLibraryType:
	      case SourceNoteType:
                title = [librarySource titleAtIndex: index];
		uid = [librarySource uniqueNumberAtIndex: index];
		path = [librarySource pathOfItemAtIndex: index];
		fullPath = [librarySource fullPathOfItemAtIndex: index];
		attrIndex = [attributesSource indexOfUniqueNumber: uid];
		[attributesSource removeObjectAtIndex: attrIndex];
		[self _removeItemWithUniqueNumber: uid];
		/* Try to remove empty directory if possible */
                unsigned int j, jcount = 3;
		for(j = 0; j < jcount; j++)
	  	  {
		    fullPath = [fullPath stringByDeletingLastPathComponent];
		    if ([[fm directoryContentsAtPath: fullPath] count] == 0)
		      {
	                [fm removeFileAtPath: fullPath handler: nil];
		      }
		    else
		      {
		        break;
		      }
		  }

		break;
	      case SourceFolderType:
		container = [sources containerAtIndex: [sourceTableView selectedRow]];
		[container removeObjectAtIndex: index];
		break;
	      default:
		return;
	    }
        }
      free(buffer);
      [itemTableView reloadData];
      [itemTableView deselectAll: self];
      [sourceTableView reloadData];
      [self _updateAllAvailableSubjectsInSearchCriterion];
    }
  else if ([mainWindow firstResponder] == sourceTableView)
    {
      int selectedRow = [sourceTableView selectedRow];
      SourceType type = [sources typeOfSourceAtIndex: selectedRow];
      switch (type)
        {
  	  case SourceNoteType:
	    {
	      [sources removeObjectAtIndex: selectedRow];
	      break;
	    }
	  case SourceFolderType:
	  case SourceSmartFolderType:
	    {
              int result = NSRunAlertPanel(sWarning, [NSString stringWithFormat: sRemoveFolder_, [sources titleAtIndex: selectedRow]], sOK, sCancel, nil, nil);
              if (result == NSAlertDefaultReturn)
                {
		  /* remove plist files */
		  NSString *path = [sources resourceFullPathAtIndex: selectedRow];
		  NSLog(@"delete path %@", path);
		  [fm removeFileAtPath: path handler: nil];
		  /* remove backup */
		  path = [path stringByAppendingString: @"~"];
		  [fm removeFileAtPath: path handler: nil];

	          [sources removeObjectAtIndex: selectedRow];
	          [self _updateAvailableSelectionsForSource];
                }
              break;
	    }
        }
      [sourceTableView reloadData];
    }
}

- (void) importFileAction: (id) sender
{
  int result;
  unsigned int index;
  NSOpenPanel *panel = [NSOpenPanel openPanel];

  [panel setCanChooseDirectories: YES];
  [panel setAllowsMultipleSelection:YES];
  result = [panel runModalForDirectory: nil file: nil types: nil];
  if (result == NSOKButton) 
    {
      MakeTimeStamp;
      NSArray *files = [panel filenames];
      NSString *filename;
      unsigned int i, count = [files count];
      ImportFilePanel *panel = [ImportFilePanel sharedPanel];
      [panel setProgress: 0];
      NSString *ratio;
      float *progress;
      NSModalSession session = [NSApp beginModalSessionForWindow: [panel panel]];
      for (i = 0; i < count; i++) 
        {
	  [NSApp runModalSession: session];
          filename = [files objectAtIndex: i];
	  [panel setRatio: [NSString stringWithFormat: @"(%d out of %d)", (i+1), count]];
	  [panel setFilename: filename];
	  [panel setProgress: ((float)(i+1)/count)];
	  [[panel panel] display];
	  index = [librarySource newItemWithFile: filename];
        }
      //NSLog(@"end session");
      [NSApp endModalSession: session];
      [[panel panel] performClose: self];
    }
  //NSLog(@"OK");
  [itemTableView reloadData];
  if (index != NSNotFound)
    {
      [itemTableView scrollRowToVisible: index];
      [itemTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: index]
 	             byExtendingSelection: NO];
    }
}

- (void) backAction: (id) sender
{
//  NSLog(@"stack_index %d", stack_index);
//  NSLog(@"%@", stack);
  /* set itemTableView as first responder */
  [mainWindow makeFirstResponder: itemTableView];

  if (stack_index == 0) return;
  stack_index--;
  stackAction = YES;
  unsigned int uid = [[stack objectAtIndex: stack_index] unsignedIntValue];
  [self _selectItemWithUniqueNumber: uid];
  stackAction = NO;
}

- (void) forwardAction: (id) sender
{
//  NSLog(@"stack_index %d", stack_index);
//  NSLog(@"%@", stack);
  /* set itemTableView as first responder */
  [mainWindow makeFirstResponder: itemTableView];

  if ((stack_index+1) >= [stack count]) return;
  stack_index++;
  stackAction = YES;
  unsigned int uid = [[stack objectAtIndex: stack_index] unsignedIntValue];
  [self _selectItemWithUniqueNumber: uid];
  stackAction = NO;
}

- (void) showFileAction: (id) sender
{
  unsigned int index = [itemTableView selectedRow];
  SourceType sourceType = [self _typeOfSelectedSource];
  id container;
  if ((sourceType == SourceFolderType) ||
      (sourceType == SourceSmartFolderType) ||
      (sourceType == SourceSearchType))
    {
      container = [sources containerAtIndex: [sourceTableView selectedRow]];
      index = [librarySource indexOfUniqueNumber: [container uniqueNumberAtIndex: index]];
    }
  ItemType type = [librarySource typeOfItemAtIndex: index];
  if (type == ItemFileType)
    {
      NSString *path = [librarySource fullPathOfItemAtIndex: index];
      [[NSWorkspace sharedWorkspace] selectFile: path inFileViewerRootedAtPath: @""];
    }
}

- (void) searchBarAction: (id) sender
{
  [mainWindow toggleToolbarShown: sender];
  BOOL isVisible = [toolbar isVisible];
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  [defaults setBool: isVisible forKey: SearchBarIsVisible];
}

- (void) summaryViewerAction: (id) sender
{
  SummaryViewer *viewer = [SummaryViewer sharedViewer];
  [viewer setDelegate: self];

  unsigned int index = [sourceTableView selectedRow];
  NSString *title = [sources titleAtIndex: index];
  [viewer setTitle: title];

  NSIndexSet *selectedRowIndexes = [itemTableView selectedRowIndexes];

  /* Save note */
  if (([selectedRowIndexes count] == 1) &&
      ([noteTextView isNoteTextViewEditable] == YES))
    {
      [self _saveSelectedNote];
    }

  id container;
  SummaryStyle summaryStyle = [viewer summaryStyle];
  int maxAttr;
  switch(summaryStyle)
    {
      case SummaryBriefStyle:
	maxAttr = 10;
	break;
      case SummaryRegularStyle:
	maxAttr = 20;
	break;
      case SummaryCompleteStyle:
	maxAttr = -1;
	break;
    }

  NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init];
  NSAttributedString *a;
  NSAttributedString *space = [[NSAttributedString alloc] initWithString: @"\n\n"];
  AUTORELEASE(space);
  SourceType type = [self _typeOfSelectedSource];
  switch(type)
    {
      case SourceFolderType:
      case SourceSmartFolderType:
      case SourceSearchType:
        container = [sources containerAtIndex: [sourceTableView selectedRow]];
	break;
      case SourceLibraryType:
        container = librarySource;
	break;
      default:
	return;
    }
  /* display summary of selected notes */
  unsigned int maxCount = [selectedRowIndexes count];
  unsigned int *buffer = malloc(sizeof(unsigned int) * maxCount);
  unsigned int i, uid;
  NSColor *color;
  NSRange range;
#ifdef GNUSTEP
  NSRange r = NSMakeRange(0, [container count]);
  [selectedRowIndexes getIndexes: buffer maxCount: maxCount inIndexRange: &r];
#else
  [selectedRowIndexes getIndexes: buffer maxCount: maxCount inIndexRange: NULL];
#endif
  for(i = 0; i < maxCount; i++)
    {
      index = *(buffer+i);
      uid = [container uniqueNumberAtIndex: index];
      index = [librarySource indexOfUniqueNumber: uid];
      range.location = [as length];
      a = [librarySource summaryAtIndex: index
                         maximalAttributes: maxAttr
			 style: summaryStyle];
      [as appendAttributedString: a];
      [as appendAttributedString: space];
      range.length = [as length] - range.location;
      if ((i % 2) == 0)
        {
	  color = [NSColor whiteColor];
        }
      else
        {
	  color = [NSColor colorWithCalibratedRed: 1
		                            green: 1
					     blue: 0.6
					    alpha: 1];
        }
      [as addAttribute: NSBackgroundColorAttributeName
	         value: color
		 range: range];
    }
  [viewer setSummary: as];
  AUTORELEASE(as);
  [viewer makeViewerKeyAndOrderFront: self];
}

- (void) indexingAction: (id) sender
{
  ContentIndexer *indexer = [ContentIndexer sharedIndexer];
  SourceType type = [self _typeOfSelectedSource];
  NSString *path;
  switch(type)
    {
      case SourceLibraryType:
        path = [libraryPath stringByAppendingPathComponent: ContentIndexDirectory];
	break;
      default:
	return;
    }
  [indexer setIndexLocation: path];
  [indexer makeIndexerKeyAndOrderFront: sender];
  MakeTimeStamp;
}

- (void) libraryLocationAction: (id) sender
{
  int result;
  NSString *original, *path;
  LibraryLocationPanel *locationPanel = [LibraryLocationPanel sharedPanel];  
  result = [locationPanel makePanelKeyAndOrderFront: sender];
  if (result == NSCancelButton) return;
  path = [locationPanel libraryLocationPath];
  if ([path isEqualToString: MyLibraryPath(nil)]) return;

  path = [path stringByStandardizingPath];

  /* make sure library directory is properly set up */
  BOOL isDir;

  if ([fm fileExistsAtPath: path isDirectory: &isDir])
    {
      if (isDir == NO)
        {
	  NSRunAlertPanel(sFileError, [NSString stringWithFormat: sNotValidLibrary_, path], sOK, nil, nil, nil);
	  return;
        }
    }
  else
    {
      int result = NSRunAlertPanel(sCreateLibrary, [NSString stringWithFormat: sLibraryWillBeCreated_, path], sOK, sCancel, nil, nil);
      if (result != NSAlertDefaultReturn)
        {
	  return;
        }
      if ([fm createDirectoryAtPath: path attributes: nil] == NO)
        {
	  NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCannotBeCreated_, path], sOK, nil, nil, nil);
	  return;
        }
    }
  original = [MyLibraryPath(nil) copy];
  AUTORELEASE(original);
  //NSLog(@"original %@", original);
  /* Unload current one */
  [self _unloadLibrary];
  //NSLog(@"library unloaded");
 
  /* Try to load new one */
  MyLibraryPath(path);
  if ([self _loadLibraryAtPath: path] == NO)
    {
      NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCannotBeLoaded_, path], sOK, nil, nil, nil);
      MyLibraryPath(original);
      if ([self _loadLibraryAtPath: original] == NO)
        {
          NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCannotBeLoaded_, original], sQuit, nil, nil, nil);
          terminateNormally = NO;
          [NSApp terminate: self];
        }
    }
}

/* Window methods */

- (NSString *) windowNibName
{
  return @"MyDocument";
}

/* Basic methods */

- (id) init
{
  self = [super init];

  searchCriterion = [[Criterion alloc] init];
  /* Try to get all subject */
  [searchCriterion addAvailableSubject: sTitle forType: CriterionStringType];

  if ([searchCriterion count] < 1)
    {     
      /* Add first criterion */
      [searchCriterion addCriterionWithSubject: sTitle
                                          verb: CriterionContainsVerb
                                        object: @""];
    }       

  stack = [[NSMutableArray alloc] init];
  searchSource = nil;

  ASSIGN(fm, [NSFileManager defaultManager]);

  return self;
}

- (void) dealloc
{
  RELEASE(stack);
  RELEASE(sources);
  RELEASE(librarySource);
  RELEASE(searchSource);
  RELEASE(attributesSource);
  RELEASE(toolbar);
  RELEASE(searchCriterion);
  RELEASE(fm);
  [super dealloc];
}

@end
