Logo Search packages:      
Sourcecode: adun.app version File versions  Download package

ULDatabaseBrowser.m

/* 
   Project: UL

   Copyright (C) 2006 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   This application 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 application 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
   Library General Public License for more details.
 
   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

#include <AppKit/AppKit.h>
#include "ULDatabaseBrowser.h"

@implementation ULDatabaseBrowserPath

- (id) init
{
      if(self = [super init])
      {
            path = [NSMutableArray new];
      }     
            
      return self;
}

- (void) dealloc
{
      [path release];
      [super dealloc];
}

- (void) setItem: (id)  object forLevel: (int) level
{
      //NSLog(@"Setting item %@ at level %d", object, level);

      //truncate to given level
      if([self truncateToLevel: level])
      {
            //remove the object currently at the given level
            [path removeLastObject];
      }     
      //add the new object in its place
      [self addItem: object];
}

- (BOOL) truncateToLevel: (int) value
{
      int i;

      if(value > [path count])
      {
            [NSException raise: NSInvalidArgumentException
                  format: @"Attempt to set item at level %d - current level %d", 
                  value, 
                  [self currentLevel]];
      }           
      else if(value < [path count])
      {
            //remove all objects at index higher then level
            
            for(i=[self currentLevel]; i>value; i--)
                  [path removeObjectAtIndex: i];
            
            return YES;

      }

      return NO;
}

- (void) addItem: (id) object
{
      [path addObject: object];
}

- (id) itemForLevel: (int) level
{
      return [path objectAtIndex: level];
}

- (NSArray*) currentPath
{
      return [[path copy] autorelease];
}

- (int) currentLevel
{
      return [path count] - 1;
}

- (void) clearPath
{
      [path removeAllObjects];
}

@end

@implementation ULDatabaseBrowser

- (void) deselectAllRows: (id) sender
{
      int row;
      id selectedRows;
      
      selectedRows = [browserView selectedRowIndexes];
      if([selectedRows count] == 0)
            return;

      row = [selectedRows firstIndex];
      while(row != NSNotFound)
      {
            [browserView deselectRow: row];
            row = [selectedRows indexGreaterThanIndex: row];
      }
      
      [browserView setNeedsDisplay: YES];
} 

- (id) init
{
      if(self = [super init])
      {
            isActive = NO;
            selectedSystems = [NSMutableArray new];
            selectedOptions = [NSMutableArray new];
            selectedDataSets = [NSMutableArray new];
            selectedSimulations = [NSMutableArray new];
            path = [ULDatabaseBrowserPath new];
            allowedActions = [[NSArray alloc] initWithObjects:
                              @"copy:",
                              @"cut:", 
                              @"paste:",
                              @"remove:",
                              @"import:",
                              @"export:",
                              nil];
            cut = NO;
            editedObject = nil;
            progressPanel = nil;
      }

      return self;
}

- (void) awakeFromNib
{
      [browserView setDataSource: self];
      [browserView setDelegate: self];
      [referenceList selectItemWithTitle: @"No References"];
      [[NSNotificationCenter defaultCenter]
            addObserver: self
            selector: @selector(updateView:)
            name: @"ULDatabaseInterfaceDidAddObjectNotification"
            object: nil];
      [[NSNotificationCenter defaultCenter]
            addObserver: self
            selector: @selector(updateView:)
            name: @"ULDatabaseInterfaceDidUpdateMetadataNotification"
            object: nil];
      databaseInterface = [[ULDatabaseInterface databaseInterface] retain];
      [browserView reloadData];

}

- (void) dealloc
{
      [allowedActions release];
      [path release];
      [databaseInterface release];
      [selectedDataSets release];
      [selectedSystems release];
      [selectedOptions release];
      [selectedSimulations release];
      [super dealloc];
}

- (BOOL) isActive;
{
      return isActive;
}

- (void) setActive: (BOOL) value
{
      if(!value)
            [self deselectAllRows: self];
      isActive = value;
}

- (void) updateView: (NSNotification*) aNotification
{
      if(progressPanel != nil)
      {     
            [progressPanel setProgressInfo: @"Complete"];
            sleep(1.5);
            [progressPanel endPanel];
            [progressPanel release];
            progressPanel = nil;
      }     
      [browserView reloadData];
}


- (BOOL) validateMenuItem: (NSMenuItem*) menuItem
{
      int selectedRow, itemLevel;
      
      //deselectAllRows is active when there are more than 0 rows selected
      if([menuItem action] == @selector(deselectAllRows:))
      {
            if([[browserView selectedRowIndexes] count] > 0)
                  return YES; 
            else
                  return NO;
      }           

      //selectedRow is not updated when the browser
      //collapses its view (until another selection is made). 
      //This means that if the index of the last row in the collapsed view 
      //is less than the index of the last selected row
      //calling [browserView itemAtRow: [browserView selectedRow]]
      //will crash the program (or raise an exception).
      //\note File this as a bug

      selectedRow = [browserView selectedRow];
      if(selectedRow >= [browserView numberOfRows])
            return NO;
      
      if(selectedRow == -1)
            return NO;

      if([[browserView selectedRowIndexes] count] > 1)
            return NO;

      itemLevel = [browserView levelForRow: selectedRow];
      if([menuItem action] == @selector(paste:))
      {
            if(editedObject != nil && itemLevel == 1)
                  return YES;
            else
                  return NO;
      }           
      
      //import is active when a database schema is selected
      if([NSStringFromSelector([menuItem action]) isEqual: @"import:"])
      {
            if(itemLevel == 1)
                  return YES;
            else
                  return NO;
      }           

      if([allowedActions containsObject: NSStringFromSelector([menuItem action])])
      {
            if(itemLevel == 3)
                  return YES;
            else  
                  return NO;
      }           

      return NO;
}

- (void) remove: (id) sender
{
      int row, lastRow;
      NSIndexSet* selectedRows;
      id item;

      //FIXME: Currently remove: is activated if only one object
      //is selected

      selectedRows = [browserView selectedRowIndexes];
      row = [selectedRows firstIndex];
      
      NS_DURING
      {
            while(row != NSNotFound)
            {
                  lastRow = row;
                  item = [browserView itemAtRow: row];
            
                  //remove references to this object from all the objects that
                  //generated it.
                  //FIXME: Not sure if we should remove input references.
                  //FIXME: This only works for the file system database 

                  [databaseInterface removeOutputReferencesToObjectWithID: [item objectForKey: @"Identification"]
                        ofClass: [item objectForKey:@"Class"]
                        inSchema: [item objectForKey: @"Schema"]
                        ofClient: [item objectForKey: @"DatabaseClient"]];
                        
                  [databaseInterface removeObjectOfClass: [item objectForKey:@"Class"]
                        withID: [item objectForKey: @"Identification"]
                        fromSchema: [item objectForKey: @"Schema"]
                        ofClient: [item objectForKey: @"DatabaseClient"]];
            
                  row = [selectedRows indexGreaterThanIndex: row];
            }
      }
      NS_HANDLER
      {
            NSWarnLog(@"Caught exception while attempting to remove object from client %@.",
                        [item objectForKey: @"Database"]);
            NSWarnLog(@"Item class %@. Item id %@", 
                        [item objectForKey: @"Class"],
                        [item objectForKey: @"Identification"]);
            NSWarnLog(@"Exception name %@. Reason %@. Information %@", 
                        [localException name],
                        [localException reason],
                        [localException userInfo]);
            NSRunAlertPanel(@"Error - Unable to remove selected object",
                  [localException reason],
                  @"Dismiss", 
                  nil,
                  nil);
      }
      NS_ENDHANDLER

      [browserView reloadData];
      //update selection
      if(lastRow <= [browserView numberOfRows])
            row = [browserView numberOfRows] - 1;

      [browserView selectRowIndexes: [NSIndexSet indexSetWithIndex: lastRow]
            byExtendingSelection: NO];
}

- (void) cut: (id) sender
{
      int selectedRow;
      id item;

      selectedRow = [browserView selectedRow];
      item = [browserView itemAtRow: selectedRow];

      [editedObject release];
      editedObject = [item retain];
      NSLog(@"Edited obejct %@", editedObject);
      cut = YES;
}

- (void) copy: (id) sender
{
      int selectedRow;
      id item;

      selectedRow = [browserView selectedRow];
      item = [browserView itemAtRow: selectedRow];
      [editedObject release];
      editedObject = [item retain];
      cut = NO;
}

- (void) _cutAndPasteItem: (id) item 
      from: (NSString*) source 
      to: (NSString*) destination
{

      int retVal;
      id realObject; //the actual unarchived object

      progressPanel = [ULProgressPanel progressPanelWithTitle:
                        [NSString stringWithFormat: @"Moving %@ to %@", 
                              [editedObject objectForKey: @"Name"],
                              [item objectForKey: @"ULDatabaseClientName"]]
                        message: @"Moving"      
                        progressInfo: @"Retrieving"];
      [progressPanel retain];             
      [progressPanel setIndeterminate: YES];    
      
      retVal = NSRunAlertPanel(@"Move",
                  [NSString stringWithFormat: @"Move %@ from\n %@\n to\n %@?", 
                        [editedObject objectForKey: @"Name"],
                        source,
                        destination],
                  @"OK", 
                  @"Dismiss",
                  nil);
                  
      if(retVal == NSOKButton)
      {
            [progressPanel runProgressPanel: NO];           

            //get the real object
            realObject = [databaseInterface unarchiveObjectWithID: 
                        [editedObject objectForKey: @"Identification"]
                  ofClass: [editedObject objectForKey: @"Class"]
                  fromSchema: [editedObject objectForKey: @"Schema"]
                  ofClient: [editedObject objectForKey: @"DatabaseClient"]];


            //FIXME: We should check remove and add permissions
            //Aswell as database availability before commiting 
            //to anything here.

            //delete it from its old database
            [databaseInterface removeObjectOfClass: [editedObject valueForKey:@"Class"]
            withID: [editedObject valueForKey: @"Identification"]
            fromSchema: [editedObject objectForKey: @"Schema"]
            ofClient: [editedObject objectForKey: @"DatabaseClient"]];

            [progressPanel setProgressInfo: @"Adding"];
            //add it to its new database
            [databaseInterface addObject: realObject
            toSchema: [item objectForKey: @"ULSchemaName"]
            ofClient: [item objectForKey: @"ULDatabaseClientName"]];

            sleep(1.0);
      }
      else
      {
            [progressPanel release];
            progressPanel = nil;
            editedObject = nil;
            cut = NO;
      }
}

- (void) _copyAndPasteItem: (id) item 
      from: (NSString*) source 
      to: (NSString*) destination
{
      int retVal;
      id realObject; //the actual unarchived object
      
      progressPanel = [ULProgressPanel progressPanelWithTitle: 
                        [NSString stringWithFormat: @"Copying %@ to %@", 
                              [editedObject objectForKey: @"Name"],
                              [item objectForKey: @"ULDatabaseClientName"]]   
                        message: @"Copying"
                        progressInfo: @"Estimated Time - Unknown"];
      [progressPanel retain];             
      [progressPanel setIndeterminate: YES];    
      [NSApp updateWindows];

      retVal = NSRunAlertPanel(@"Copy",
                  [NSString stringWithFormat: @"Copy %@ from\n %@\n to\n %@?", 
                        [editedObject objectForKey: @"Name"],
                        source,
                        destination],
                  @"OK", 
                  @"Dismiss",
                  nil);
      if(retVal == NSOKButton)
      {
            [progressPanel runProgressPanel: NO];           
            realObject = [databaseInterface unarchiveObjectWithID: 
                              [editedObject objectForKey: @"Identification"]
                        ofClass: [editedObject objectForKey: @"Class"]
                        fromSchema: [editedObject objectForKey: @"Schema"]
                        ofClient: [editedObject objectForKey: @"DatabaseClient"]];
            
            [databaseInterface addObject: realObject  
                  toSchema: [item objectForKey: @"ULSchemaName"]
                  ofClient: [item objectForKey: @"ULDatabaseClientName"]];
            sleep(1.0);
      }
      else
      {
            [progressPanel release];
            progressPanel = nil;
            editedObject = nil;
            cut = NO;
      }
}

- (void) paste: (id) sender
{
      int selectedRow;
      NSString* source, *destination;
      id item;

      selectedRow = [browserView selectedRow];
      item = [browserView itemAtRow: selectedRow];

      source = [NSString stringWithFormat: @"%@/%@",
                  [editedObject objectForKey: @"Database"],
                  [editedObject objectForKey: @"Schema"]]; 
      destination = [NSString stringWithFormat: @"%@/%@",
                  [item objectForKey: @"ULDatabaseClientName"],
                  [item objectForKey: @"ULSchemaName"]]; 

      if(cut)
      {     
            [self _cutAndPasteItem: item 
                  from: source 
                  to: destination];
      }
      else
      {     
            [self _copyAndPasteItem: item 
                  from: source 
                  to: destination];
      }     
}

- (void) export: (id) sender
{
      BOOL retVal;
      int selectedRow, result;
      id realObject; //the actual unarchived object
      id item, savePanel, filename;
      NSError *error;
      NSMutableData *data;
      NSKeyedArchiver* archiver;
      NSString* storagePath, *destinationPath;

      selectedRow = [browserView selectedRow];
      item = [browserView itemAtRow: selectedRow];
      
      NS_DURING
      {
            savePanel = [NSSavePanel savePanel];      
            [savePanel setTitle: 
                  [NSString stringWithFormat: @"Export Data - %@",
                        [item objectForKey: @"Name"]]];
            [savePanel setDirectory: 
                  [NSHomeDirectory() stringByAppendingPathComponent: @"adun"]];
            result = [savePanel runModal];
            filename = [savePanel filename];

            if(result == NSOKButton)
            {
                  realObject = [databaseInterface unarchiveObjectWithID: [item objectForKey: @"Identification"]
                              ofClass: [item objectForKey: @"Class"]
                              fromSchema: [item objectForKey: @"Schema"]
                              ofClient: [item objectForKey: @"DatabaseClient"]];
                  
                  //If its a simulation we also have to export the simulation
                  //data directory. This involves copying the directory to the
                  //chosen location.      
                  if([realObject isKindOfClass: [ULSimulation class]])
                  {
                        storagePath = [[realObject dataStorage] storagePath];
                        destinationPath =  [filename stringByAppendingString: @"_Data"];
                        retVal = [[NSFileManager defaultManager]
                                    copyPath: storagePath
                                    toPath: destinationPath
                                    handler: nil];

                        if(!retVal)
                        {
                              //Abort
                              NSRunAlertPanel(@"Error",
                                    @"Unable to extract simulation data - Aborting",
                                    @"Dismiss", 
                                    nil,
                                    nil);

                              return;
                        }     
                  }
                  
                  data = [NSMutableData new];
                  archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
                  [archiver setOutputFormat: NSPropertyListXMLFormat_v1_0];
                  [archiver encodeObject: realObject forKey: @"root"];
                  [archiver finishEncoding];
                  [archiver release];
                  
                  retVal = [[ULIOManager appIOManager]
                              writeObject: data 
                              toFile: filename 
                              error: &error];

                  [data release];
                  if(!retVal)
                  {
                        NSRunAlertPanel(@"Error",
                              [[error userInfo] objectForKey:NSLocalizedDescriptionKey],
                              @"Dismiss", 
                              nil,
                              nil);
                  }
            }
      }
      NS_HANDLER
      {
            NSRunAlertPanel(@"Alert", [localException reason], @"Dismiss", nil, nil);
      }
      NS_ENDHANDLER
}

- (BOOL) _canImportObject: (id) object error: (NSError**) error toSchema: (id) info
{
      /*
        If the object already exists in the file system database
        we dont add it. If we do it will cause problems in the case of simulation
        data since the data storage being imported will clash with the data 
        storage already present. 
        FIXME: There is no method for checking if an object is present in
        a remote database so we have to add everything. Luckily the 
        problem doesnt exist in the SQL backend case however it would be better
        to check first.
       */ 

      //FIXME: Related to above - We dont know here what database we are
      //adding to. At the moment it is always the fileSystemDatabase. In the future
      //we should use a method objectInSchema:ofDatabase etc using the schema information
      //passed instead.
      if([databaseInterface objectInFileSystemDatabase: object])
      {
            *error = [NSError errorWithDomain: @"ULErrorDomain"
                        code: 10
                        userInfo: [NSDictionary dictionaryWithObjectsAndKeys: 
                                    @"Data already present in database", 
                                    NSLocalizedDescriptionKey,
                                    nil]];
            return NO;
      }

      //Check the file storage is valid. 
      if([object isKindOfClass: [ULSimulation class]])
            if(![[object dataStorage] isAccessible])
            {
                  *error = [[object dataStorage] accessError];
                  return NO;
            }
      
      return YES;
}

