
/*!
 Copyright 2009 Sun Microsystems, Inc.
  
 @author
 jak
 
 @class
 DbMySQLTableEditor
 
 @abstract
 The main controller for the MySQL Table Editor.
 
 @ingroup
 MySQL Table Editor
 
 */



#import "DbMySQLTableEditor.h"
#import "mysql_table_editor.h"
#import "db_object_helpers.h"
#import "NSString_extras.h"
#import "MCPPUtilities.h"
#import "MacTableEditorInformationSource.h"
#import "MacTableEditorColumnsInformationSource.h"

#import "MacTableEditorIndexColumnsInformationSource.h"
#import "MacTableEditorFKColumnsInformationSource.h"
#import "editor_table.h"
#import "editor_table_inserts.h"
#import "MTextImageCell.h"
#import "GRTTreeDataSource.h"

#import "DbPrivilegeEditorTab.h"


const NSString* shouldRaiseException = @"should raise exception";


// Callback to update the editor GUI to reflect changes in the backend.
static void call_refresh(DbMysqlTableEditor* theEditor)
{
  // As it turns out, this call-back can be called from non-main threads.
  [theEditor performSelectorOnMainThread: @selector(refreshTableEditorGUI)
                              withObject: nil
                           waitUntilDone: NO];
}



@implementation DbMysqlTableEditor



#pragma mark Refreshing of views


// Set up values for the Table tab.
- (void) refreshTableEditorGUITableTab;
{
  if (mBackEnd != nil) {
    NSString* name = [NSString stringWithCPPString: mBackEnd->get_name()];
    [mTableName setStringValue: name];
    
    NSString* collation = [NSString stringWithCPPString: mBackEnd->get_table_option_by_name("CHARACTER SET - COLLATE")];
    if ([collation isEqualToString: @" - "] || [collation length] == 0) {
      collation = @"Schema Default";
    }
    {
      // DEBUG
      id item = [mTableCollation itemWithTitle: collation];
      if (item == nil) {
        NSLog(@"*** Warning: Table collation '%@' not found in menu.", collation);
      }
    }
    [mTableCollation selectItemWithTitle: collation];
    
    NSString* engine = [NSString stringWithCPPString: mBackEnd->get_table_option_by_name("ENGINE")];
    [mTableEngine selectItemWithTitle: engine];
    
    NSString* comments = [NSString stringWithCPPString: mBackEnd->get_comment()];
    [mTableComment setString: comments];
  }
}



// Set up values for the Columns tab.
- (void) refreshTableEditorGUIColumnsTab;
{
  [mColumnsTable reloadData];
  
  int rowIndex = [mColumnsTable selectedRow];
  if (rowIndex >= 0) {
    NSString* collation = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::CharsetCollation
                                                                   row: rowIndex];
    if ([collation isEqualToString: @" - "] || [collation length] == 0) {
      collation = @"Table Default";
    }
    [mColumnsCollation selectItemWithTitle: collation];
    NSString* collationEnabled = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::HasCharset
                                                                          row: rowIndex];
    [mColumnsCollation setEnabled: [collationEnabled isEqualToString: @"1"]];
    
    NSString* columnName = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Name
                                                                    row: rowIndex];
    [mColumnsDetailsBox setTitle: [NSString stringWithFormat: @"Column details '%@'", columnName]];
    
    NSString* comments = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Comment
                                                                  row: rowIndex];
    [mColumnsComment setString: comments];
    
    // Other column flag checkboxes outside the table view.
    {
      // Remove any old checkboxes.
      int i, c = [mColumnFlagCheckboxes count];
      for (i = 0; i < c; i++) {
        [[mColumnFlagCheckboxes objectAtIndex: i] removeFromSuperview];
      }
      [mColumnFlagCheckboxes autorelease];
      mColumnFlagCheckboxes = nil;
    }
    
    NSArray* flags = [mColumnsDataSource columnFlagsForRow: rowIndex];
    
    {
      // Add new checkboxes for the column flags.
      mColumnFlagCheckboxes = [[NSMutableArray alloc] initWithCapacity: [flags count]];
      NSRect r = [mColumnsCollation frame];
      NSRect lastFlagRect;
      r.size.height = 14;
      r.origin.y -= 21;
      int i, c = [flags count] / 2;
      for (i = 0; i < c; i++) {
        NSString* title = [flags objectAtIndex: i * 2];
        NSCellStateValue state = ( [[flags objectAtIndex: i * 2 + 1] boolValue] ? NSOnState : NSOffState );
        NSButton* b = [[[NSButton alloc] initWithFrame: r] autorelease];
        [b setTag: i];
        [b setButtonType: NSSwitchButton];
        [b setTitle: title];
        [b setState: state];
        [b setFont: [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]];
        [[b cell] setControlSize: NSSmallControlSize];
        [b sizeToFit];
        [b setAutoresizingMask: NSViewMinYMargin];
        [b setTarget: self];
        [b setAction: @selector(userClickButton:)];
        [mColumnsDetailsBox addSubview: b];
        [mColumnFlagCheckboxes addObject: b];
        r = NSOffsetRect(r, 0, -17);
      }
      lastFlagRect = r;
      
      [mColumnFlagsLabel setHidden: c == 0];
      
      {
        // Resize the comments text area.
        NSView* scrollView = [[mColumnsComment superview] superview];
        NSRect commentsFrame = [scrollView frame];
        float commentsHeight = lastFlagRect.origin.y + lastFlagRect.size.height - commentsFrame.origin.y;
        commentsFrame.size.height = commentsHeight;
        [scrollView setFrame: commentsFrame];
        
        NSRect labelFrame = [mColumnsCommentLabel frame];
        labelFrame.origin.y = NSMaxY(commentsFrame) - labelFrame.size.height;
        [mColumnsCommentLabel setFrame: labelFrame];
      }
    }
  }
}



- (void) refreshTableEditorGUIIndicesTab;
{
  [mIndicesTable reloadData];
  
  int rowIndex = [mIndicesTable selectedRow];
  if (rowIndex >= 0) {
    NSString* indexName = [mIndicesDataSource objectValueForValueIndex: MySQLTableIndexListBE::Name
                                                                   row: rowIndex];
    
    [mIndicesDetailsBox setTitle: [NSString stringWithFormat: @"Index details '%@'", indexName]];
    
//    obj = [mIndicesDataSource objectValueForValueIndex: valueIndex
//                                                   row: rowIndex];

//    NSString* columnName = [mIndicesDataSource valueForIndexColumn: bec::IndexColumnsListBE::Name
//                                                        atIndexRow: rowIndex
//                                                  withTableBackend: mBackEnd];
    {
      mBackEnd->get_indexes()->select_index(rowIndex);
      
      NSString* storageType = [mIndicesDataSource objectValueForValueIndex: MySQLTableIndexListBE::StorageType
                                                                       row: rowIndex];
      [mIndicesStorageTypes selectItemWithTitle: storageType];
      NSString* blockSize = [mIndicesDataSource objectValueForValueIndex: MySQLTableIndexListBE::RowBlockSize
                                                                     row: rowIndex];
      [mIndicesBlockSize setStringValue: blockSize];
      NSString* parser = [mIndicesDataSource objectValueForValueIndex: MySQLTableIndexListBE::Parser
                                                                  row: rowIndex];
      [mIndicesParser setStringValue: parser];
      
      NSString* comment = [mIndicesDataSource objectValueForValueIndex: bec::IndexListBE::Comment
                                                                   row: rowIndex];
      [mIndicesComment setString: comment];
      
      [mIndexColumnsTable reloadData];
    }
  }
}



