/* 
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

/**
 * @abstract
 * The main controller for the MySQL Table Editor.
 *
 * @ingroup
 * MySQL Table Editor
*/

#include "base/geometry.h"
#include "base/string_utilities.h"

#import "MySQLTableEditor.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 "MTextImageCell.h"
#import "GRTTreeDataSource.h"
#import "MResultsetViewer.h"
#import "MTabSwitcher.h"
#import "MColoredView.h"
#import "ScintillaView.h"
#import "MVerticalLayoutView.h"

#import "WBTabItem.h" // needed for WBCustomTabItemView

#import "DbPrivilegeEditorTab.h"
#include <mforms/view.h>

static 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: YES];
}

@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];
    
    [mSchemaName setStringValue: [NSString stringWithCPPString: mBackEnd->get_schema_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]];
    [mColumnsName setStringValue: columnName];
    [mColumnsType setStringValue: [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Type 
                                                                           row: rowIndex]];
    [mColumnsDefault setStringValue: [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Default
                                                                              row: rowIndex]];
    
    NSString* comments = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Comment
                                                                  row: rowIndex];
    [mColumnsComment setString: comments];
    
    if (mBackEnd->is_editing_live_object())
    {
      NSArray *flags = MArrayFromStringVector(mBackEnd->get_columns()->get_datatype_flags(rowIndex, true));
      int flag;
      mBackEnd->get_columns()->get_field(rowIndex, bec::TableColumnsListBE::IsPK, flag);
      [mColumnsFlagPK setState: flag ? NSOnState : NSOffState];
      mBackEnd->get_columns()->get_field(rowIndex, bec::TableColumnsListBE::IsNotNull, flag);
      [mColumnsFlagNN setState: flag ? NSOnState : NSOffState];
      mBackEnd->get_columns()->get_field(rowIndex, bec::TableColumnsListBE::IsUnique, flag);
      [mColumnsFlagUNQ setState: flag ? NSOnState : NSOffState];

      [mColumnsFlagBIN setEnabled: [flags containsObject: @"BINARY"]];
      [mColumnsFlagBIN setState: mBackEnd->get_columns()->get_column_flag(rowIndex, "BINARY") ? NSOnState : NSOffState];
      [mColumnsFlagUN setEnabled: [flags containsObject: @"UNSIGNED"]];
      [mColumnsFlagUN setState: mBackEnd->get_columns()->get_column_flag(rowIndex, "UNSIGNED") ? NSOnState : NSOffState];
      [mColumnsFlagZF setEnabled: [flags containsObject: @"ZEROFILL"]];
      [mColumnsFlagZF setState: mBackEnd->get_columns()->get_column_flag(rowIndex, "ZEROFILL") ? NSOnState : NSOffState];

      mBackEnd->get_columns()->get_field(rowIndex,MySQLTableColumnsListBE::IsAutoIncrementable, flag);
      [mColumnsFlagAI setEnabled: flag];
      mBackEnd->get_columns()->get_field(rowIndex, MySQLTableColumnsListBE::IsAutoIncrement, flag);
      [mColumnsFlagAI setState: flag ? NSOnState : NSOffState];
    }
  }
}



- (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];
    
    bool flag = false;
    mBackEnd->get_fks()->get_field(rowIndex, bec::FKConstraintListBE::ModelOnly, flag);
    [mFKModelOnly setState: flag ? NSOnState : NSOffState];
    
    [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];
  [mPartitionCountTextField 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];
  [mSubpartitionCountTextField 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];
    [mPartitionCountTextField 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];
    [mSubpartitionCountTextField 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 = @"Don't Use";
  [mOptionsMergeMethod selectItemWithTitle: [option capitalizedString]];
}



- (void) refreshTableEditorGUIInsertsTab;
{
  if (mBackEnd->get_table()->columns().count() > 0)
    [mEditorInsertsController refreshFull];
}


- (void)refreshTableEditorGUITriggersTab
{
}


- (void)refreshTableEditorGUI;
{
  [self refreshTableEditorGUITableTab];
  [self refreshTableEditorGUIColumnsTab];
  [self refreshTableEditorGUIIndicesTab];
  [self refreshTableEditorGUIFKTab];
  [self refreshTableEditorGUITriggersTab];
  [self refreshTableEditorGUIPartitioningTab];
  [self refreshTableEditorGUIOptionsTab];
  if (!mBackEnd->is_editing_live_object())
    [self refreshTableEditorGUIInsertsTab];
}


