/* 
 * Copyright (c) 2009, 2012, 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
 */

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using MySQL.Controls;
using MySQL.Grt;
using MySQL.Grt.Db;
using MySQL.Utilities;
using MySQL.Workbench;
using MySQL.GUI.Workbench.Plugins;
using MySQL.Forms;

namespace MySQL.GUI.Workbench
{
  public partial class SqlIdeForm : Plugins.DockablePlugin, IWorkbenchObserver
  {
    static int InstanceCount = 0;

    private List<RecordsetView> pendingRelayouts = new List<RecordsetView>();

    private SqlEditorFormWrapper dbSqlEditorBE;
    public SqlEditorFormWrapper Backend { get { return dbSqlEditorBE; } }

    private SqlEditorWrapper runningEditor;

    protected WbContext wbContext;

    private bool canTrackChanges = false; // True when initialization is done and UI changes can be tracked.

    #region Initialization

    public SqlIdeForm(WbContext wbContext, UIForm uiForm)
      : base((uiForm as SqlEditorFormWrapper).grt_manager())
    {
      InstanceCount++;

      this.wbContext = wbContext;
      dbSqlEditorBE = uiForm as SqlEditorFormWrapper;
      Initialize();
    }

    protected void Initialize()
    {
      Logger.LogInfo("WQE.net", "Launching SQL IDE\n");

      InitializeComponent();

      if (InstanceCount == 1)
        RegisterCommands();

      outputSelector.SelectedIndex = 0;

      TabText = dbSqlEditorBE.get_title();

      dbSqlEditorBE.sql_editor_new_ui_cb(OnSqlEditorNew);

      logView = new GridView(dbSqlEditorBE.log());
      logView.AutoScroll = true;
      logView.RowHeadersVisible = false;
      logView.Parent = actionPanel;
      logView.AllowAutoResizeColumns = false;

      // Some visual setup of the header.
      logView.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
      logView.ColumnHeadersDefaultCellStyle.Font = ControlUtilities.GetFont("Segoe UI", 7.5f);
      logView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;

      dbSqlEditorBE.log().refresh_ui_cb(logView.ProcessModelRowsChange);
      logView.ProcessModelChange();
      logView.Columns[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
      logView.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
      logView.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
      logView.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
      logView.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
      logView.Columns[4].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
      logView.Columns[5].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
      logView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
      logView.ForeColor = Color.Black;
      logView.CellContextMenuStripNeeded += new DataGridViewCellContextMenuStripNeededEventHandler(logView_CellContextMenuStripNeeded);

      historyEntriesView = new GridView(dbSqlEditorBE.history().entries_model());
      historyEntriesView.AutoScroll = true;
      historyEntriesView.ForeColor = Color.Black;
      historyEntriesView.MultiSelect = false;
      historyEntriesView.RowHeadersVisible = false;
      historyEntriesView.Parent = historySplitContainer.Panel1;
      dbSqlEditorBE.history().entries_model().refresh_ui_cb(ProcessModelHistoryEntryRowsChange);
      historyEntriesView.ProcessModelChange();
      historyEntriesView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
      historyEntriesView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
      historyEntriesView.RowEnter += new DataGridViewCellEventHandler(historyEntriesView_RowEnter);
      historyEntriesView.CellContextMenuStripNeeded += historyEntriesView_CellContextMenuStripNeeded;
      {
        SqlIdeMenuManager.MenuContext popupMenuContext = new SqlIdeMenuManager.MenuContext();
        popupMenuContext.GetNodesMenuItems = historyEntriesView.Model.get_popup_items_for_nodes;
        popupMenuContext.GetSelectedNodes = historyEntriesView.SelectedNodes;
        popupMenuContext.TriggerNodesAction = historyEntriesView.Model.activate_popup_item_for_nodes;
        SqlIdeMenuManager.InitMenu(historyListMenuStrip, popupMenuContext);
      }

      historyDetailsView = new GridView(dbSqlEditorBE.history().details_model());
      historyDetailsView.AutoScroll = true;
      historyDetailsView.ForeColor = Color.Black;
      historyDetailsView.RowHeadersVisible = false;
      historyDetailsView.Parent = historySplitContainer.Panel2;
      historyDetailsView.CellContextMenuStripNeeded += new DataGridViewCellContextMenuStripNeededEventHandler(historyDetailsView_CellContextMenuStripNeeded);
      dbSqlEditorBE.history().details_model().refresh_ui_cb(historyDetailsView.ProcessModelRowsChange);
      historyDetailsView.ProcessModelChange();
      historyDetailsView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
      historyDetailsView.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
      historyDetailsView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
      historyDetailsView.CellDoubleClick += new DataGridViewCellEventHandler(historyDetailsView_CellDoubleClick);

      dbSqlEditorBE.exec_sql_task.finish_cb(AfterExecSql);
      dbSqlEditorBE.exec_sql_task.progress_cb(OnExecSqlProgress);
      dbSqlEditorBE.recordset_list_changed_cb(RecordsetListChanged);
      dbSqlEditorBE.output_text_ui_cb(RecordsetTextOutput);
      resultSetTextBox.Font = GrtManager.get_font_option("workbench.general.Editor:Font");

      dbSqlEditorBE.refresh_ui().set_partial_refresh_slot(OnPartialRefresh);

      Control sidebar = dbSqlEditorBE.get_sidebar_control();
      sideArea.Controls.Add(sidebar);
      sidebar.Dock = DockStyle.Fill;

      // Help/snippets palette.
      Control palette = dbSqlEditorBE.get_palette_control();
      rightSplitContainer.Panel1.Controls.Add(palette);
      palette.Dock = DockStyle.Fill;
      palette.Show();

      outputPageToolStrip.Renderer = new FlatSubToolStripRenderer();

      ManagedNotificationCenter.AddObserver(this, "GNFormTitleDidChange");
    }

    #endregion

    #region Finalization

    private void Destroy()
    {
      Logger.LogInfo("WQE.net", "Shutting down SQL editor (" + TabText + ")\n");

      ManagedNotificationCenter.RemoveObserver(this, "GNFormTitleDidChange");

      if (InstanceCount == 1)
        UnregisterCommands();

      // Make a copy of the keys first as the dictionary is modified while closing the recordsets.
      long[] keys = new long[recordset2placeholder.Count];
      recordset2placeholder.Keys.CopyTo(keys, 0);
      foreach (long key in keys)
        CloseRecordset(key);

      // Dispose of all editors explicitly to sync backend release code with our shutdown.
      // Otherwise essential cleanup is done when the GC cleans up, which might be too late.
      foreach (SqlEditorWrapper editor in sqlEditors)
        editor.Dispose();
      sqlEditors.Clear();

      historyDetailsView.Dispose();
      historyEntriesView.Dispose();

      dbSqlEditorBE.Dispose();

      InstanceCount--;
    }

    #endregion

    #region Refresh Messages

    protected void OnPartialRefresh(int what)
    {
      SqlEditorFormWrapper.PartialRefreshType refresh_type = (SqlEditorFormWrapper.PartialRefreshType)what;
      switch (refresh_type)
      {
        case SqlEditorFormWrapper.PartialRefreshType.RefreshEditorTitle:
          OnSqlEditorTitleChange();
          break;
        
        case SqlEditorFormWrapper.PartialRefreshType.RefreshRecordsetTitle:
          OnRecordsetCaptionChange();
          break;

        case SqlEditorFormWrapper.PartialRefreshType.QueryExecutionStarted:
          // This handling works properly only for single query execution since we don't get an
          // indicator which editor is actually involved (the backend can actually start multiple
          // editor actions at the same time and in random order).
          // TODO: find a better way to report start and stop of editor actions in the mid term.
          runningEditor = ActiveEditor;
          if (runningEditor != null)
            editorTabControl.SetBusy(TabIndexFromEditor(runningEditor), true);
          break;

        default:
          Logger.LogDebug("WQE.net", 1, "Unhandled partial refresh message\n");
          break;
      }
    }

    /// <summary>
    /// Notifications coming in from the notification center.
    /// </summary>
    /// <param name="name">The name of the notification. We only get called for notifications we want.</param>
    /// <param name="sender">The object that caused the notification to be sent.</param>
    /// <param name="info">Name/Value string pairs for data to be passed.</param>
    public void HandleNotification(string name, IntPtr sender, Dictionary<string, string> info)
    {
      if (name == "GNFormTitleDidChange" && dbSqlEditorBE.form_id() == info["form"])
        TabText = dbSqlEditorBE.get_title();
    }

    public override void PerformCommand(string command)
    {
      switch (command)
      {
        case "wb.toggleSchemataBar":
          mainSplitContainer.Panel1Collapsed = !mainSplitContainer.Panel1Collapsed;
          wbContext.save_state("sidebar_visible", "query_editor", !mainSplitContainer.Panel1Collapsed);
          break;
        case "wb.toggleOutputArea":
          contentSplitContainer.Panel2Collapsed = !contentSplitContainer.Panel2Collapsed;
          wbContext.save_state("output_visible", "query_editor", !contentSplitContainer.Panel2Collapsed);
          break;
        case "wb.toggleSideBar":
          mainContentSplitContainer.Panel2Collapsed = !mainContentSplitContainer.Panel2Collapsed;
          wbContext.save_state("support_sidebar_visible", "query_editor", !mainContentSplitContainer.Panel2Collapsed);
          break;
        case "close_editor":
          editorTabControl.CloseTabPage(editorTabControl.SelectedTab);
          break;
      }
    }

    private List<String> commands = new List<string>();

    private void RegisterCommands()
    {
      commands.Add("wb.toggleSchemataBar");
      commands.Add("wb.toggleOutputArea");
      commands.Add("wb.toggleSideBar");
      commands.Add("close_editor");

      wbContext.add_frontend_commands(commands);
    }

    private void UnregisterCommands()
    {
      wbContext.remove_frontend_commands(commands);
    }

    #endregion

    #region SQL Script Execution

    private int OnExecSqlProgress(float progress, String msg)
    {
      logView.ProcessModelRowsChange();
      return 0;
    }

    private int AfterExecSql()
    {
      // For normal sql execution we have an editor (which is not necessarily the active editor).
      // If there is no running editor then we probably have an EXPLAIN call that just finished
      // which is always for the active editor.
      SqlEditorWrapper editor = runningEditor;
      if (editor == null)
        editor = ActiveEditor;
      if (editor == null)
        return 0;

      runningEditor = null; // Just like noted when setting this member. Handling needs improvement.

      int tabIndex = TabIndexFromEditor(editor);
      int editorIndex = EditorIndexFromTabIndex(tabIndex);

      if (dbSqlEditorBE.exec_sql_error_count() == 0)
        editorTabControl_SelectedIndexChanged(null, null);

      logView.ProcessModelRowsChange();
      logView.AutoResizeColumn(1);
      logView.AutoResizeColumn(2);
      logView.AutoResizeColumn(5);

      if (logView.Rows.Count > 0)
        logView.SetRowSelected(logView.Rows.Count - 1);

      editorTabControl.SelectedIndex = tabIndex;
      sqlEditors[editorIndex].get_native_editor().Focus();
      editorTabControl.SetBusy(tabIndex, false);

      return 0;
    }

    void onSizeChanged(object sender, EventArgs e)
    {
      foreach (RecordsetView view in pendingRelayouts)
      {
        // Workaround for data grids added while the form was minimized.
        // They refuse to compute their own layout until a new resize event occurs.
        view.GridView.Dock = DockStyle.None;
        view.GridView.Dock = DockStyle.Fill;
      }
      pendingRelayouts.Clear();
    }
    #endregion

    #region SQL Editors

    private List<SqlEditorWrapper> sqlEditors = new List<SqlEditorWrapper>();
    private Dictionary<SqlEditorWrapper, TabPage> sqlEditor2page = new Dictionary<SqlEditorWrapper, TabPage>();
    private Dictionary<TabPage, SqlEditorWrapper> page2SqlEditor = new Dictionary<TabPage, SqlEditorWrapper>();

    private SqlEditorWrapper ActiveEditor
    {
      get
      {
        int index = dbSqlEditorBE.active_sql_editor_index();
        if (index < 0 || index > sqlEditors.Count - 1)
          return null;
        return sqlEditors[index];
      }
    }

    /// <summary>
    /// Returns the index of the tab page that hosts the given editor.
    /// There is no 1:1 relationship between the sql editor collections and the tab collection,
    /// because we can have other types of forms docked (e.g. object editors).
    /// </summary>
    private int TabIndexFromEditor(SqlEditorWrapper editor)
    {
      TabPage page = sqlEditor2page[editor];
      if (page == null)
        return -1;
      return editorTabControl.TabPages.IndexOf(page);
    }

    /// <summary>
    /// The reverse operation to the previous function, without traveling the layout hierarchy.
    /// This way we could change our UI without affecting the mapping.
    /// </summary>
    private int EditorIndexFromTabIndex(int tabIndex)
    {
      if (tabIndex < 0 || tabIndex > editorTabControl.TabCount - 1)
        return -1;

      return EditorIndexFromTabPage(editorTabControl.TabPages[tabIndex]);
    }

    private int EditorIndexFromTabPage(TabPage page)
    {
      if (!page2SqlEditor.ContainsKey(page))
        return -1;

      return dbSqlEditorBE.sql_editor_index(page2SqlEditor[page]);
    }

    private int OnSqlEditorNew(int index)
    {
      AddSqlEditor(index);
      return 0;
    }

    private void OnSqlEditorTitleChange()
    {
      int editorIndex = dbSqlEditorBE.active_sql_editor_index();
      if (editorIndex >= 0 && editorIndex < sqlEditors.Count)
      {
        SqlEditorWrapper editor = sqlEditors[editorIndex];
        sqlEditor2page[editor].Text = dbSqlEditorBE.sql_editor_caption(editorIndex);

        // Also update the tooltip text on the way.
        if (!dbSqlEditorBE.sql_editor_is_scratch(editorIndex))
          sqlEditor2page[editor].ToolTipText = dbSqlEditorBE.sql_editor_path(editorIndex);
      }
    }

    private SqlEditorWrapper AddSqlEditor(int index)
    {
      Logger.LogDebug("WQE.net", 1, "Adding new SQL editor (" + TabText + ")\n");

      SqlEditorWrapper sqlEditor = dbSqlEditorBE.sql_editor(index);
      sqlEditor.set_result_docking_delegate(new ResultPaneDockDelegate(this, sqlEditor));
      Control editorControl = sqlEditor.get_native_editor();
      String caption = dbSqlEditorBE.sql_editor_caption(index);

      TabPage page = new TabPage(caption);
      page.Tag = sqlEditor;

      // Create control hierarchy.
      SplitContainer editorSplitContainer = new SplitContainer();
      editorSplitContainer.Orientation = Orientation.Horizontal;
      editorSplitContainer.Dock = DockStyle.Fill;

      // Upper part (editor + toolbar).
      editorSplitContainer.Panel1.Controls.Add(editorControl);
      editorControl.Dock = DockStyle.Fill;
      ToolStrip toolStrip = dbSqlEditorBE.get_editor_toolbar(index);
      editorSplitContainer.Panel1.Controls.Add(toolStrip);
      toolStrip.Dock = DockStyle.Top;

      // Lower part (record set views).
      FlatTabControl resultTabControl = new FlatTabControl();
      resultTabControl.BackgroundColor = System.Drawing.Color.FromArgb(40, 55, 82);
      resultTabControl.CanCloseLastTab = true;
      resultTabControl.ContentPadding = new System.Windows.Forms.Padding(0);
      editorSplitContainer.Panel2.Controls.Add(resultTabControl);
      resultTabControl.Dock = System.Windows.Forms.DockStyle.Fill;
      resultTabControl.HideWhenEmpty = false;
      resultTabControl.ItemPadding = new System.Windows.Forms.Padding(6, 0, 6, 0);
      resultTabControl.MaxTabSize = 200;
      resultTabControl.Name = "resultTabControl";
      resultTabControl.ShowCloseButton = true;
      resultTabControl.ShowFocusState = true;
      resultTabControl.ShowToolTips = true;
      resultTabControl.CanReorderTabs = true;
      resultTabControl.AllowDrop = true;
      resultTabControl.TabStyle = MySQL.Controls.FlatTabControl.TabStyleType.BottomNormal;
      resultTabControl.TabClosing += new System.EventHandler<MySQL.Controls.TabClosingEventArgs>(ResultTabClosing);
      resultTabControl.TabClosed += new System.EventHandler<MySQL.Controls.TabClosedEventArgs>(ResultTabClosed);
      resultTabControl.MouseClick += new MouseEventHandler(recordsetTabControlMouseClick);
      resultTabControl.SelectedIndexChanged += new EventHandler(resultTabControl_SelectedIndexChanged);

      page.Controls.Add(editorSplitContainer);
      editorTabControl.TabPages.Add(page);

      sqlEditors.Add(sqlEditor);
      sqlEditor2page[sqlEditor] = page;
      page2SqlEditor[page] = sqlEditor;

      // Set an initial height for the result set panel depending on what the user last had.
      int splitterDistance = wbContext.read_state("recordset_height", "query_editor", editorSplitContainer.Height / 2);
      if (editorSplitContainer.Height > splitterDistance + editorSplitContainer.SplitterWidth)
        editorSplitContainer.SplitterDistance = editorSplitContainer.Height - editorSplitContainer.SplitterWidth - splitterDistance;

      editorSplitContainer.SplitterMoved += new SplitterEventHandler(editorSplitContainer_SplitterMoved);
      editorSplitContainer.Panel2Collapsed = true;

      // Select tab last so that callbacks will work on attached recordset.
      editorTabControl.SelectedIndex = editorTabControl.TabCount - 1;

      return sqlEditor;
    }

    private void RemoveSqlEditor(int index)
    {
      if (index == -1)
        index = dbSqlEditorBE.active_sql_editor_index();

      // This call implicitly deletes all recordsets too.
      SqlEditorWrapper sqlEditor = sqlEditors[index];
      sqlEditors.RemoveAt(index);
      TabPage page = sqlEditor2page[sqlEditor];
      sqlEditor2page.Remove(sqlEditor);
      page2SqlEditor.Remove(page);

      // It is essential that we dispose of the editor here to make it close the backend properly,
      // which in turn has to stop pending tasks, which access UI elements.
      sqlEditor.Dispose();

      dbSqlEditorBE.remove_sql_editor(index);
    }

    #endregion

    #region Event handling

    void editorTabControl_SelectedIndexChanged(object sender, EventArgs e)
    {
      // This could set -1 as active editor index in the backend if a page was selected
      // that does not hold an SQL editor (e.g. an object editor page).
      dbSqlEditorBE.active_sql_editor_index(EditorIndexFromTabIndex(editorTabControl.SelectedIndex));
      UpdateActiveRecordsetInBackend();

      SqlEditorWrapper sqlEditor = ActiveEditor;
      if (sqlEditor != null)
      {
        Control editorControl = sqlEditor.get_native_editor();
        if (editorControl != null && editorControl.CanFocus)
          editorControl.Focus();
      }
      UpdateApplyCancel();
    }

    void editorTabControl_TabClosing(object sender, MySQL.Controls.TabClosingEventArgs e)
    {
      int editorIndex = EditorIndexFromTabPage(e.page);
      if (editorIndex >= 0)
        e.canClose = dbSqlEditorBE.sql_editor_will_close(editorIndex);
      else
      {
        // One of the object editors.
        ITabDocument tabDocument = editorTabControl.ActiveDocument;
        if (tabDocument is IWorkbenchDocument)
          e.canClose = (tabDocument as IWorkbenchDocument).CanCloseDocument();
      }
    }

    void editorTabControl_TabClosed(object sender, MySQL.Controls.TabClosedEventArgs e)
    {
      int editorIndex = EditorIndexFromTabPage(e.page);
      if (editorIndex >= 0)
      {
        RemoveSqlEditor(editorIndex);

        // If the last editor was closed create a new, empty one.
        if (sqlEditors.Count > 0)
        {
          editorIndex = EditorIndexFromTabIndex(editorTabControl.SelectedIndex);
          if (editorIndex > -1)
            dbSqlEditorBE.active_sql_editor_index(editorIndex);
        }
        else
        {
          dbSqlEditorBE.new_sql_script_file(); // This will call our callback for setting up the editor.
          dbSqlEditorBE.active_sql_editor_index(0);
          SqlEditorWrapper sqlEditor = ActiveEditor;
          if (sqlEditor != null)
            ActiveControl = sqlEditor.get_native_editor();
        }

      }
      else
      {
        ITabDocument tabDocument = editorTabControl.DocumentFromPage(e.page);
        if (tabDocument is IWorkbenchDocument)
          (tabDocument as IWorkbenchDocument).CloseDocument();
      }
    }

    void resultTabControl_SelectedIndexChanged(object sender, EventArgs e)
    {
      UpdateActiveRecordsetInBackend();
      UpdateApplyCancel();
    }

    private void editorTabControl_TabMoving(object sender, TabMovingEventArgs e)
    {
      // When moving tab pages we have to consider the sql editor indexing, which
      // might be different from that of the tab control (e.g. because of object editors).
      int from = EditorIndexFromTabIndex(e.FromIndex);
      if (from > -1)
      {
        // We are indeed moving a tab with an sql editor. Determine the target sql editor
        // index by iterating from the target index to the source index until another sql editor
        // is found. This is then the new sql editor target index.
        int offset = (e.FromIndex < e.ToIndex) ? -1 : 1;
        for (int index = e.ToIndex; index != e.FromIndex; index += offset)
        {
          int to = EditorIndexFromTabIndex(index);
          if (to > -1)
          {
            dbSqlEditorBE.sql_editor_reorder(from, to);

            // Also update our sqlEditor cache.
            SqlEditorWrapper editor = sqlEditors[from];
            sqlEditors[from] = sqlEditors[to];
            sqlEditors[to] = editor;
            break;
          }
        }
      }
    }

    private void applyButton_Click(object sender, EventArgs e)
    {
      RecordsetView view = ActiveRecordsetView;
      if (view != null)
        view.SaveChanges();
      UpdateApplyCancel();
    }

    private void cancelButton_Click(object sender, EventArgs e)
    {
      RecordsetView view = ActiveRecordsetView;
      if (view != null)
        view.DiscardChanges();
      UpdateApplyCancel();
    }

    #endregion

    #region IWorkbenchDocument implementation

    public override UIForm BackendForm
    {
      get { return dbSqlEditorBE; }
    }

    #endregion

    #region Recordsets

    // Simple mapper from a record set to its tab page/view or vice versa.
    // No information is stored to which editor they belong.
    private Dictionary<long, TabPage> recordset2page = new Dictionary<long, TabPage>();
    private Dictionary<long, RecordsetView> recordset2placeholder = new Dictionary<long, RecordsetView>();
    private Dictionary<TabPage, MySQL.Grt.Db.RecordsetWrapper> page2recordset = new Dictionary<TabPage, MySQL.Grt.Db.RecordsetWrapper>();

    public MySQL.Grt.Db.RecordsetWrapper ActiveRecordset
    {
      get
      {
        if (editorTabControl.SelectedTab == null)
          return null;

        FlatTabControl resultTabControl = GetResultTabControlForEditor(ActiveEditor);
        if (resultTabControl == null || resultTabControl.SelectedTab == null)
          return null;
        
        return page2recordset.ContainsKey(resultTabControl.SelectedTab) ?
          page2recordset[resultTabControl.SelectedTab] : null;
      }
    }

    public RecordsetView ActiveRecordsetView
    {
      get
      {
        MySQL.Grt.Db.RecordsetWrapper recordset = ActiveRecordset;
        if (recordset == null)
          return null;

        return recordset2placeholder[recordset.key()];
      }
    }

    private void UpdateActiveRecordsetInBackend()
    {
      if (editorTabControl.SelectedTab == null)
        return;

      int index = dbSqlEditorBE.active_sql_editor_index();
      MySQL.Grt.Db.RecordsetWrapper activeRecordset = ActiveRecordset;
      if (activeRecordset == null)
        dbSqlEditorBE.active_recordset(index, null);
      else
          dbSqlEditorBE.active_recordset(index, activeRecordset);
    }

    private void OnRecordsetCaptionChange()
    {
      if (editorTabControl.SelectedTab == null)
        return;

      FlatTabControl resultTabControl = GetResultTabControlForEditor(ActiveEditor);
      if (resultTabControl == null || resultTabControl.SelectedTab == null)
        return;

      TabPage page = resultTabControl.SelectedTab;
      if (page.Tag is AppViewImpl)
        page.Text = (page.Tag as AppViewImpl).GetTitle();
      else
      {
        MySQL.Grt.Db.RecordsetWrapper rs = page2recordset[resultTabControl.SelectedTab];
        page.Text = rs.caption();

        UpdateApplyCancel();

        // Trigger also a menu validation as the dirty state change might also change menu items.
        wbContext.validate_menu_for_form(dbSqlEditorBE);
      }
    }

    delegate void DelegateFunc();
    private void RecordsetListChanged(int editor_index, long key, bool added)
    {
      if (added)
      {
        if (InvokeRequired)
          Invoke((Action)(() => AddRecordsetToResultTabview(editor_index, key)));
        else
          AddRecordsetToResultTabview(editor_index, key);
      }
      else
      {
        if (InvokeRequired)
          Invoke((Action)(() => CloseRecordset(key)));
        else
          CloseRecordset(key);
      }
    }

    private void AddRecordsetToResultTabview(int editorIndex, long key)
    {
      if (recordset2page.ContainsKey(key))
        return; // Shouldn't happen. Just in case...

      FlatTabControl resultTabControl = GetResultTabControlForEditor(sqlEditors[editorIndex]);
      if (resultTabControl == null)
        return;

      // Due to the way start and stop of editor actions are reported by the backend we cannot
      // easily correlate between the messages sent and the editors they are meant for.
      // However, when results arrive we know for sure the sql operation is over and we can safely
      // remove the busy animation for that (here known) editor.
      editorTabControl.SetBusy(TabIndexFromEditor(sqlEditors[editorIndex]), false);

      MySQL.Grt.Db.RecordsetWrapper rs = dbSqlEditorBE.recordset_for_key(editorIndex, key);
      TabPage page = new TabPage(rs.caption());
      page.BackColor = SystemColors.ButtonFace;

      RecordsetView recordsetView = new RecordsetView();
      page.Tag = recordsetView;

      Form mainForm = ParentForm;
      if (!mainForm.Visible)
        pendingRelayouts.Add(recordsetView);

      recordsetView.Embed(page, rs);
      resultTabControl.TabPages.Add(page);
      int newIndex = resultTabControl.TabPages.Count - 1;
      resultTabControl.SetCloseButtonVisibility(newIndex, FlatTabControl.CloseButtonVisiblity.ShowButton);
      recordset2page.Add(rs.key(), page);
      recordset2placeholder.Add(rs.key(), recordsetView);
      page2recordset.Add(page, rs);
      recordsetView.ProcessModelChange();

      resultTabControl.SelectedIndex = newIndex;

      SplitContainer editorSplitContainer = resultTabControl.Parent.Parent as SplitContainer;

      // Hide SQL text area if we started collapsed (means: in edit mode for a table).
      // If there is no result set, however, show the editor.
      editorSplitContainer.Panel2Collapsed = resultTabControl.TabCount == 0;
      if (resultTabControl.TabCount > 0)
        editorSplitContainer.Panel1Collapsed = dbSqlEditorBE.sql_editor_start_collapsed(editorIndex);
    }

    private void CloseRecordset(long key)
    {
      if (recordset2page.ContainsKey(key))
      {
        TabPage page = recordset2page[key];
        if (page.Parent != null)
        {
          // If the page's parent is null then we closed it in the TabClosed event where this
          // collapsing is also handled.
          FlatTabControl resultTabControl = page.Parent as FlatTabControl;
          SplitContainer editorSplitContainer = resultTabControl.Parent.Parent as SplitContainer;
          editorSplitContainer.Panel2Collapsed = resultTabControl.TabCount == 1;
        }

        recordset2page.Remove(key);

        RecordsetView recordsetView = recordset2placeholder[key];
        page.Tag = null;
        recordset2placeholder.Remove(key);
        recordsetView.Dispose();

        page2recordset.Remove(page);
        page.Dispose();
      }
    }

    private delegate void RecordsetTextOutputDelegate(String text, bool bringToFront);

    private void RecordsetTextOutput(String text, bool bringToFront)
    {
      if (InvokeRequired)
      {
        RecordsetTextOutputDelegate f = new RecordsetTextOutputDelegate(RecordsetTextOutput);
        Invoke(f, new Object[] {text, bringToFront} );
      }
      else
      {
        resultSetTextBox.AppendText(text);
        if (bringToFront)
          outputSelector.SelectedIndex = 1;
      }
    }

    private void ResultTabClosing(object sender, TabClosingEventArgs e)
    {
      e.canClose = false;
      MySQL.Grt.Db.RecordsetWrapper rs = page2recordset.ContainsKey(e.page) ? page2recordset[e.page] : null;
      if (rs != null)
        e.canClose = rs.can_close();
      else
      {
        // Any docked app view (plugin)?
        if (e.page.Tag is AppViewImpl)
          e.canClose = (e.page.Tag as AppViewImpl).DocumentClosing();
      }
    }

    private void ResultTabClosed(object sender, TabClosedEventArgs e)
    {
      // Collapse the result set pane if this was the last tab that was closed.
      FlatTabControl resultTabControl = sender as FlatTabControl;
      if (resultTabControl.TabCount == 0)
        (resultTabControl.Parent.Parent as SplitContainer).Panel2Collapsed = true;

      MySQL.Grt.Db.RecordsetWrapper rs = page2recordset.ContainsKey(e.page) ? page2recordset[e.page] : null;
      if (rs != null)
        rs.close(); // Will trigger the notification chain via RecordListChanged.
      // No need for handling of AppViewImpl, as this is already closed in ResultTabClosing.
    }

    private void recordsetTabContextMenuClick(object sender, EventArgs e)
    {
      FlatTabControl resultTabControl = GetResultTabControlForEditor(ActiveEditor);
      if (resultTabControl == null)
        return;

      TabPage page = resultTabControl.SelectedTab;
      if (page == null)
        return;
      if (!page2recordset.ContainsKey(page))
        return;

      MySQL.Grt.Db.RecordsetWrapper rs = page2recordset[page];

      ToolStripMenuItem item = sender as ToolStripMenuItem;
      int clickedTab = (int)item.Owner.Tag;
      switch (item.Tag as string)
      {
        case "0": // Rename editor and tab. (currently not active)
          String newName = page.Text;
          if (StringInputForm.ShowModal("Rename a page", "Enter a new name for the page", "Name", ref newName) == DialogResult.OK)
          {
            rs.caption(newName);
            page.Text = rs.caption();
          }
          break;
        case "1": // Close recordset.
          resultTabControl.CloseTabPage(page);
          break;
        case "2": // Close all recordsets.
          while (resultTabControl.TabCount > 0)
            resultTabControl.CloseTabPage(resultTabControl.TabPages[0]);
          break;
        case "3": // Close all editors but the clicked one.
          while (resultTabControl.TabCount > 1)
          {
            if (resultTabControl.TabPages[0] == page)
              resultTabControl.CloseTabPage(resultTabControl.TabPages[1]);
            else
              resultTabControl.CloseTabPage(resultTabControl.TabPages[0]);
          }
          break;
      }
    }

    private void editorTabControlMouseClick(object sender, MouseEventArgs e)
    {
      switch (e.Button)
      {
        case MouseButtons.Right:
          {
            int clickedIndex = editorTabControl.TabIndexFromPosition(e.Location);
            if (clickedIndex < 0)
              return;

            // Keep the found index for later handling in the item click handler.
            editorTabsContextMenuStrip.Tag = clickedIndex;

            string path = "";
            int editorIndex = EditorIndexFromTabIndex(clickedIndex);
            if (editorIndex > -1)
              path = dbSqlEditorBE.sql_editor_path(editorIndex);
            copyFullPathToClipboardToolStripMenuItem.Enabled = path.Length > 0;
            Point p = editorTabControl.PointToScreen(e.Location);
            editorTabsContextMenuStrip.Show(p);
          }
          break;
      }
    }

    private void recordsetTabControlMouseClick(object sender, MouseEventArgs e)
    {
      switch (e.Button)
      {
        case MouseButtons.Right:
          {
            FlatTabControl resultTabControl = GetResultTabControlForEditor(ActiveEditor);
            if (resultTabControl == null)
              return;

            int clickedIndex = resultTabControl.TabIndexFromPosition(e.Location);
            if (clickedIndex < 0)
              return;

            // Keep the found index for later handling in the item click handler.
            recordsetTabsContextMenuStrip.Tag = clickedIndex;

            TabPage page = resultTabControl.TabPages[clickedIndex];
            renamePage_ToolStripMenuItem.Enabled = page2recordset.ContainsKey(page);
            Point p = resultTabControl.PointToScreen((Point)e.Location);
            recordsetTabsContextMenuStrip.Show(p);
          }
          break;
      }
    }

    private void editorTabContextMenuItemClick(object sender, EventArgs e)
    {
      ToolStripMenuItem item = sender as ToolStripMenuItem;
      int clickedTab = (int)item.Owner.Tag;
      TabPage page = editorTabControl.TabPages[clickedTab];
      switch (item.Tag as string)
      {
        case "0": // Rename editor and tab. (currently not active)
          String newName = page.Text;
          if (StringInputForm.ShowModal("Rename a page", "Enter a new name for the page", "Name", ref newName) == DialogResult.OK)
          {
            dbSqlEditorBE.sql_editor_caption(clickedTab, newName);
            page.Text = newName;
          }
          break;
        case "1": // Close editor.
          editorTabControl.CloseTabPage(page);
          break;
        case "2": // Close all editors.
          // Close them backwards and only exactly the number of editors that are there when we started.
          // Because on close of the last one a new empty one is created which we do not want to close again.
          // Additionally, some tabs might not be closable due to changed content and the user refusing
          // to close them.
          for (int i = editorTabControl.TabCount - 1; i >= 0; i--)
            editorTabControl.CloseTabPage(editorTabControl.TabPages[i]);
          break;
        case "3": // Close all editors but the clicked one.
          // Similar to case 2, but we don't need to care for a newly created editor, as we always
          // keep one open.
          List<TabPage> open_tabs = new List<TabPage>();
          
          for(int index = 0; index < editorTabControl.TabCount; index++)
          {
            if(editorTabControl.TabPages[index] != page)
              open_tabs.Add(editorTabControl.TabPages[index]);
          }

          foreach(TabPage open_page in open_tabs)
            editorTabControl.CloseTabPage(open_page);

          break;

        case "4": // Copy full path to clipboard (only valid for SQL editors).
          System.Windows.Forms.Clipboard.SetText(dbSqlEditorBE.sql_editor_path(clickedTab));
          break;

        case "5": // Close all of the same type.
          ITabDocument document = editorTabControl.DocumentFromIndex(clickedTab);
          bool closeDocument = document != null; // Only two types of pages: documents (table editors) or sql editors.
          for (int i = editorTabControl.TabCount - 1; i >= 0; i--)
          {
            if (closeDocument ^ editorTabControl.DocumentFromIndex(i) == null)
              editorTabControl.CloseTabPage(editorTabControl.TabPages[i]);
          }
          break;
      }
    }

    #endregion

    #region Log Panel

    private GridView logView;

    #endregion

    #region History Panel

    private GridView historyEntriesView;
    private GridView historyDetailsView;

    ContextMenuStrip logViewMenuStrip = null;
    ContextMenuStrip historyListMenuStrip = new ContextMenuStrip();
    ContextMenuStrip historyDetailsMenuStrip = null;

    private void historyEntriesView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
    {
      e.ContextMenuStrip = historyListMenuStrip;
    }

    void historyDetailsView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
    {
      if (historyDetailsMenuStrip == null)
      {
        // Prepare context menu strip on first use. Since this is actually an mforms managed
        // menu we don't get item clicks from there. So we hook into the click events here too.
        // TODO: this entire handling needs a general review.
        historyDetailsMenuStrip = dbSqlEditorBE.history().get_details_context_menu();
        foreach (ToolStripItem item in historyDetailsMenuStrip.Items)
          item.Click += new EventHandler(historyDetailsMenuItemClick);
      }

      if (historyDetailsView.SelectedRows.Count > 0)
        e.ContextMenuStrip = historyDetailsMenuStrip;
    }

    private void logView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
    {
      if (logViewMenuStrip == null)
      {
        // Prepare context menu strip on first use.
        logViewMenuStrip = dbSqlEditorBE.get_log_context_menu();
      }

      {
        List<DataGridViewRow> rows = logView.GetSelectedRows();
        List<int> indexes = new List<int>(rows.Count);
        foreach (DataGridViewRow row in rows)
        {
          indexes.Add(row.Index);
        }
        dbSqlEditorBE.set_log_selection(indexes);
      }

      e.ContextMenuStrip = logViewMenuStrip;
    }

    enum HistoryAction { HistoryAppendToEditor, HistoryReplaceEditor, HistoryCopyToClipboard };

    void historyDetailsMenuItemClick(object sender, EventArgs e)
    {
      ToolStripItem item = sender as ToolStripItem;
      switch (item.Tag.ToString())
      {
        case "copy_row":
          loadSelectedHistoryItems(HistoryAction.HistoryCopyToClipboard);
          break;
        case "append_selected_items":
          loadSelectedHistoryItems(HistoryAction.HistoryAppendToEditor);
          break;
        case "replace_sql_script":
          loadSelectedHistoryItems(HistoryAction.HistoryReplaceEditor);
          break;
      }
    }

    void historyEntriesView_RowEnter(Object sender, DataGridViewCellEventArgs e)
    {
      dbSqlEditorBE.history().current_entry(e.RowIndex);
    }

    void loadSelectedHistoryItems(HistoryAction action)
    {
      if (null == historyEntriesView.CurrentRow)
        return;

      List<int> sel_indexes = new List<int>();
      foreach (DataGridViewRow row in historyDetailsView.SelectedRows)
        sel_indexes.Add(row.Index);
      sel_indexes.Sort();
      String sql = dbSqlEditorBE.restore_sql_from_history(historyEntriesView.CurrentRow.Index, sel_indexes);

      SqlEditorWrapper sqlEditor = ActiveEditor;
      switch (action)
      {
        case HistoryAction.HistoryAppendToEditor:
          if (sqlEditor != null)
            sqlEditor.append_text(sql);
          break;
        case HistoryAction.HistoryReplaceEditor:
          if (sqlEditor != null)
            sqlEditor.set_text(sql);
          break;
        case HistoryAction.HistoryCopyToClipboard:
          System.Windows.Forms.Clipboard.SetText(sql);
          break;
      }
    }

    void historyDetailsView_CellDoubleClick(Object sender, DataGridViewCellEventArgs e)
    {
      loadSelectedHistoryItems(HistoryAction.HistoryAppendToEditor);
    }

    private int ProcessModelHistoryEntryRowsChange()
    {
      int ret_val = historyEntriesView.ProcessModelRowsChange();
      int selected_entry = dbSqlEditorBE.history().current_entry();

      if (selected_entry != -1)
        historyEntriesView.SetRowSelected(selected_entry);

      return ret_val;
    }

    #endregion

    #region Event Handling

    private void DbSqlEditor_Shown(object sender, EventArgs e)
    {
      LoadFormState();
      canTrackChanges = true;
    }

    private void mainSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
    {
      if (!canTrackChanges || IsDisposed || Disposing)
        return;

      int sidebarWidth;
      bool leftAligned = wbContext.read_option_value("", "Sidebar:RightAligned", "0") == "0";
      if (leftAligned)
        sidebarWidth = mainSplitContainer.SplitterDistance;
      else
        sidebarWidth = mainSplitContainer.Width - mainSplitContainer.SplitterWidth - mainSplitContainer.SplitterDistance;
      wbContext.save_state("sidebar_width", "query_editor", sidebarWidth);
    }

    private void contentSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
    {
      if (!canTrackChanges || IsDisposed || Disposing)
        return;

      int splitterDistance = contentSplitContainer.Height - contentSplitContainer.SplitterWidth - contentSplitContainer.SplitterDistance;
      wbContext.save_state("output_height", "query_editor", splitterDistance);
    }

    private void mainContentSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
    {
      if (!canTrackChanges || IsDisposed || Disposing)
        return;

      int splitterDistance = mainContentSplitContainer.Width - mainContentSplitContainer.SplitterWidth - mainContentSplitContainer.SplitterDistance;
      wbContext.save_state("support_sidebar_width", "query_editor", splitterDistance);
    }

    private void editorSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
    {
      if (!canTrackChanges || IsDisposed || Disposing)
        return;

      SplitContainer container = sender as SplitContainer;
      int splitterDistance = container.Height - container.SplitterWidth - container.SplitterDistance;
      wbContext.save_state("recordset_height", "query_editor", splitterDistance);
    }

    void outputPaneIndexChanged(object sender, System.EventArgs e)
    {
      ToolStripComboBox selector = sender as ToolStripComboBox;
      if (outputPageContent.Controls.Count > 0)
        outputPageContent.Controls.RemoveAt(0);
      switch (selector.SelectedIndex)
      {
        case 0:
          outputPageContent.Controls.Add(actionPanel);
          actionPanel.Dock = DockStyle.Fill;
          break;
        case 1:
          outputPageContent.Controls.Add(textOutputPage);
          textOutputPage.Dock = DockStyle.Fill;
          break;
        case 2:
          outputPageContent.Controls.Add(historyPage);
          historyPage.Dock = DockStyle.Fill;
          break;
      }
    }

    #endregion

    #region Form Layout

    private void LoadFormState()
    {
      // Object side bar.
      int splitterDistance = wbContext.read_state("sidebar_width", "query_editor", 200);
      bool leftAligned = wbContext.read_option_value("", "Sidebar:RightAligned", "0") == "0";

      if (leftAligned)
      {
        if (mainSplitContainer.Width > splitterDistance)
          mainSplitContainer.SplitterDistance = splitterDistance;
      }
      else
      {
        mainSplitContainer.FixedPanel = FixedPanel.Panel2;
        contentSplitContainer.Parent = mainSplitContainer.Panel1;
        sideArea.Parent = mainSplitContainer.Panel2;
        if (mainSplitContainer.Width > splitterDistance + mainSplitContainer.SplitterWidth)
          mainSplitContainer.SplitterDistance = mainSplitContainer.Width - mainSplitContainer.SplitterWidth - splitterDistance;
      }

      // Output tab. Distance measured from bottom (for easy default value).
      splitterDistance = wbContext.read_state("output_height", "query_editor", 200);
      if (contentSplitContainer.Height > splitterDistance + contentSplitContainer.SplitterWidth)
        contentSplitContainer.SplitterDistance = contentSplitContainer.Height - contentSplitContainer.SplitterWidth - splitterDistance;

      // Support side bar. Distance measured from right (for easy default value).
      splitterDistance = wbContext.read_state("support_sidebar_width", "query_editor", 200);
      if (mainContentSplitContainer.Height > splitterDistance + mainContentSplitContainer.SplitterWidth)
        mainContentSplitContainer.SplitterDistance = mainContentSplitContainer.Width - mainContentSplitContainer.SplitterWidth - splitterDistance;

      // Visibility of the sidebar/output areas.
      bool visible = wbContext.read_state("sidebar_visible", "query_editor", true);
      dbSqlEditorBE.set_tool_item_checked("wb.toggleSchemataBar", visible);
      mainSplitContainer.Panel1Collapsed = !visible;

      visible = wbContext.read_state("output_visible", "query_editor", true);
      dbSqlEditorBE.set_tool_item_checked("wb.toggleOutputArea", visible);
      contentSplitContainer.Panel2Collapsed = !visible;

      visible = wbContext.read_state("support_sidebar_visible", "query_editor", true);
      dbSqlEditorBE.set_tool_item_checked("wb.toggleSideBar", visible);
      mainContentSplitContainer.Panel2Collapsed = !visible;
    }
    
    /// <summary>
    /// Docks the given document to the editor tab control (usually object editors).
    /// </summary>
    public void DockDocument(ITabDocument document, bool activate)
    {
      if (!editorTabControl.HasDocument(document))
        editorTabControl.AddDocument(document);
      if (activate)
        document.Activate();

      if (contentSplitContainer.Panel2Collapsed)
      {
        contentSplitContainer.Panel2Collapsed = false;

        // Set a splitter distance or we end up at almost full display. Use a relatively small
        // value for panel2. The document's min height will kick in and does the right job.
        contentSplitContainer.SplitterDistance = contentSplitContainer.Height - 100;
      }
    }

    public void UndockDocument(ITabDocument document)
    {
      editorTabControl.RemoveDocument(document);
    }

    #endregion

    #region Miscellaneous

    /// <summary>
    /// Helper method to ease access to the result set tab control, stored in our
    /// nested tab hierarchy.
    /// </summary>
    private FlatTabControl GetResultTabControlForEditor(SqlEditorWrapper editor)
    {
      SplitContainer editorSplitContainer = GetSplitContainerForEditor(editor);
      if (editorSplitContainer == null || editorSplitContainer.Panel2.Controls.Count == 0)
        return null;

      return editorSplitContainer.Panel2.Controls[0] as FlatTabControl;
    }

    /// <summary>
    /// Helper method to ease access to the result set tab control, stored in our
    /// nested tab hierarchy.
    /// </summary>
    private SplitContainer GetSplitContainerForEditor(SqlEditorWrapper editor)
    {
      if (editor == null)
        return null;

      int index = TabIndexFromEditor(editor);
      if (index < 0 || index > editorTabControl.TabCount - 1)
        return null;

      TabPage editorTabPage = editorTabControl.TabPages[index];
      if (editorTabPage.Controls.Count == 0)
        return null;

      return editorTabPage.Controls[0] as SplitContainer;
    }

    /// <summary>
    /// Update visibility and enabled state of apply and cancel button.
    /// </summary>
    private void UpdateApplyCancel()
    {
      RecordsetView view = ActiveRecordsetView;
      if (view == null)
      {
        applyButton.Visible = false;
        cancelButton.Visible = false;
        readOnlyLabel.Visible = false;
        readOnlyPictureBox.Visible = false;
        labelsTooltip.RemoveAll();
      }
      else
      {
        if (view.GridView.ReadOnly)
        {
          readOnlyLabel.Visible = true;
          readOnlyPictureBox.Visible = true;
          labelsTooltip.SetToolTip(readOnlyLabel, view.GridView.Model.readonly_reason());
          labelsTooltip.SetToolTip(readOnlyPictureBox, view.GridView.Model.readonly_reason());
          applyButton.Visible = false;
          cancelButton.Visible = false;
        }
        else
        {
          readOnlyLabel.Visible = false;
          readOnlyPictureBox.Visible = false;
          labelsTooltip.RemoveAll();
          applyButton.Visible = true;
          cancelButton.Visible = true;
          applyButton.Enabled = view.Dirty;
          cancelButton.Enabled = view.Dirty;
        }
      }
    }

    public override bool CanCloseDocument()
    {
      // The backend will take care for all SQL editors. We only check object editors here.
      if (!Backend.can_close())
        return false;

      foreach (ITabDocument document in editorTabControl.Documents)
      {
        if (document is IWorkbenchDocument)
        {
          UIForm form = (document as IWorkbenchDocument).BackendForm;
          if (!form.can_close())
            return false;
        }

      }
      return true;
    }

    /// <summary>
    /// Adds an arbitrary view object to the result pane for the given editor.
    /// The view is usually the visual representation of a plugin.
    /// </summary>
    /// <param name="editor">The editor whose result tabview is used.</param>
    /// <param name="view">The view to dock.</param>
    public void AddViewToResultTabview(SqlEditorWrapper editor, AppViewImpl view)
    {
      FlatTabControl resultTabControl = GetResultTabControlForEditor(editor);
      if (resultTabControl == null)
        return;

      TabPage page = new TabPage(view.GetTitle());
      page.BackColor = SystemColors.ButtonFace;

      page.Tag = view;

      Control control = view.GetHost();
      control.Dock = DockStyle.Fill;
      control.Margin = new Padding(0);
      control.Padding = new Padding(0);
      control.Show();
      page.Controls.Add(control);
      resultTabControl.TabPages.Add(page);
      int newIndex = resultTabControl.TabPages.Count - 1;
      resultTabControl.SetCloseButtonVisibility(newIndex, FlatTabControl.CloseButtonVisiblity.ShowButton);
      resultTabControl.SelectedIndex = newIndex;

      SplitContainer editorSplitContainer = resultTabControl.Parent.Parent as SplitContainer;
      editorSplitContainer.Panel2Collapsed = false;
    }

    /// <summary>
    /// Removes a previously added view from the result tabview of the given editor.
    /// </summary>
    /// <param name="editor">The editor whose result tabview is used.</param>
    /// <param name="view">The view to dock.</param>
    public void RemoveViewFromResultTabview(SqlEditorWrapper editor, AppViewImpl view)
    {
      FlatTabControl resultTabControl = GetResultTabControlForEditor(editor);
      if (resultTabControl == null)
        return;

      TabPage page = view.GetHost().Parent as TabPage;
      if (page == null)
        return;

      SplitContainer editorSplitContainer = resultTabControl.Parent.Parent as SplitContainer;
      editorSplitContainer.Panel2Collapsed = resultTabControl.TabCount == 1;

      page.Dispose();
    }

    public bool SelectView(SqlEditorWrapper editor, AppViewImpl view)
    {
      FlatTabControl resultTabControl = GetResultTabControlForEditor(editor);
      if (resultTabControl == null)
        return false;
      return true;
    }

    public void SetViewTitle(SqlEditorWrapper editor, AppViewImpl view, String title)
    {
      TabPage page = view.GetHost().Parent as TabPage;
      if (page == null)
        return;
      page.Text = title;
    }

    public void GetResultTabViewSize(SqlEditorWrapper editor, out int width, out int height)
    {
      FlatTabControl resultTabControl = GetResultTabControlForEditor(editor);
      if (resultTabControl == null)
      {
        width = 0;
        height = 0;
        return;
      }

      width = resultTabControl.ClientSize.Width;
      height = resultTabControl.ClientSize.Height;
    }

    #endregion

  }