- (void) refreshTableEditorGUIFKTab;
{
  [mIndicesTable reloadData];
  
  {
    // Setup the popup menu items for the referencing table column.
    id col = [mFKTable tableColumnWithIdentifier: @"referenced table"];
    NSPopUpButtonCell* cell = [col dataCell];
    MFillPopupButtonWithStrings((NSPopUpButton*)cell, mBackEnd->get_all_table_names());  
    
    col = [mFKColumnsTable tableColumnWithIdentifier: @"referenced column"];
    cell = [col dataCell];
    NSInteger rowIndex = [mFKTable selectedRow];
    if (rowIndex >= 0) {
      NSString* tableName = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::RefTable
                                                                row: rowIndex];
      if ([tableName length] > 0) {
        MFillPopupButtonWithStrings((NSPopUpButton*)cell, mBackEnd->get_table_column_names([tableName UTF8String]));
      }
    }
  }
  
  int rowIndex = [mFKTable selectedRow];
  
  if (rowIndex >= 0) {
    NSString* fkName = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::Name
                                                     row: rowIndex];
    [mFKDetailsBox setTitle: [NSString stringWithFormat: @"Foreign key details '%@'", fkName]];
    
    mBackEnd->get_fks()->select_fk(rowIndex);
    
    int rowIndex = [mFKTable selectedRow];
    NSString* updateAction = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::OnUpdate
                                                                 row: rowIndex];
    [mFKOnUpdate selectItemWithTitle: updateAction];
    
    NSString* deleteAction = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::OnDelete
                                                                 row: rowIndex];
    [mFKOnDelete selectItemWithTitle: deleteAction];
    
    NSString* comment = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::Comment
                                                            row: rowIndex];
    [mFKComment setString: comment];
    
    [mFKColumnsTable reloadData];
  }
}

- (void) refreshTableEditorGUIPartitioningTab;
{
  [mPartitionTable reloadData];
  
  std::string prtn_type = mBackEnd->get_partition_type();
  NSString* partitionType = [NSString stringWithUTF8String: prtn_type.c_str()];
  BOOL enabled = ( [partitionType length] > 0 );
  [mPartitionEnabledCheckbox setState: (enabled ? NSOnState : NSOffState)];
  [mPartitionEnabledCheckbox setEnabled: YES];
  
  // Enable or disable the first row of controls.
  [mPartitionPopup setEnabled: enabled];
  [mPartitionParametersTextField setEnabled: enabled];
  [mPartitionCountCombo setEnabled: enabled];
  [mPartitionManualCheckbox setEnabled: enabled];
  
  // Enable or disable the first popup on the second row of controls.
//  NSString* partitionType = [mPartitionPopup titleOfSelectedItem];
  BOOL subEnabled = enabled && ( [partitionType isEqualToString: @"RANGE"] || [partitionType isEqualToString: @"LIST"] );
  [mSubpartitionPopup setEnabled: subEnabled];

  // Enable or disable the rest of the second row of controls.
  NSString* subpartitionType = [mSubpartitionPopup titleOfSelectedItem];
  subEnabled = subEnabled && (! [subpartitionType isEqualToString: @"Disabled"]);
  [mSubPartitionParametersTextField setEnabled: subEnabled];
  [mSubpartitionCountCombo setEnabled: subEnabled];
  [mSubpartitionManualCheckbox setEnabled: subEnabled && ([mPartitionManualCheckbox state] == NSOnState)];
  
  {
    // Set up partitioning controls.
    [mPartitionPopup selectItemWithTitle: partitionType];
    
    std::string s = mBackEnd->get_partition_expression();
    NSString* partExpr = [NSString stringWithUTF8String: s.c_str()];
    [mPartitionParametersTextField setStringValue: partExpr];

    int c = mBackEnd->get_partition_count();
    NSString* partCount = [NSString stringWithFormat: @"%d", c];
    [mPartitionCountCombo setStringValue: partCount];
    
    NSCellStateValue manualState = ( mBackEnd->get_explicit_partitions() == true ? NSOnState : NSOffState );
    [mPartitionManualCheckbox setState: manualState];
  }
  
  {
    // Set up subpartitioning controls.
    std::string s = mBackEnd->get_subpartition_type();
    NSString* partType = [NSString stringWithUTF8String: s.c_str()];
    if ([partType length] == 0) {
      partType = @"Disabled";
    }
    [mSubpartitionPopup selectItemWithTitle: partType];
    
    s = mBackEnd->get_subpartition_expression();
    NSString* partExpr = [NSString stringWithUTF8String: s.c_str()];
    [mSubPartitionParametersTextField setStringValue: partExpr];
    
    int c = mBackEnd->get_subpartition_count();
    NSString* partCount = [NSString stringWithFormat: @"%d", c];
    [mSubpartitionCountCombo setStringValue: partCount];
    
    NSCellStateValue manualState = ( mBackEnd->get_explicit_subpartitions() == true ? NSOnState : NSOffState );
    [mSubpartitionManualCheckbox setState: manualState];
  }

  // Enable or disable the table view.
  BOOL tabViewEnabled = ( ([mPartitionManualCheckbox isEnabled] && [mPartitionManualCheckbox state] == NSOnState) );
  [mPartitionTable setEnabled: tabViewEnabled];
}



- (void) refreshTableEditorGUIOptionsTab;
{
  // General options
  
  NSString* option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("PACK_KEYS").c_str()];
  if ([option length] == 0)
    option = @"Default";
  [mOptionsPackKeys selectItemWithTitle: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("PASSWORD").c_str()];
  [mOptionsTablePassword setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("AUTO_INCREMENT").c_str()];
  [mOptionsAutoIncrement setStringValue: option];

  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("DELAY_KEY_WRITE").c_str()];
  [mOptionsDelayKeyUpdates setState: ([option isEqualToString: @"1"] ? NSOnState : NSOffState)];
  
  // Row options
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("ROW_FORMAT").c_str()];
  if ([option length] == 0)
    option = @"Default";
  [mOptionsRowFormat selectItemWithTitle: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("AVG_ROW_LENGTH").c_str()];
  [mOptionsAvgRowLength setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("MIN_ROWS").c_str()];
  [mOptionsMinRows setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("MAX_ROWS").c_str()];
  [mOptionsMaxRows setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("CHECKSUM").c_str()];
  [mOptionsUseChecksum setState: ([option isEqualToString: @"1"] ? NSOnState : NSOffState)];
  
  // Storage options
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("DATA DIRECTORY").c_str()];
  [mOptionsDataDirectory setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("INDEX DIRECTORY").c_str()];
  [mOptionsIndexDirectory setStringValue: option];
  
  // Merge table options
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("UNION").c_str()];
  [mOptionsUnionTables setStringValue: option];
  
  option = [NSString stringWithUTF8String: mBackEnd->get_table_option_by_name("INSERT_METHOD").c_str()];
  if ([option length] == 0)
    option = @"Default";
  [mOptionsMergeMethod selectItemWithTitle: option];
}



- (void) refreshTableEditorGUIInsertsTab;
{
  [mEditorInsertsText setString: [NSString stringWithUTF8String: mBackEnd->get_inserts().c_str()]];
  
  // Enable the button only if number of columns > 0. 
  NSInteger number = [mColumnsDataSource numberOfRowsInTableView: nil];
  [mEditorInsertsOpenEditor setEnabled: (number > 1)];
  [mEditorInsertsHint setHidden: (number > 1)];
}



- (void) refreshTableEditorGUI;
{
  [self refreshTableEditorGUITableTab];
  [self refreshTableEditorGUIColumnsTab];
  [self refreshTableEditorGUIIndicesTab];
  [self refreshTableEditorGUIFKTab];
  [self refreshTableEditorGUIPartitioningTab];

  [self refreshTableEditorGUIOptionsTab];
  [self refreshTableEditorGUIInsertsTab];
}