/**
 * Converts the current placeholder (the last line in the columns grid into a real column.
 */
- (void)activateColumnPlaceholder: (NSUInteger) rowIndex;
{
  // 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.
  
  // Tell the backend we are editing now the placeholder row.
  [mColumnsDataSource setIntValue: 1
                    forValueIndex: bec::TableColumnsListBE::Name
                              row: rowIndex];
  
  // Get the default value for the name field...
  id value = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Name
                                                      row: rowIndex];
  
  // ... and set it in the backend. This way the backend will know next time
  // we set a value that we need a new place holder.
  [mColumnsDataSource setStringValue: value
                       forValueIndex: bec::TableColumnsListBE::Name
                                 row: rowIndex];
  
  [mColumnsTable reloadData];
}

- (void) switchToColumnsTab;
{
  if (![[[mEditorsTabView selectedTabViewItem] identifier] isEqual: @"columns"])
  {
    [mEditorsTabView selectTabViewItemWithIdentifier: @"columns"];
    
    [mColumnsDataSource refresh];
    
    NSInteger lastRowIndex = [self numberOfRowsInTableView: mColumnsTable] - 1;
    
    // Select the last row. There is always at least one row, because of the placeholder.
    if (lastRowIndex < 0)
      [mColumnsTable deselectAll: nil];
    else
      [mColumnsTable selectRowIndexes: [NSIndexSet indexSetWithIndex: lastRowIndex]
                 byExtendingSelection: NO];
    if (lastRowIndex == 0)
    {
      // If there is *only* the placeholder then make it a normal row (which adds a new placeholder)
      // and set some default values for it. Start editing its name after that.
      [self activateColumnPlaceholder: 0];
      [mColumnsTable editColumn: 0
                            row: 0
                      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 {
    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: @"unique"]) {
      valueIndex = bec::TableColumnsListBE::IsUnique;
    }    
    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";
  }
  
  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();
  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: @"unique"]) {
      valueIndex = bec::TableColumnsListBE::IsUnique;
      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 selectRowIndexes: [NSIndexSet indexSetWithIndex: 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)
    {
      // If we currently edit the placeholder then convert it into a normal row
      // (adding thereby a new placeholder row), load default name and default type.
      NSInteger lastRowIndex = [self numberOfRowsInTableView: mColumnsTable] - 1;
      if (lastRowIndex == rowIndex)
        [self activateColumnPlaceholder: rowIndex];
      [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];
      if (!mBackEnd->is_editing_live_object())
        [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 < (NSUInteger)[[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 < (NSUInteger)[[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 < (NSUInteger)[[cell menu] numberOfItems]) {
        NSString* title  = [[[cell menu] itemAtIndex: menuItemIndex] title];
        [mFKColumnsDataSource setStringValue: title
                               forValueIndex: bec::FKConstraintColumnsListBE::RefColumn
                                         row: rowIndex];
      }
      
      [self refreshTableEditorGUIFKTab];
    }
  }
    
  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];
    }
  }  
  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];
    }
  }
}

- (BOOL)tableView: (NSTableView *)aTableView
shouldEditTableColumn: (NSTableColumn *)aTableColumn
              row: (NSInteger)rowIndex
{
  // Activate the placeholder row and set the default value if this is the name column.
  if (aTableView == mColumnsTable && rowIndex == [mColumnsDataSource numberOfRowsInTableView: aTableView] - 1) {
    NSString* columnName = [mColumnsDataSource objectValueForValueIndex: bec::TableColumnsListBE::Name
                                                                    row: rowIndex];
    if (columnName.length == 0) {
      // Mark this row as placeholder. This activates special handling in the backend
      // (e.g. default value generation).
      mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::Name, 1);
    }
  }
  return YES;
}

#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 selectRowIndexes: [NSIndexSet indexSetWithIndex: 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 selectRowIndexes: [NSIndexSet indexSetWithIndex: dropRow]
            byExtendingSelection: NO];
  }
  
  return didAccept;
}


#pragma mark Combo box support



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



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


- (NSString *)comboBoxCell:(NSComboBoxCell *)aComboBoxCell completedString:(NSString *)uncompletedString
{
  NSString *upper = [uncompletedString uppercaseString];
  for (NSString *s in mColumnTypes)
  {
    if ([s hasPrefix: upper])
      return s;
  }
  return nil;
}