- (void) import: (id) sender
{
      BOOL retVal;
      int selectedRow, result;
      id object; 
      id item, openPanel, filename;
      NSError *error;
      NSMutableData *data = [NSMutableData new];
      NSString* storagePath;
      ULFileSystemSimulationStorage *dataStorage;

      selectedRow = [browserView selectedRow];
      item = [browserView itemAtRow: selectedRow];
      
      NS_DURING
      {
            progressPanel = [ULProgressPanel progressPanelWithTitle: @"Import"
                              message: @"Importing"
                              progressInfo: @"Estimated time - Unknown"];
            [progressPanel retain];             
            [progressPanel setIndeterminate: YES];    
            [NSApp updateWindows];

            openPanel = [NSOpenPanel openPanel];      
            [openPanel setTitle: @"Import Data"];
            [openPanel setDirectory: 
                  [NSHomeDirectory() stringByAppendingPathComponent: @"adun"]];
            result = [openPanel runModal];
            filename = [openPanel filename];

            if(result == NSOKButton)
            {
                  [progressPanel setMessage: 
                        [NSString stringWithFormat: @"Importing file - %@", [filename lastPathComponent]]];
                  [progressPanel runProgressPanel: NO];           
                  object = [NSKeyedUnarchiver unarchiveObjectWithFile: filename];

                  //If its a simulation we have to import its data aswell.
                  //This is a directory with the same name as filename but 
                  //with _Data appended (see export: above).
                  if([object isKindOfClass: [ULSimulation class]])
                  {
                        storagePath = [filename stringByAppendingString: @"_Data"];
                        //Create a ULFileSystemSimulationStorage object for the data
                        dataStorage = [[ULFileSystemSimulationStorage alloc]
                                    initForReadingSimulationDataAtPath: storagePath];
                        [dataStorage autorelease];
                        [object setDataStorage: dataStorage];
                  }

                  if(![self _canImportObject: object error: &error toSchema: item])
                  {
                        [progressPanel endPanel];
                        [progressPanel release];
                        progressPanel = nil;
                        NSRunAlertPanel(@"Error - Import Failed", 
                              [[error userInfo] objectForKey: NSLocalizedDescriptionKey], 
                              @"Dismiss", 
                              nil, 
                              nil);
                  }
                  else
                  {
                        //We add non-ULSimulation objects in the standard way
                        [databaseInterface addObject: object      
                              toSchema: [item objectForKey: @"ULSchemaName"]
                              ofClient: [item objectForKey: @"ULDatabaseClientName"]];
                  }           
            }
            else
            {
                  //If the user chose cancel we have to
                  //destroy the progress panel we set up above
                  [progressPanel release];
                  progressPanel = nil;
            }
      }
      NS_HANDLER
      {
            //There was an exception during the import.
            //End the progress panel and display an alert panel.
            [progressPanel endPanel];
            [progressPanel release];
            progressPanel = nil;
            NSRunAlertPanel(@"Alert", [localException reason], @"Dismiss", nil, nil);
      }
      NS_ENDHANDLER
}