- (void) switchToColumnsTab;
{
  if (! [[[mEditorsTabView selectedTabViewItem] identifier] isEqual: @"columns"]) {
    [mEditorsTabView selectTabViewItemWithIdentifier: @"columns"];
    
    [mColumnsDataSource refresh];
    
    NSUInteger rowIndex = [self numberOfRowsInTableView: mColumnsTable] - 1;
    
    // First select the last row...
    [mColumnsTable selectRow: rowIndex
        byExtendingSelection: NO];
    
    if (rowIndex == 0) {
      // The following code is a bit involved, but it makes the tablew view
      // properly display the default PK column name and all its other settings.
      
      // Force automatic filling of default values in the BE.
      [mColumnsDataSource setIntValue: 1
                        forValueIndex: bec::TableColumnsListBE::Name
                                  row: rowIndex];
      
      // ...then initiate editing of the first cell to make the default value stick.
      [mColumnsTable editColumn: 0
                            row: rowIndex
                      withEvent: nil
                         select: YES];
      
      // Refresh the UI to update state of checkboxes etc.
      [self refreshTableEditorGUIColumnsTab];
    }
    
    // Initiate editing of the first cell again.
    [mColumnsTable editColumn: 0
                          row: rowIndex
                    withEvent: nil
                       select: YES];
  }
}



#pragma mark Table view support



- (NSInteger) numberOfRowsInTableView: (NSTableView*) aTableView;
{
  NSInteger number;
  
  if (aTableView == mColumnsTable) {
    number = [mColumnsDataSource numberOfRowsInTableView: aTableView];
  }
  else if (aTableView == mIndicesTable) {
    number = [mIndicesDataSource numberOfRowsInTableView: aTableView];
  }
  else if (aTableView == mIndexColumnsTable) {
    number = [mIndexColumnsDataSource numberOfRowsInTableView: aTableView];
  }
  else if (aTableView == mFKTable) {
    number = [mFKDataSource numberOfRowsInTableView: aTableView];
  }
  else if (aTableView == mFKColumnsTable) {
    number = [mFKColumnsDataSource numberOfRowsInTableView: aTableView];
  }
  else if (aTableView == mEditorInsertsTable) {
    number = ( mGrid != NULL ? mGrid->count() : 0 );
  }
  else {
    number = ( mDidAwakeFromNib ? NSNotFound : 0 );
  }
  
  NSAssert(number != NSNotFound, @"Mismatch in tables.");
  
  return number;
}



- (id) tableView: (NSTableView*) aTableView
objectValueForTableColumn: (NSTableColumn*) aTableColumn
             row: (NSInteger) rowIndex;
{
  NSString* obj = shouldRaiseException;
  
  id identifier = [aTableColumn identifier];
  int valueIndex = NSNotFound;
  
  if (aTableView == mColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      valueIndex = bec::TableColumnsListBE::Name;
    }
    else if ([identifier isEqual: @"type"]) {
      valueIndex = bec::TableColumnsListBE::Type;
    }
    else if ([identifier isEqual: @"primarykey"]) {
      valueIndex = bec::TableColumnsListBE::IsPK;
    } 
    else if ([identifier isEqual: @"notnull"]) {
      valueIndex = bec::TableColumnsListBE::IsNotNull;
    }
    else if ([identifier isEqual: @"binary"]) {
      valueIndex = bec::TableColumnsListBE::IsBinary;
    }
    else if ([identifier isEqual: @"unsigned"]) {
      valueIndex = bec::TableColumnsListBE::IsUnsigned;
    }
    else if ([identifier isEqual: @"zerofill"]) {
      valueIndex = bec::TableColumnsListBE::IsZerofill;
    }
    else if ([identifier isEqual: @"autoincrement"]) {
      valueIndex = MySQLTableColumnsListBE::IsAutoIncrement;
    }
    else if ([identifier isEqual: @"default"]) {
      valueIndex = bec::TableColumnsListBE::Default;
    }
    
    NSAssert(valueIndex != NSNotFound, @"Mismatch in columns.");

    obj = [mColumnsDataSource objectValueForValueIndex: valueIndex
                                                   row: rowIndex];
  }
  else if (aTableView == mIndicesTable) {
    if ([identifier isEqual: @"name"]) {
      valueIndex = MySQLTableIndexListBE::Name;
    }
    else if ([identifier isEqual: @"type"]) {
      valueIndex = MySQLTableIndexListBE::Type;
    }
    
    NSAssert(valueIndex != NSNotFound, @"Mismatch in columns.");
    
    obj = [mIndicesDataSource objectValueForValueIndex: valueIndex
                                                   row: rowIndex];
  }
  else if (aTableView == mIndexColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      valueIndex = bec::IndexColumnsListBE::Name;
    }
    else if ([identifier isEqual: @"#"]) {
      valueIndex = bec::IndexColumnsListBE::OrderIndex;
    }
    else if ([identifier isEqual: @"order"]) {
      valueIndex = bec::IndexColumnsListBE::Descending;
    }
    else if ([identifier isEqual: @"length"]) {
      valueIndex = bec::IndexColumnsListBE::Length;
    }
    
    NSAssert(valueIndex != NSNotFound, @"Mismatch in columns.");
    
    obj = [mIndexColumnsDataSource objectValueForValueIndex: valueIndex
                                                        row: rowIndex];
    
    if ( ([identifier isEqual: @"length"]) && ([obj isEqual: @"0"]) ) {
      // Do not display zero.
      obj = @"";
    }
  }
  
  else if (aTableView == mFKTable) {
    if ([identifier isEqual: @"name"]) {
      obj = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::Name
                                                row: rowIndex];
    }
    else if ([identifier isEqual: @"referenced table"]) {
      obj = @"foo";
    }
  }
  
  else if (aTableView == mFKColumnsTable) {
    obj = @"foo";
  }

  else if (aTableView == mEditorInsertsTable) {
    const ::bec::NodeId& node = mGrid->get_node(rowIndex);
    int columnIndex = [[aTableColumn identifier] intValue];
    std::string value;
    int didFine = mGrid->get_field(node, columnIndex, value);
    if (didFine != 0)
      obj = [NSString stringWithUTF8String: value.c_str()];
    else
      obj = @"";
  }
  
  NSAssert( obj != shouldRaiseException, @"No match for tableview.");
  
  return obj;
}



- (void) tableView: (NSTableView*) aTableView
   willDisplayCell: (id) aCell
    forTableColumn: (NSTableColumn*) aTableColumn
               row: (NSInteger) rowIndex;
{
  id identifier = [aTableColumn identifier];

  if (aTableView == mColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      NSImage* img = [mColumnsDataSource iconAtRow: rowIndex];
      [aCell setImage: img];
      [aCell setPlaceholderString: @"<click to edit>"];
    }
  }
  
  else if (aTableView == mIndexColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      id obj = [mIndexColumnsDataSource objectValueForValueIndex: bec::IndexColumnsListBE::Name
                                                             row: rowIndex];
      [aCell setTitle: obj];
      
      BOOL yn = [mIndexColumnsDataSource rowEnabled: rowIndex];
      [aCell setState: ( yn ? NSOnState : NSOffState )];
    }
    else if ([identifier isEqual: @"order"]) {
      id obj = [mIndexColumnsDataSource objectValueForValueIndex: bec::IndexColumnsListBE::Descending
                                                             row: rowIndex];
      [aCell selectItemWithTitle: ( [obj intValue] == 0 ? @"ASC" : @"DESC" )];
    }
  }

  else if (aTableView == mIndicesTable) {
    if ([identifier isEqual: @"name"])
      [aCell setPlaceholderString: @"<click to edit>"];
    else if ([identifier isEqual: @"type"]) {
      NSString* title = [mIndicesDataSource objectValueForValueIndex: MySQLTableIndexListBE::Type
                                                                 row: rowIndex];
      [aCell selectItemWithTitle: title];
    }
  }

  else if (aTableView == mFKTable) {
    if ([identifier isEqual: @"name"])
      [aCell setPlaceholderString: @"<click to edit>"];
    else if ([identifier isEqual: @"referenced table"]) {
      NSString* title = [mFKDataSource objectValueForValueIndex: bec::FKConstraintListBE::RefTable
                                                            row: rowIndex];
      [aCell selectItemWithTitle: title];
    }
  }
  
  else if (aTableView == mFKColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      NSString* title = [mFKColumnsDataSource objectValueForValueIndex: bec::FKConstraintColumnsListBE::Column
                                                                   row: rowIndex];
      [aCell setTitle: title];
      BOOL yn = [mFKColumnsDataSource rowEnabled: rowIndex];
      [aCell setState: ( yn ? NSOnState : NSOffState )];
    }
    else if ([identifier isEqual: @"referenced column"]) {
      NSString* title = [mFKColumnsDataSource objectValueForValueIndex: bec::FKConstraintColumnsListBE::RefColumn
                                                                   row: rowIndex];
      [aCell selectItemWithTitle: title];
    }
  }
}