  #region DockingPoint delegate implementation

  public class ResultPaneDockDelegate : DockingPointDelegateWrapper
  {
    private SqlIdeForm owner = null;

    public ResultPaneDockDelegate(SqlIdeForm owner, SqlEditorWrapper editorWrapper)
      : base(editorWrapper)
    {
      this.owner = owner;
    }

    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
    }

    public override String get_type(Object representedObject)
    {
      return "db.Query.QueryEditor:result"; // Same as defined in wb_sql_editor_form.h
    }

    public override void dock_view(Object representedObject, AppViewImpl view, String arg1, int arg2)
    {
      SqlEditorWrapper wrapper = representedObject as SqlEditorWrapper;
      owner.AddViewToResultTabview(wrapper, view);
    }

    public override void undock_view(Object representedObject, AppViewImpl view)
    {
      SqlEditorWrapper wrapper = representedObject as SqlEditorWrapper;
      owner.RemoveViewFromResultTabview(wrapper, view);
    }

    public override bool select_view(Object representedObject, AppViewImpl view)
    {
      SqlEditorWrapper wrapper = representedObject as SqlEditorWrapper;
      return owner.SelectView(wrapper, view);
    }

    public override void set_view_title(Object representedObject, AppViewImpl view, String str)
    {
      SqlEditorWrapper wrapper = representedObject as SqlEditorWrapper;
      owner.SetViewTitle(wrapper, view, str);
    }

    /// <summary>
    /// Meant to return the size of the host of the view, not the view itself.
    /// </summary>
    public override void get_size(Object representedObject, out int w, out int h)
    {
      SqlEditorWrapper wrapper = representedObject as SqlEditorWrapper;
      owner.GetResultTabViewSize(wrapper, out w, out h);
    }
  };

  #endregion

}