/**
ULPasteboard delegate methods
**/

00728 - (NSArray*) availableTypes
{
      NSMutableArray* array = [NSMutableArray array];

      if([selectedSystems count] != 0)
            [array addObject: @"ULSystem"];
      
      if([selectedOptions count] != 0)
            [array addObject: @"ULOptions"];
      
      if([selectedDataSets count] != 0)
            [array addObject: @"AdDataSet"];
      
      if([selectedSimulations count] != 0)
            [array addObject: @"ULSimulation"];

      return array;     
}

00747 - (id) objectForType: (NSString*) type
{
      id item; 
      id object;

      NS_DURING
      {
            if([type isEqual: @"ULSystem"])
                  item = [selectedSystems objectAtIndex: 0];
            else if([type isEqual: @"ULOptions"])
                  item = [selectedOptions objectAtIndex: 0];
            else if([type isEqual: @"AdDataSet"])
                  item = [selectedDataSets objectAtIndex: 0];
            else if([type isEqual: @"ULSimulation"])
                  item = [selectedSimulations objectAtIndex: 0];
            
            object = [databaseInterface 
                        unarchiveObjectWithID: [item objectForKey: @"Identification"]
                        ofClass: [item objectForKey: @"Class"]
                        fromSchema: [item objectForKey: @"Schema"]
                        ofClient: [item objectForKey: @"DatabaseClient"]];

            return object;          
      }
      NS_HANDLER
      {
            //Objects should call availableTypes (or countOfObjectsForType:)
            //to determine if the neccessary type is available. However
            //in case they dont we must catch the NSRangeException that will be
            //raised if there is no object of the requested type available
            if([[localException name] isEqual: NSRangeException])
            {
                  NSWarnMLog(@"Recieved request for unavailable object type %@"
                              , type);
            }
            else
            {
                  //Deal with an exception from the database interface
                  NSWarnLog(@"Caught exception while attempting to retrieve object from client %@.",
                              [item objectForKey: @"Database"]);
                  NSWarnLog(@"Item class %@. Item id %@", 
                              [item objectForKey: @"Identification"],
                              [item objectForKey: @"Class"]);
                  NSWarnLog(@"Exception name %@. Reason %@. Information %@", 
                              [localException name],
                              [localException reason],
                              [localException userInfo]);
                  NSRunAlertPanel(@"Error - Unable to retrieve selected object",
                        [localException reason],
                        @"Dismiss", 
                        nil,
                        nil);
            }
      }
      NS_ENDHANDLER

      return nil;
}