- (bec::ListModel*)listModelForTableView:(NSTableView*)table
{
  if (table == mColumnsTable)
    return mBackEnd->get_columns();
  else if (table == mIndicesTable)
    return mBackEnd->get_indexes();
  else if (table == mFKTable)
    return mBackEnd->get_fks();
  else if (table == mEditorInsertsTable)
    return mBackEnd->get_inserts_grid();
  return 0;
}



- (void) tableViewSelectionDidChange: (NSNotification*) aNotification;
{
  id sender = [aNotification object];
  if (sender == mColumnsTable) {
    [self refreshTableEditorGUIColumnsTab];
  }
  else if (sender == mIndicesTable) {
    [self refreshTableEditorGUIIndicesTab];
  }
  else if (sender == mFKTable) {
    [self refreshTableEditorGUIFKTab];
  }
}



- (void) tableView: (NSTableView*) aTableView
    setObjectValue: (id) anObject
    forTableColumn: (NSTableColumn*) aTableColumn
               row: (NSInteger) rowIndex;
{
  BOOL shouldRefreshGUI = YES;
  
  id identifier = [aTableColumn identifier];
  
  if (aTableView == mColumnsTable) {
    NSString* value;
    int valueIndex;
    NSString* keepEditingTypeString = nil;
    bool isflag= false;
    
    if ([identifier isEqual: @"name"]) {
      valueIndex = bec::TableColumnsListBE::Name;
      value = anObject;
    }
    else if ([identifier isEqual: @"type"]) {
      valueIndex = bec::TableColumnsListBE::Type;
      value = anObject;
      if ( ([value length] >= 2) && ([[value substringFromIndex: [value length] - 2] isEqualToString: @"()"]) ) {
        keepEditingTypeString = value;
      }
    }
    else if ([identifier isEqual: @"primarykey"]) {
      valueIndex = bec::TableColumnsListBE::IsPK;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    } 
    else if ([identifier isEqual: @"notnull"]) {
      valueIndex = bec::TableColumnsListBE::IsNotNull;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    }
    else if ([identifier isEqual: @"autoincrement"]) {
      valueIndex = MySQLTableColumnsListBE::IsAutoIncrement;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    }
    else if ([identifier isEqual: @"binary"]) {
      valueIndex = bec::TableColumnsListBE::IsBinary;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    }
    else if ([identifier isEqual: @"unsigned"]) {
      valueIndex = bec::TableColumnsListBE::IsUnsigned;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    }
    else if ([identifier isEqual: @"zerofill"]) {
      valueIndex = bec::TableColumnsListBE::IsZerofill;
      value = ([anObject boolValue] ? @"1" : @"0");
      isflag= true;
    }
    else if ([identifier isEqual: @"default"]) {
      valueIndex = bec::TableColumnsListBE::Default;
      value = anObject;
      
      // select and edit first column of next row
      NSEvent *currentEvent= [NSApp currentEvent];
      if ([currentEvent type] == NSKeyDown && [[currentEvent characters] characterAtIndex: 0] == '\t'
        && [mColumnsTable numberOfRows] > rowIndex)
      {
        [mColumnsTable selectRow: rowIndex+1 byExtendingSelection:NO];
        [mColumnsTable editColumn: 0
                              row: rowIndex+1
                        withEvent: nil
                           select: YES];        
      }
    }
    else {
      valueIndex = NSNotFound;
      value = nil;
    }
    
    NSAssert(valueIndex != NSNotFound, @"DEBUG - Mismatch in columns table view.");
    
    if (isflag) {
      [mColumnsDataSource setIntValue: [value intValue]
                        forValueIndex: valueIndex
                                  row: rowIndex];
    }
    else {
      if (keepEditingTypeString == nil) {        
        [mColumnsDataSource setStringValue: value
                             forValueIndex: valueIndex
                                       row: rowIndex];
      }
      else {
        [mColumnsTable editColumn: 1
                              row: rowIndex
                        withEvent: nil
                           select: NO];
        NSText* ce = [mColumnsTable currentEditor];
        [ce setString: keepEditingTypeString];
        [ce setSelectedRange: NSMakeRange([keepEditingTypeString length] - 1, 0)];
        
        // Make sure the GUI does not refresh and reload table, which would cause the editing of the cell to end.
        shouldRefreshGUI = NO;
      }
    }
    
    if (shouldRefreshGUI) {
      [self refreshTableEditorGUIColumnsTab];
      [self refreshTableEditorGUIInsertsTab];
    }
  }
  
  else if (aTableView == mIndicesTable) {
    if ([identifier isEqual: @"name"]) {
      [mIndicesDataSource setStringValue: anObject
                           forValueIndex: MySQLTableIndexListBE::Name
                                     row: rowIndex];
    }
    else if ([identifier isEqual: @"type"]) {
      NSUInteger menuItemIndex = [anObject intValue];
      NSPopUpButtonCell* cell = [aTableColumn dataCell];
      if (menuItemIndex < [[cell menu] numberOfItems]) {
        NSString* title  = [[[cell menu] itemAtIndex: menuItemIndex] title];
        [mIndicesDataSource setStringValue: title
                             forValueIndex: MySQLTableIndexListBE::Type
                                       row: rowIndex];
      }
    }
  }
  
  else if (aTableView == mIndexColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      [mIndexColumnsDataSource setRow: rowIndex
                              enabled: [anObject boolValue]];
      [mIndexColumnsTable reloadData];
    }
    else if ([identifier isEqual: @"#"]) {
      [mIndexColumnsDataSource setStringValue: anObject
                                forValueIndex: bec::IndexColumnsListBE::OrderIndex
                                          row: rowIndex];
      [mIndexColumnsTable reloadData];
    }
    else if ([identifier isEqual: @"order"]) {
      NSUInteger menuItemIndex = [anObject intValue];
      [mIndexColumnsDataSource setIntValue: menuItemIndex
                             forValueIndex: bec::IndexColumnsListBE::Descending
                                       row: rowIndex];
    }
    else if ([identifier isEqual: @"length"]) {
      NSUInteger l = [anObject intValue];
      [mIndexColumnsDataSource setIntValue: l
                             forValueIndex: bec::IndexColumnsListBE::Length
                                       row: rowIndex];
    }
  }
  
  else if (aTableView == mFKTable) {
    if ([identifier isEqual: @"name"]) {
      [mFKDataSource setStringValue: anObject
                      forValueIndex: bec::FKConstraintListBE::Name
                                row: rowIndex];
    }
    else if ([identifier isEqual: @"referenced table"]) {
      NSUInteger menuItemIndex = [anObject intValue];
      NSPopUpButtonCell* cell = [aTableColumn dataCell];   
      if (menuItemIndex < [[cell menu] numberOfItems]) {
        NSString* title  = [[[cell menu] itemAtIndex: menuItemIndex] title];
        [mFKDataSource setStringValue: title
                        forValueIndex: bec::FKConstraintListBE::RefTable
                                  row: rowIndex];
      }
      [self refreshTableEditorGUIFKTab];
    }
  }
  
  else if (aTableView == mFKColumnsTable) {
    if ([identifier isEqual: @"name"]) {
      [mFKColumnsDataSource setRow: rowIndex
                           enabled: ( [anObject intValue] == 1 ? YES : NO ) ];
    }
    else if ([identifier isEqual: @"referenced column"]) {
      NSUInteger menuItemIndex = [anObject intValue];
      NSPopUpButtonCell* cell = [aTableColumn dataCell];      
      if (menuItemIndex < [[cell menu] numberOfItems]) {
        NSString* title  = [[[cell menu] itemAtIndex: menuItemIndex] title];
        [mFKColumnsDataSource setStringValue: title
                               forValueIndex: bec::FKConstraintColumnsListBE::RefColumn
                                         row: rowIndex];
      }
      
      [self refreshTableEditorGUIFKTab];
    }
  }
  
  else if (aTableView == mEditorInsertsTable) {
    int colIndex = [identifier intValue];
    const ::bec::NodeId& node = mGrid->get_node(rowIndex);
    mGrid->set_convert_field(node, colIndex, [anObject UTF8String]);
  }
  
  else {
    NSAssert( NO, @"No match in tableView:setObjectValue:::.");
  }
  
  if (shouldRefreshGUI) {
    [aTableView reloadData];
  }
}