- (NSUInteger)comboBoxCell:(NSComboBoxCell *)aComboBoxCell indexOfItemWithStringValue:(NSString *)aString
{
  NSString *upper = [aString uppercaseString];
  
  return [mColumnTypes indexOfObject: upper];
}


#pragma mark User interaction

// Show FK editor or disable it by showing a placeholder with info depending on the currently
// selected table engine.
- (void) updateFKPlaceholder
{
  [mFKWarningPanel setHidden: !mBackEnd->is_editing_live_object() || mBackEnd->engine_supports_foreign_keys()];
}

- (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]);
    [self updateFKPlaceholder];
  }
  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];
    if ([sender indexOfSelectedItem] == 0)
      popItemTitle= @""; // Reset to nothing to remove the option entirely.
    mBackEnd->set_table_option_by_name("INSERT_METHOD", [[popItemTitle uppercaseString] UTF8String]);
  }
  else {
    NSAssert1(NO, @"DEBUG - User selected unmatched popup menu. Sender: '%@'", sender);
  }
}


- (void)toggleHeader:(BOOL)flag
{
  float collapsedHeight = 38;
  float expandedHeight = 130;
  NSRect rect = [[self view] frame];
  NSRect hrect = [mHeaderView frame];
  NSRect trect = [mEditorsTabView frame];
  
  if (flag)
    hrect.size.height = expandedHeight;
  else
    hrect.size.height = collapsedHeight;

  hrect.origin.y = NSMaxY(rect) - NSHeight(hrect) + 1;
  trect.size.height = NSHeight(rect) - NSMinY(trect) - NSHeight(hrect) + 1;
  [mHeaderView setFrame: hrect];
  [mEditorsTabView setFrame: trect];
  [[mTableComment enclosingScrollView] setHidden: !flag];
  
  [[mHeaderView viewWithTag: 1] setFrame: flag ? NSMakeRect(9, NSHeight(hrect) - 48 - 9, 48, 48) : NSMakeRect(9, NSHeight(hrect) - 24 - 9, 24, 24)];
    
  for (id view in [mHeaderView subviews])
  {
    if ([view tag] >= 100)
      [view setHidden: !flag];
  }
}


- (IBAction) userClickButton: (id) sender
{
  if (sender == mColumnsFlagPK)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsPK, [sender state] == NSOnState);
  else if (sender == mColumnsFlagNN)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsNotNull, [sender state] == NSOnState);
  else if (sender == mColumnsFlagUNQ)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsUnique, [sender state] == NSOnState);
  else if (sender == mColumnsFlagBIN)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsBinary, [sender state] == NSOnState);
  else if (sender == mColumnsFlagUN)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsUnsigned, [sender state] == NSOnState);
  else if (sender == mColumnsFlagZF)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], bec::TableColumnsListBE::IsZerofill, [sender state] == NSOnState);
  else if (sender == mColumnsFlagAI)
    mBackEnd->get_columns()->set_field([mColumnsTable selectedRow], MySQLTableColumnsListBE::IsAutoIncrement, [sender state] == NSOnState);
  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 == mFKModelOnly) 
  {
    if ([mFKTable selectedRow] >= 0)
      mBackEnd->get_fks()->set_field([mFKTable selectedRow], 
                                     bec::FKConstraintListBE::ModelOnly,
                                     [sender state] == NSOnState);
  }
  else if (sender == mHeaderExpander)
  {
    [self toggleHeader: [sender state] == NSOnState];
  }
  else {
    NSAssert1(NO, @"DEBUG - User clicked unmatched button: '%@'", [sender title]);
  }
}

/**
 * Called by Cocoa after every keystroke in a text field. This is used to update parts of
 * the UI at every keystroke.
 * TODO: reconsider this handling. Doing a full page refresh for evey keystroke is just nonsense.
 */