- (NSArray*) objectsForType: (NSString*) type
{
      NSMutableArray* array = [NSMutableArray array];
      NSEnumerator* selectionEnum;
      id item, object;

      if([type isEqual: @"ULSystem"])
            selectionEnum = [selectedSystems objectEnumerator];
      else if([type isEqual: @"ULOptions"])
            selectionEnum = [selectedOptions objectEnumerator];
      else if([type isEqual: @"AdDataSet"])
            selectionEnum = [selectedDataSets objectEnumerator];
      else if([type isEqual: @"ULSimulation"])
            selectionEnum = [selectedSimulations objectEnumerator];
      
      NS_DURING
      {
            while(item = [selectionEnum nextObject])
            {
                  object = [databaseInterface unarchiveObjectWithID: 
                              [item objectForKey: @"Identification"]
                              ofClass: [item objectForKey: @"Class"]
                              fromSchema: [item objectForKey: @"Schema"]
                              ofClient: [item objectForKey: @"DatabaseClient"]];
                  [array addObject: object];

            }                 
      }
      NS_HANDLER
      {
            NSWarnLog(@"Caught exception while attempting to retrieve object from client %@.",
                        [item objectForKey: @"Database"]);
            NSWarnLog(@"Item class %@. Item id %@", 
                        [item objectForKey: @"Identification"],
                        [item objectForKey: @"Class"]);
            NSWarnLog(@"Exception name %@. Reason %@. Information %@", 
                        [localException name],
                        [localException reason],
                        [localException userInfo]);
            NSRunAlertPanel(@"Error - Unable to retrieve selected object",
                  [localException reason],
                  @"Dismiss", 
                  nil,
                  nil);
      }
      NS_ENDHANDLER

      return array;
}

- (int) countOfObjectsForType: (NSString*) type
{

      if([type isEqual: @"ULSystem"])
            return [selectedSystems count];

      if([type isEqual: @"ULOptions"])
            return [selectedOptions count];

      if([type isEqual: @"AdDataSet"])
            return [selectedDataSets count];
      
      if([type isEqual: @"ULSimulation"])
            return [selectedSimulations count];
}

- (void) pasteboardChangedOwner: (id) pasteboard
{
      [self deselectAllRows: self];
      isActive = NO;
}

@end




Generated by  Doxygen 1.6.0   Back to index