- (void)userDeleteSelectedRowInTableView:(NSTableView*)table
{
  if (table == mColumnsTable)
  {
    NSInteger row= [table selectedRow];
    if (row >= 0)
    {
      // delete row
      mBackEnd->get_columns()->delete_node(row);
      [table noteNumberOfRowsChanged];
    }
  }  
  else if (table == mEditorInsertsTable)
  {
    NSInteger row= [table selectedRow];
    if (row >= 0)
    {
      // delete row
      mGrid->delete_node(row);
      [table noteNumberOfRowsChanged];
    }
  }
  else if (table == mIndicesTable)
  {
    NSInteger row= [table selectedRow];
    if (row >= 0)
    {
      // delete row
      mBackEnd->get_indexes()->delete_node(row);
      [table noteNumberOfRowsChanged];
    }
  }
  else if (table == mFKTable)
  {
    NSInteger row= [table selectedRow];
    if (row >= 0)
    {
      // delete row
      mBackEnd->get_fks()->delete_node(row);
      [table noteNumberOfRowsChanged];
    }
  }
}


#pragma mark Table view drag n drop



- (BOOL) tableView: (NSTableView*) aTableView
writeRowsWithIndexes: (NSIndexSet*) rowIndices
      toPasteboard: (NSPasteboard*) pboard;
{
  BOOL shouldStartDrag = NO;
  
  NSUInteger rowIndex = [rowIndices firstIndex];
  NSMutableDictionary* dict = [NSMutableDictionary dictionary];
  [dict setObject: [NSNumber numberWithInt: rowIndex]
           forKey: @"rowIndex"];
  
  if (aTableView == mColumnsTable) {
    [pboard declareTypes: [NSArray arrayWithObject: @"com.sun.mysql.workbench.column"]
                   owner: self];
    [pboard setPropertyList: dict
                    forType: @"com.sun.mysql.workbench.column"];
    
    shouldStartDrag = YES;
  }
  
  if (shouldStartDrag) {
    [aTableView selectRow: rowIndex
     byExtendingSelection: NO];
  }
  
  return shouldStartDrag;
}



- (NSDragOperation) tableView: (NSTableView*) aTableView
                 validateDrop: (id <NSDraggingInfo>) info
                  proposedRow: (NSInteger) proposedRow
        proposedDropOperation: (NSTableViewDropOperation) operation;
{
  NSDragOperation op = NSDragOperationNone;
  
  NSPasteboard* pb = [info draggingPasteboard];
  NSDictionary* dict = nil;
  if (aTableView == mColumnsTable) {
    dict = [pb propertyListForType: @"com.sun.mysql.workbench.column"];
  }
  
  NSAssert( (dict != nil), @"Drag flavour was not found.");
  
  NSInteger originatingRow = [[dict objectForKey: @"rowIndex"] intValue];
  if ( ((proposedRow < originatingRow) || (proposedRow > originatingRow + 1))
      && (proposedRow < [self numberOfRowsInTableView: aTableView]) ) {
    [aTableView setDropRow: proposedRow
             dropOperation: NSTableViewDropAbove];
    op = NSDragOperationMove;
  }
  
  return op;
}



- (BOOL) tableView: (NSTableView*) aTableView
        acceptDrop: (id <NSDraggingInfo>) info
               row: (NSInteger) dropRow
     dropOperation: (NSTableViewDropOperation) operation;
{  
  BOOL didAccept = NO;

  NSPasteboard* pb = [info draggingPasteboard];
  NSInteger originatingRow = NSNotFound;
    
  if (aTableView == mColumnsTable) {
    NSDictionary* dict = [pb propertyListForType: @"com.sun.mysql.workbench.column"];
    NSAssert( (dict != nil), @"Drag flavour was not found.");

    originatingRow = [[dict objectForKey: @"rowIndex"] intValue];
    
    if (dropRow > originatingRow)
      dropRow --;    
    
    [mColumnsDataSource moveColumnAtRow: originatingRow
                                  toRow: dropRow];
    
    didAccept = YES;
  }
  
  if (didAccept) {
    [aTableView reloadData];
    // Select the dropped row in the table.
    [aTableView selectRow: dropRow
     byExtendingSelection: NO];
  }
  
  return didAccept;
}



#pragma mark -



- (void) setupInsertsTablePanel;
{
  {
    // Remove previous columns.
    id list = [mEditorInsertsTable tableColumns];
    int i, c = [list count];
    for (i = c - 1; i >= 0; i--) {
      [mEditorInsertsTable removeTableColumn: [list objectAtIndex: i]];
    }
  }
  
  mGrid->refresh();
  mBackEnd->update_inserts_grid();
  
  // Add columns for the current table.
  int i, c = mGrid->get_column_count();
  for (i = 0; i < c; i++) {
    NSString* columnTitle = [NSString stringWithUTF8String: mGrid->get_column_caption(i).c_str()];
    NSTableColumn* col = [[[NSTableColumn alloc] initWithIdentifier: [NSNumber numberWithInt: i]] autorelease];
    [[col headerCell] setStringValue: columnTitle];
    [mEditorInsertsTable addTableColumn: col];
  }
  
  // Compute the width of the table and its scroller.
  CGFloat width = 0;
  for (NSTableColumn* col in [mEditorInsertsTable tableColumns]) {
    width += [col width];
  }
  width += [[(NSScrollView*)[[mEditorInsertsTable superview] superview] verticalScroller] frame].size.width + 10;
  
  // Resize the window nicely to fit the table and scrollview.
  width = MAX(width, [mEditorInsertsPanel minSize].width);
  width = MIN(width, round([[NSScreen mainScreen] frame].size.width * 0.95));
  NSRect frame = [mEditorInsertsPanel frame];
  frame.size.width = width;
  [mEditorInsertsPanel setFrame: frame
                        display: NO];
  
  mGrid->enable_placeholder_row(true);
  
  [mEditorInsertsTable reloadData];
  
  if ([self numberOfRowsInTableView: mEditorInsertsTable] > 1) {
    // First select the first row...
    [mEditorInsertsTable selectRow: 0
              byExtendingSelection: NO];
    // ...then initiate editing of the first cell.
    [mEditorInsertsTable editColumn: 0
                                row: 0
                          withEvent: nil
                             select: YES];
  }
  
  // Posing the window modally will put it in the visual center position on screen.
  [NSApp runModalForWindow: mEditorInsertsPanel];
}



- (void) closeInsertsTablePanelCommitContents: (BOOL) yn;
{
  [mEditorInsertsPanel makeFirstResponder: mEditorInsertsPanel];
  
  if (yn) {
    mBackEnd->update_from_inserts_grid();
    [self refreshTableEditorGUIInsertsTab];
  }
  
  [NSApp stopModal];
  [mEditorInsertsPanel orderOut: self];
}



#pragma mark Combo box support



- (NSInteger) numberOfItemsInComboBoxCell: (NSComboBoxCell*) aComboBoxCell;
{
  return [mColumnTypes count];
}



- (id) comboBoxCell: (NSComboBoxCell*) aComboBoxCell
objectValueForItemAtIndex: (NSInteger) index;
{
  return [mColumnTypes objectAtIndex: index];
}



#pragma mark User interaction