- (void)controlTextDidChange: (NSNotification*)aNotification
{
  id sender = [aNotification object];
  
  if (sender == mPartitionCountTextField) {
    int val = [sender intValue];
    mBackEnd->set_partition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
  else if (sender == mSubpartitionCountTextField) {
    int val = [sender intValue];
    mBackEnd->set_subpartition_count(val);
    [self refreshTableEditorGUIPartitioningTab];
  }
}



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

  id sender = [aNotification object];

  // For text fields in a table view this is always the table view itself, not any of the text fields.
  if (sender == mColumnsTable)
  {
    NSText *text = [[aNotification userInfo] objectForKey: @"NSFieldEditor"];

    // We can use a generic call here because the order of the columns defined in the table view is the same
    // as that of the column type enum. If that ever changes we need to take care here too.
    if (text != nil && text.string != nil)
      mBackEnd->get_columns()->set_field([sender editedRow], [sender editedColumn], [text.string UTF8String]);

    return;
  }
  else if ([sender isKindOfClass: [NSTableView class]])
    return;

  if (sender == mTableName) {
    mBackEnd->set_name([[mTableName stringValue] UTF8String]);
    [self updateTitle: [self title]];
  }
  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 == mPartitionCountTextField) {
    // 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 == mSubpartitionCountTextField) {
    // 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 {
    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 isKindOfClass: [ScintillaView class]]) {
    // Works because we only have one code editor.
    mBackEnd->commit_changes();
  }
  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);
  }
}


- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
{
  if (splitView == mColumnsSplitter && ![splitView isVertical])
    return MIN(proposedMax, NSHeight([splitView frame]) - 150.0);
  return proposedMax;
}

#pragma mark Super class overrides

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


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


- (void)pluginDidShow: (id)sender
{
  [[[self view] window] makeFirstResponder: mTableName];
}


- (BOOL)pluginWillClose: (id)sender
{
  if (mEditorInsertsController && [mEditorInsertsController hasPendingChanges])
  {
    int ret = NSRunAlertPanel(@"Close Table Editor",
                              [NSString stringWithFormat: @"There are unsaved changes to the INSERTs data for %@. If you do not save, these changes will be discarded.",
                               [NSString stringWithCPPString: mBackEnd->get_name()]],
                              @"Save Changes", @"Don't Save", @"Cancel");
    if (ret == NSAlertDefaultReturn)
    {
      [mEditorInsertsController saveEdits: self];
    }
    else if (ret == NSAlertAlternateReturn)
    {
      [mEditorInsertsController discardEdits: self];
    }
    else
      return NO;
  }
  return [super pluginWillClose: sender];
}

#pragma mark Creation + Destruction



- (id) initWithModule: (grt::Module*) module
           GRTManager: (bec::GRTManager*) grtm
            arguments: (const grt::BaseListRef&) args;
{
  self = [super initWithNibName:@"MySQLTableEditor" bundle: [NSBundle bundleForClass: [self class]]];
  if (self != nil) {
    _grtm = grtm;
    
    [self loadView];
    
    [self reinitWithArguments: args];
  }
  
  return self;
}


- (void)reinitWithArguments:(const grt::BaseListRef&)args
{
  BOOL isReinit = mBackEnd != 0;
  
  [super reinitWithArguments: args];
  
  [[mEditorInsertsController view] removeFromSuperview];
  [mEditorInsertsController release];
  delete mBackEnd;

  db_mysql_TableRef table = db_mysql_TableRef::cast_from(args[0]);
  mBackEnd = new MySQLTableEditorBE(_grtm, table, get_rdbms_for_db_object(table));
  
  if (!isReinit)
  {
    if (!mBackEnd->is_editing_live_object())
    {
      mUnusedColumnsDetailsBox = mColumnsDetailsBox;
      mColumnsDetailsBox = mColumnsDetailsBox2;
      mColumnsCollation = mColumnsCollation2;
      mColumnsComment = mColumnsComment2;
      [mColumnsSplitter setVertical: YES];
    }
    else
    {
      mUnusedColumnsDetailsBox = mColumnsDetailsBox2;
      [mColumnsSplitter setVertical: NO];
    }
    [mColumnsSplitter addSubview: mColumnsDetailsBox];
  }

  [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];
  
  if (!mBackEnd->is_editing_live_object())
  {
    if ([mEditorsTabView indexOfTabViewItemWithIdentifier: @"inserts"] == NSNotFound)
    {
      id item = [[[NSTabViewItem alloc] initWithIdentifier: @"inserts"] autorelease];
      [item setView: mEditorInserts];
      [item setLabel: @"Inserts"];
      [mEditorsTabView addTabViewItem: item];
    }
    mEditorInsertsController = [[MResultsetViewer alloc] initWithRecordset: mBackEnd->get_inserts_model()];
    [mEditorInserts addSubview: [mEditorInsertsController view]];
    [mEditorInserts setAutoresizesSubviews: YES];
    [[mEditorInsertsController view] setFrame: [mEditorInserts bounds]];
    [[mEditorInsertsController view] setAutoresizingMask: NSViewMinXMargin|NSViewMaxXMargin|NSViewMinYMargin|NSViewMaxYMargin|NSViewWidthSizable|NSViewHeightSizable];
  }
  
  // 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];
    
  if (!mBackEnd->is_editing_live_object())
  {
    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];
    [(MColoredView*)[mPrivilegesTab view] setBackgroundColor: [NSColor whiteColor]];
  }
  // 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(boost::bind(call_refresh, self));    
  
  [self updateFKPlaceholder];
  {
    id view = mBackEnd->get_trigger_panel()->get_data();
    [mTriggerTabItem addSubview: view];
    [mTriggerTabItem setBackgroundColor: [NSColor whiteColor]];
    [view setFrame: [mTriggerTabItem bounds]];
    [view setAutoresizesSubviews: YES];
    [view setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable|NSMinXEdge|NSMinYEdge|NSMaxXEdge|NSMaxYEdge];
  }
  
  // Update the GUI.
  [self refreshTableEditorGUI];
}


- (void)awakeFromNib;
{
  [mTabSwitcher setTabStyle: MEditorBottomTabSwitcher];
  
  // collapse header by default
  [mHeaderExpander setState: NSOffState];
  [self toggleHeader: NO];
    
  // Store the min size specified in the .xib file.
  NSSize size = [[self view] frame].size;
  [self setMinimumSize: size];
 
  // Assemle all the separate editor views into the tab view.
  NSTabViewItem* item;
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"columns"] autorelease];
  [item setView: mEditorColumns];
  [item setLabel: @"Columns"];
  [mEditorsTabView addTabViewItem: item];
  [mEditorColumns setBackgroundColor: [NSColor whiteColor]];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"indices"] autorelease];
  [item setView: mEditorIndices];
  [item setLabel: @"Indices"];
  [mEditorsTabView addTabViewItem: item];
  [mEditorIndices setBackgroundColor: [NSColor whiteColor]];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"foreignkeys"] autorelease];
  [item setView: mEditorForeignKeys];
  [item setLabel: @"Foreign Keys"];
  [mEditorsTabView addTabViewItem: item];
  [mEditorForeignKeys setBackgroundColor: [NSColor whiteColor]];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"triggers"] autorelease];
  [item setView: mTriggerTabItem];
  [item setLabel: @"Triggers"];
  [mEditorsTabView addTabViewItem: item];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"partitioning"] autorelease];
  [item setView: mEditorPartitioning];
  [item setLabel: @"Partitioning"];
  [mEditorsTabView addTabViewItem: item];
  [mEditorPartitioning setBackgroundColor: [NSColor whiteColor]];
  
  item = [[[NSTabViewItem alloc] initWithIdentifier: @"options"] autorelease];
  NSScrollView* sv = [[NSScrollView alloc] initWithFrame: [mEditorColumns 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];
  [mEditorOptions setBackgroundColor: [NSColor whiteColor]];
  
  [mColumnsTable registerForDraggedTypes: [NSArray arrayWithObject: @"com.sun.mysql.workbench.column"]];
    
  mDidAwakeFromNib = YES;
}

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

- (void) dealloc;
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];
  [NSRunLoop cancelPreviousPerformRequestsWithTarget: self];
  
  [mPrivilegesTab release];
  [mColumnsDataSource release];
  [mColumnTypes release];
  [mIndicesDataSource release];
  [mIndexColumnsDataSource release];
  [mFKDataSource release];
  [mPartitionsTreeDataSource release];
  [mFKColumnsDataSource release];
  // don't release this because NSViewController will release all possible assiedn values for this
  //[mUnusedColumnsDetailsBox release];
  
  delete mBackEnd;
  
  [super dealloc];
}

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

/**
 * 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]);
  }
};

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

- (bec::BaseEditor*)editorBE
{
  return mBackEnd;
}


- (NSImage*)titleIcon
{
  return [NSImage imageNamed: @"tab_icon_db.Table.png"];
}

@end