- (IBAction) userPickPopup: (id) sender;
{
  NSString* popItemTitle = [sender titleOfSelectedItem];
  
  if (sender == mTableCollation) {
    if ([popItemTitle isEqualToString: @"Schema Default"])
      popItemTitle = @" - ";
    mBackEnd->set_table_option_by_name("CHARACTER SET - COLLATE", [popItemTitle UTF8String]);
  }
  else if (sender == mTableEngine) {
    mBackEnd->set_table_option_by_name("ENGINE", [popItemTitle UTF8String]);
  }
  else if( sender == mColumnsCollation) {
    int rowIndex = [mColumnsTable selectedRow];
    if ([popItemTitle isEqualToString: @"Table Default"])
      popItemTitle = @" - ";
    [mColumnsDataSource setStringValue: popItemTitle
                         forValueIndex: bec::TableColumnsListBE::CharsetCollation
                                   row: rowIndex];
  }
  else if (sender == mIndicesStorageTypes) {
    int rowIndex = [mIndicesTable selectedRow];
    NSString* storageType = popItemTitle;
    [mIndicesDataSource setStringValue: storageType
                         forValueIndex: MySQLTableIndexListBE::StorageType
                                   row: rowIndex];
  }
  
  else if (sender == mPartitionPopup) {
    mBackEnd->set_partition_type([popItemTitle UTF8String]);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionPopup) {
    if ([popItemTitle isEqualToString: @"Disabled"])
      popItemTitle = @"";
    mBackEnd->set_subpartition_type([popItemTitle UTF8String]);
    [self refreshTableEditorGUIPartitioningTab];
  }
  
  else if (sender == mFKOnUpdate) {
    int rowIndex = [mFKTable selectedRow];
    [mFKDataSource setStringValue: popItemTitle
                    forValueIndex: bec::FKConstraintListBE::OnUpdate
                              row: rowIndex];
  }
  else if (sender == mFKOnDelete) {
    int rowIndex = [mFKTable selectedRow];
    [mFKDataSource setStringValue: popItemTitle
                    forValueIndex: bec::FKConstraintListBE::OnDelete
                              row: rowIndex];
  }
  
  else if (sender == mOptionsPackKeys) {
    [[sender window] makeFirstResponder: sender];
    mBackEnd->set_table_option_by_name("PACK_KEYS", [popItemTitle UTF8String]);
  }
  else if (sender == mOptionsRowFormat) {
    [[sender window] makeFirstResponder: sender];
    mBackEnd->set_table_option_by_name("ROW_FORMAT", [popItemTitle UTF8String]);
  }
  else if (sender == mOptionsMergeMethod) {
    [[sender window] makeFirstResponder: sender];
    mBackEnd->set_table_option_by_name("INSERT_METHOD", [popItemTitle UTF8String]);
  }
  else {
    NSAssert1(NO, @"DEBUG - User selected unmatched popup menu. Sender: '%@'", sender);
  }
}



- (IBAction) userClickButton: (id) sender;
{
  if ([mColumnFlagCheckboxes containsObject: sender]) {
    // The button is one of the dynamically created checkboxes in the Columns section.
    [mColumnsDataSource setFlag: ([sender state] == NSOnState ? YES : NO)
                    forFlagName: [sender title]
                  atColumnIndex: [mColumnsTable selectedRow]];
    [mColumnsTable setNeedsDisplay: YES];
  }
  else if (sender == mPartitionEnabledCheckbox) {
    mBackEnd->set_partition_type( ([mPartitionEnabledCheckbox state] == NSOnState ? "HASH" : "") );
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mPartitionManualCheckbox) {
    mBackEnd->set_explicit_partitions( ([sender state] == NSOnState ? true : false) );
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionManualCheckbox) {
    mBackEnd->set_explicit_subpartitions( ([sender state] == NSOnState ? true : false) );
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mOptionsDelayKeyUpdates) {
    [[sender window] makeFirstResponder: sender];
    mBackEnd->set_table_option_by_name("DELAY_KEY_WRITE", ([sender state] == NSOnState ? "1" : "0"));
  }
  else if (sender == mOptionsUseChecksum) {
    [[sender window] makeFirstResponder: sender];
    mBackEnd->set_table_option_by_name("CHECKSUM", ([sender state] == NSOnState ? "1" : "0"));
  }
  else if (sender == mEditorInsertsOpenEditor) {
    [self setupInsertsTablePanel];
  }
  else if (sender == mEditorInsertsPanelCancel) {
    [self closeInsertsTablePanelCommitContents: NO];
  }
  else if (sender == mEditorInsertsPanelOK) {
    [self closeInsertsTablePanelCommitContents: YES];
  }
  
  else {
    NSAssert1(NO, @"DEBUG - User clicked unmatched button: '%@'", [sender title]);
  }
}



- (void) comboBoxSelectionDidChange: (NSNotification*) aNotification;
{
  id sender = [aNotification object];
  
  if (sender == mPartitionCountCombo) {
    int val = [[sender objectValueOfSelectedItem] intValue];
    mBackEnd->set_partition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionCountCombo) {
    int val = [[sender objectValueOfSelectedItem] intValue];
    mBackEnd->set_subpartition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
}



// Called by Cocoa after every keystroke in a text field.
- (void) controlTextDidChange: (NSNotification*) aNotification;
{
  id sender = [aNotification object];
  
  if (sender == mPartitionCountCombo) {
    int val = [sender intValue];
    mBackEnd->set_partition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionCountCombo) {
    int val = [sender intValue];
    mBackEnd->set_subpartition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
  
  else {
    // We want to commit the changes to text fields as the user types, but we use a delay so not to blow up the undo and history lists.
    [self performSelector: @selector(controlTextDidEndEditing:)
               withObject: aNotification
               afterDelay: 1];
  }
}



// Called by Cocoa when keyboard focus leaves a text field.
- (void) controlTextDidEndEditing: (NSNotification*) aNotification;
{
  
  // First cancel any pending calls originating in performSelector:withObject:afterDelay:.
  [NSRunLoop cancelPreviousPerformRequestsWithTarget: self];
  
  id sender = [aNotification object];

  if (sender == mTableName) {
    mBackEnd->set_name([[mTableName stringValue] UTF8String]);    
    [self updateTitle: [self title]];
    
    // Try if the user ended editing by pressing the Return key or the Enter key.
    NSEvent* theEvent = [[sender window] currentEvent];
    if ([theEvent type] == NSKeyDown) {
      unsigned short key = [theEvent keyCode];
      if ( (key == 36) || (key == 52) || (key == 76) ) {
        // User pressed return or enter key.
        [self performSelector: @selector(switchToColumnsTab)
                   withObject: nil
                   afterDelay: 0];
      }
    }    
  }
  else if (sender == mColumnsTable) {
    ; // Ignore.
  }
  else if (sender == mIndicesTable) {
    ; // Ignore.
  }
  else if (sender == mIndexColumnsTable) {
    ; // Ignore.
  }
  else if (sender == mIndicesBlockSize) {
    int rowIndex = [mIndicesTable selectedRow];
    NSString* blockSize = [mIndicesBlockSize stringValue];
    [mIndicesDataSource setStringValue: blockSize
                         forValueIndex: MySQLTableIndexListBE::RowBlockSize
                              row: rowIndex];
  }
  else if (sender == mIndicesParser) {
    int rowIndex = [mIndicesTable selectedRow];
    NSString* blockSize = [mIndicesParser stringValue];
    [mIndicesDataSource setStringValue: blockSize
                         forValueIndex: MySQLTableIndexListBE::Parser
                              row: rowIndex];
  }

  else if (sender == mFKTable) {
    ; // Ignore.
  }
  
  else if (sender == mPartitionParametersTextField) {
    NSString* partExpr = [sender stringValue];
    mBackEnd->set_partition_expression([partExpr UTF8String]);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mPartitionCountCombo) {
    // Handle this case in -controlTextDidChange: for instant feedback table redraw.
    ;
  }
  else if (sender == mSubPartitionParametersTextField) {
    NSString* subpartExpr = [sender stringValue];
    mBackEnd->set_subpartition_expression([subpartExpr UTF8String]);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionCountCombo) {
    // Handle this case in -controlTextDidChange: for instant feedback table redraw.
    ;
  }
  
  else if (sender == mOptionsTablePassword) {
    mBackEnd->set_table_option_by_name("PASSWORD", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsAutoIncrement) {
    mBackEnd->set_table_option_by_name("AUTO_INCREMENT", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsAvgRowLength) {
    mBackEnd->set_table_option_by_name("AVG_ROW_LENGTH", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsMinRows) {
    mBackEnd->set_table_option_by_name("MIN_ROWS", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsMaxRows) {
    mBackEnd->set_table_option_by_name("MAX_ROWS", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsDataDirectory) {
    mBackEnd->set_table_option_by_name("DATA DIRECTORY", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsIndexDirectory) {
    mBackEnd->set_table_option_by_name("INDEX DIRECTORY", [[sender stringValue] UTF8String]);
  }
  else if (sender == mOptionsUnionTables) {
    mBackEnd->set_table_option_by_name("UNION", [[sender stringValue] UTF8String]);
  }
  else if (sender == mEditorInsertsTable) {
    ; // Ignore.
  }
  
  else {
    NSAssert1(NO, @"DEBUG - Unknown text field: %@", sender);
  }
}



// Called by Cocoa when keyboard focus leaves a text view.
- (void) textDidEndEditing: (NSNotification*) aNotification;
{
  // First cancel any pending calls originationg in performSelector:withObject:afterDelay:.
  [NSRunLoop cancelPreviousPerformRequestsWithTarget: self];
  
  id sender = [aNotification object];
  
  if (sender == mTableComment) {
    mBackEnd->set_comment([[mTableComment string] UTF8String]);
  }
  else if (sender == mColumnsComment) {
    int rowIndex = [mColumnsTable selectedRow];
    [mColumnsDataSource setStringValue: [mColumnsComment string]
                         forValueIndex: bec::TableColumnsListBE::Comment
                              row: rowIndex];
  }
  else if (sender == mEditorTriggers) {
    const std::string stdStr = [[mEditorTriggers string] UTF8String];
    mBackEnd->set_triggers_sql(stdStr);
  }
  else if (sender == mIndicesComment) {
    int rowIndex = [mIndicesTable selectedRow];
    [mIndicesDataSource setStringValue: [mIndicesComment string]
                         forValueIndex: bec::IndexListBE::Comment
                              row: rowIndex];
  }
  else if (sender == mFKComment) {
    int rowIndex = [mFKTable selectedRow];
    [mFKDataSource setStringValue: [mFKComment string]
                    forValueIndex: bec::FKConstraintListBE::Comment
                         row: rowIndex];
  }
  else {
    NSAssert1(NO, @"DEBUG - Unknown text view: %@", sender);
  }
}


#pragma mark Super class overrides



- (id) identifier;
{
  // An identifier for this editor (just take the object id).
  return [NSString stringWithCPPString:mBackEnd->get_object().id()];
}



- (NSString*) title;
{
  // The title for the editor.
  return [NSString stringWithCPPString: strfmt("%s - Table", mBackEnd->get_name().c_str())];
}



- (NSView*) dockableView;
{  
  return mEditorsTabView;
}



- (BOOL)matchesIdentifierForClosingEditor:(NSString*)identifier
{
  return mBackEnd->should_close_on_delete_of([identifier UTF8String]);
}



#pragma mark Creation + Destruction



- (id) initWithModule: (grt::Module*) module
           GRTManager: (bec::GRTManager*) grtm
            arguments: (const grt::BaseListRef&) args;
{
  self = [super initWithModule: module
                    GRTManager: grtm
                     arguments: args];
  
  if (self != nil) {
    if (![[NSBundle bundleForClass:[self class]] loadNibFile:@"MySQLTableEditor"
                                           externalNameTable:[NSDictionary dictionaryWithObject:self forKey:NSNibOwner] 
                                                    withZone:nil])
      NSLog(@"Could not load MySQLTableEditor.xib");
    
    [self reinitWithArguments: args];
  }
  
  return self;
}


- (void)reinitWithArguments:(const grt::BaseListRef&)args
{  
  [super reinitWithArguments: args];
  
  delete mBackEnd;
  
  mBackEnd = new MySQLTableEditorBE(_grtm, db_mysql_TableRef::cast_from(args[0]), get_rdbms_for_db_object(args[0]));
  [mColumnsDataSource release];
  mColumnsDataSource = [[MacTableEditorColumnsInformationSource alloc] initWithListModel: mBackEnd->get_columns()
                                                                            tableBackEnd: mBackEnd];
  [mIndicesDataSource release];
  mIndicesDataSource = [[MacTableEditorInformationSource alloc] initWithListModel: mBackEnd->get_indexes()];
  
  [mIndexColumnsDataSource release];
  mIndexColumnsDataSource = [[MacTableEditorIndexColumnsInformationSource alloc] initWithListModel: mBackEnd->get_indexes()->get_columns()
                                                                                      tableBackEnd: mBackEnd];
  
  [mFKDataSource release];
  mFKDataSource = [[MacTableEditorInformationSource alloc] initWithListModel: mBackEnd->get_fks()];
  
  [mFKColumnsDataSource release];
  mFKColumnsDataSource = [[MacTableEditorFKColumnsInformationSource alloc] initWithListModel: mBackEnd->get_fks()->get_columns()
                                                                                tableBackEnd: mBackEnd];
  
  mGrid = mBackEnd->get_inserts_grid();
  
  // Populate popup menus.
  MFillPopupButtonWithStrings(mTableCollation, mBackEnd->get_charset_collation_list());
  [[mTableCollation menu] insertItem: [NSMenuItem separatorItem]
                             atIndex: 0];
  [mTableCollation insertItemWithTitle: @"Schema Default"
                               atIndex: 0];
  
  MFillPopupButtonWithStrings(mTableEngine, mBackEnd->get_engines_list());
  
  MFillPopupButtonWithStrings(mColumnsCollation, mBackEnd->get_charset_collation_list());
  [[mColumnsCollation menu] insertItem: [NSMenuItem separatorItem]
                               atIndex: 0];
  [mColumnsCollation insertItemWithTitle: @"Table Default"
                                 atIndex: 0];
  
  id col = [mIndicesTable tableColumnWithIdentifier: @"type"];
  id cell = [col dataCell];
  MFillPopupButtonWithStrings(cell, mBackEnd->get_index_types());
  MFillPopupButtonWithStrings(mIndicesStorageTypes, mBackEnd->get_index_storage_types());
  MFillPopupButtonWithStrings(mFKOnUpdate, mBackEnd->get_fk_action_options());
  MFillPopupButtonWithStrings(mFKOnDelete, mBackEnd->get_fk_action_options());
  
  // Create column type list.
  [mColumnTypes release];
  mColumnTypes = [MArrayFromStringVector(((MySQLTableColumnsListBE*)mBackEnd->get_columns())->get_datatype_names()) retain];
  
  // Set up combo boxes in Partitioning tab.  
  [mPartitionsTreeDataSource release];
  mPartitionsTreeDataSource = [[GRTTreeDataSource alloc] initWithTreeModel: mBackEnd->get_partitions()];
  [mPartitionTable setDataSource: mPartitionsTreeDataSource];
  [mPartitionTable setDelegate: mPartitionsTreeDataSource];
  NSTableColumn* column = [mPartitionTable tableColumnWithIdentifier: @"0"];
  MTextImageCell* imageTextCell2 = [MTextImageCell new];
  [column setDataCell: imageTextCell2];
    
  NSUInteger index= [mEditorsTabView indexOfTabViewItemWithIdentifier: @"privileges"];
  if (index != NSNotFound)
    [mEditorsTabView removeTabViewItem: [mEditorsTabView tabViewItemAtIndex: index]];
  
  [mPrivilegesTab release];
  mPrivilegesTab= [[DbPrivilegeEditorTab alloc] initWithObjectEditor: mBackEnd];
  
  NSTabViewItem* item = [[[NSTabViewItem alloc] initWithIdentifier: @"privileges"] autorelease];
  [item setView: [mPrivilegesTab view]];
  [item setLabel: @"Privileges"];
  [mEditorsTabView addTabViewItem: item];
  
  // Register a callback that will call [self refresh] when the edited object is
  // changed from somewhere else in the application.
  mBackEnd->set_refresh_ui_slot(sigc::bind(sigc::ptr_fun(call_refresh), self));    
  
  [self setupEditor];
  
  // Update the GUI.
  [self refreshTableEditorGUI];
}


- (void) awakeFromNib;
{
  // Store the min size specified in the .xib file.
  [self setMinimumSize: [mEditorsTabView frame].size];
 
  // Assemle all the separate editor views into the tab view.
  NSTabViewItem* item = [[[NSTabViewItem alloc] initWithIdentifier: @"table"] autorelease];
  [item setView: mEditorTable];
  [item setLabel: @"Table"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"columns"] autorelease];
  [item setView: mEditorColumns];
  [item setLabel: @"Columns"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"indices"] autorelease];
  [item setView: mEditorIndices];
  [item setLabel: @"Indices"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"foreignkeys"] autorelease];
  [item setView: mEditorForeignKeys];
  [item setLabel: @"Foreign Keys"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"triggers"] autorelease];
  [item setView: mEditorTriggers];
  [item setLabel: @"Triggers"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"partitioning"] autorelease];
  [item setView: mEditorPartitioning];
  [item setLabel: @"Partitioning"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"options"] autorelease];
  NSScrollView* sv = [[NSScrollView alloc] initWithFrame: [mEditorTable frame]];
  [sv setDocumentView: mEditorOptions];
  [sv setHasHorizontalScroller: YES];
  [sv setHasVerticalScroller: YES];
  [[sv horizontalScroller] setControlSize: NSSmallControlSize];
  [[sv verticalScroller] setControlSize: NSSmallControlSize];
  [sv setAutohidesScrollers: YES];
  [mEditorOptions scrollRectToVisible: NSMakeRect(0, [mEditorOptions frame].size.height, 1, 1)];
  [item setView: sv];
  [item setLabel: @"Options"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"inserts"] autorelease];
  [item setView: mEditorInserts];
  [item setLabel: @"Inserts"];
  [mEditorsTabView addTabViewItem: item];

  [mColumnsTable registerForDraggedTypes: [NSArray arrayWithObject: @"com.sun.mysql.workbench.column"]];

  int i, c = 10;
  for (i = 2; i <= c; i++) {
    [mPartitionCountCombo addItemWithObjectValue: [NSNumber numberWithInt: i]];
    [mSubpartitionCountCombo addItemWithObjectValue: [NSNumber numberWithInt: i]];
  }
  
  mDidAwakeFromNib = YES;
}

//--------------------------------------------------------------------------------------------------

- (void) dealloc;
{
  [mPrivilegesTab release];
  [mColumnsDataSource release];
  [mColumnTypes release];
  [mIndicesDataSource release];
  [mIndexColumnsDataSource release];
  [mFKDataSource release];
  [mPartitionsTreeDataSource release];
  [mFKColumnsDataSource release];
  
  delete mBackEnd;
  
  [super dealloc];
}

//--------------------------------------------------------------------------------------------------

/**
 * Callback from the backend telling us about syntax errors it founds.
 */
static int process_syntax_error(const int line, const int err_tok_line_pos, const int err_tok_len,
                                const std::string& err_msg, DbMysqlTableEditor* self)
{
  // If this is the first new error then reset all error markers.
  // Don't do this in advance in checkSyntax, otherwise we get some ugly flickering.
  if (self->mErrorCount == 0)
    [self resetSyntaxErrors];
  
  Scintilla::ScintillaCocoa *backend= self->mEditorTriggers.backend;
  int line_start_pos= backend->WndProc(SCI_POSITIONFROMLINE, line - 1, 0);
  
  backend->WndProc(SCI_SETINDICATORCURRENT, 0, 0);
  backend->WndProc(SCI_INDICATORFILLRANGE, line_start_pos + err_tok_line_pos, err_tok_len);
  
  backend->WndProc(SCI_MARKERADD, line - 1, 0);
  
  ++self->mErrorCount;
  
  return 0;
}

//--------------------------------------------------------------------------------------------------

/**
 * Prepares the code editor and the backend to have proper syntax highlighting, error parsing etc.
 */
- (void) setupEditor
{  
  [[NSNotificationCenter defaultCenter] removeObserver: self
                                                  name: NSTextDidChangeNotification
                                                object: mEditorTriggers];
  
  [[NSNotificationCenter defaultCenter] addObserver: self
                                           selector: @selector(textDidChange:)
                                               name: NSTextDidChangeNotification
                                             object: mEditorTriggers];
  [WBPluginEditorBase setupCodeEditor: mEditorTriggers backend: mBackEnd->get_sql_editor() withStatus: YES];
  [mEditorTriggers setStatusText: @"No errors found"];
  
  // Initial text.
  std::string s = mBackEnd->get_all_triggers_sql();
  NSString* str = [NSString stringWithUTF8String: s.c_str()];
  [mEditorTriggers setString: str];
  
  // Connect the parser with our callback to get notifications about syntax errors.
  mBackEnd->get_sql_editor()->sql_parser_err_cb(sigc::bind(sigc::ptr_fun(process_syntax_error), self));
}

//--------------------------------------------------------------------------------------------------

/**
 * Called when text was added or removed in the editor.
 */
- (void) textDidChange: (NSNotification*) aNotification
{
  if ([aNotification object] == mTableComment)
  {
    // Set comment for the table.
    mBackEnd->set_comment([[mTableComment string] UTF8String]);
  }
  else
    if ([aNotification object] == mEditorTriggers)
    {
      // Stop the timer in case it is about to trigger.
      [mSyntaxCheckTimer invalidate];
      
      // Set up a new timer.
      mSyntaxCheckTimer= [NSTimer scheduledTimerWithTimeInterval: 0.5
                                                          target: self
                                                        selector: @selector(checkSyntax:)
                                                        userInfo: nil
                                                         repeats: NO];
    }
};

//--------------------------------------------------------------------------------------------------

/**
 * Remove all markers we set for previous syntax errors.
 */
- (void) resetSyntaxErrors
{
  [mEditorTriggers setStatusText: @"No errors found"];
  int length= mEditorTriggers.backend->WndProc(SCI_GETLENGTH, 0, 0);
  
  [mEditorTriggers setGeneralProperty: SCI_SETINDICATORCURRENT parameter: 0 value: 0];
  [mEditorTriggers setGeneralProperty: SCI_INDICATORCLEARRANGE parameter: 0 value: length];
  
  [mEditorTriggers setGeneralProperty: SCI_MARKERDELETEALL parameter: -1 value: 0];
}

//--------------------------------------------------------------------------------------------------

/**
 * Triggered once a change in the editor content happend and there was a pause of at least 500 ms.
 */
- (void) checkSyntax:(NSTimer*) timer
{
  mSyntaxCheckTimer = nil;
  NSString *text;
  
  // Before syntax checking store all changes.
  [self performSelector: @selector(textDidEndEditing:)
             withObject: [NSNotification notificationWithName: NSTextDidEndEditingNotification
                                                       object: (id) mEditorTriggers]];
  
  text= [mEditorTriggers string];
  if (text)
  {
    [mEditorTriggers setStatusText: @"Parsing SQL ..."];
    mBackEnd->get_sql_editor()->sql([text UTF8String]);
    
    mErrorCount = 0;
    mBackEnd->get_sql_editor()->check_sql();
    
    // If there was no syntax error then reset any remaining error markers now.
    if (mErrorCount == 0)
      [self resetSyntaxErrors];
    else
      [mEditorTriggers setStatusText: [NSString stringWithFormat: @"%d error(s) found.", mErrorCount]];
  }
  else
    [self resetSyntaxErrors];
}

//--------------------------------------------------------------------------------------------------

@end


