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

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using MySQL.Controls;

using MySQL.Grt;
using MySQL.GUI.Workbench.Plugins;
using MySQL.GUI.Workbench.Properties;
using MySQL.Utilities;
using MySQL.Utilities.SysUtils;
using MySQL.Forms;
using MySQL.Workbench;
using System.IO;

namespace MySQL.GUI.Workbench
{
  public partial class ModelOverviewForm : TabDocument, IWorkbenchDocument
  {
    #region Member Variables
    
    // Flags to avoid recursive refreshes.
    private enum RefreshState
    {
      Attachments,
      Diagrams,
      Privileges,
      Schema
    }

    private class Identifier
    {
      public NodeId id;
      public String objectId;

      public Identifier(NodeId id)
      {
        this.id = id;
      }
    }

    private Hashtable states = new Hashtable();

    // Keeps states of columns for each section listview we have.
    // Currently only column widths are stored.
    private Hashtable columnStates = new Hashtable();

    // The Workbench context
    private WbContext wbContext;
    // The Workbench overview
    private Overview wbOverview;

    private WorkbenchMenuManager workbenchMenuManager;

    private List<CollapsingPanel> panelList = new List<CollapsingPanel>();

    private Overview.DisplayMode currentOverviewDisplayMode = Overview.DisplayMode.SmallIcon;

    private Dictionary<String, CollapsingPanel> panelsByNode = new Dictionary<string, CollapsingPanel>();
    private Dictionary<String, ListView> listsByNode = new Dictionary<String, ListView>();

    // Mapper between element icon ids and image list indices, depending on 
    // the view mode of the listview the item is in.
    private Dictionary<int, int> imageIndexMapper = new Dictionary<int, int>();

    private bool overviewInvalid = false;

    private String lastSearchText = "";
    private NodeId lastFoundNode = null;

    private ModelObjectDescriptionForm modelObjectDescriptionForm;
    private UserDatatypesForm userDatatypesForm;
    private UndoHistoryForm historyForm;
    private IMessageFilter wheelMessageFilter;

    private bool splitterMovePending = false;

    public bool skipHeader = false;

    #endregion

    #region Constructors

    public ModelOverviewForm(WbContext WbContext, Overview be)
    {
      InitializeComponent();

      wbContext = WbContext;
      wbOverview = be;

      UpdateTabText();
      workbenchMenuManager = new WorkbenchMenuManager(wbContext);
      userDatatypesForm = new UserDatatypesForm(wbContext);
      historyForm = new UndoHistoryForm(wbContext);
      modelObjectDescriptionForm = new ModelObjectDescriptionForm(wbContext);

      wheelMessageFilter = new WheelMessageFilter(this);
      Application.AddMessageFilter(wheelMessageFilter);

      SetupSideBar();
    }

    private void UpdateTabText()
    {
      Text = wbOverview.get_title();
      String newTabText = wbOverview.get_title();

      String filename = wbContext.get_filename();
      if (filename.Length > 0)
        newTabText += " (" + Path.GetFileName(filename) + ")";

      TabText = newTabText;
    }

    #endregion

    #region Internal classes

    /// <summary>
    /// Internal class used to forward mouse wheel messages to the overview page to be able to scroll
    /// all panels using the mouse wheel.
    /// </summary>
    internal class WheelMessageFilter : IMessageFilter
    {
      private ModelOverviewForm form;

      public WheelMessageFilter(ModelOverviewForm form)
      {
        this.form = form;
      }

      public bool PreFilterMessage(ref Message m)
      {
        if ((WM)m.Msg == WM.MOUSEWHEEL)
        {
          // Only handle messages if the control belongs to our form.
          Win32.POINT cursorPoint = new Win32.POINT();
          Win32.GetCursorPos(ref cursorPoint);
          IntPtr targetWindow = Win32.WindowFromPoint(cursorPoint);
          Control control = Control.FromChildHandle(targetWindow);
          if (control != null && ControlUtilities.ContainsControl(form.contentHeaderPanel, control))
          {
            // Forward the message to the control or the first of its parents which can have scrollbars.
            while (control != null && !(control is ScrollableControl))
              control = control.Parent;
            if (control != null)
            {
              Win32.SendMessage(control.Handle, m.Msg, m.WParam, m.LParam);
              return true;
            }
          }
        }

        return false;
      }
    }

    #endregion

    #region IWorkbenchDocument Interface

    public UIForm BackendForm
    {
      get { return wbOverview.get_uiform(); }
    }

    public void RefreshGUI(RefreshType refresh, String str, IntPtr ptr)
    {
      switch (refresh)
      {
        case RefreshType.RefreshSelection:
          if (ptr != null && ptr.ToInt64() != 0)
          {
            UIForm form = UIForm.GetFromFixedId(ptr);
            modelObjectDescriptionForm.UpdateForView(form);
          }
          else
            modelObjectDescriptionForm.UpdateForView(null);
          break;

        case RefreshType.RefreshDocument:
          UpdateTabText();
          break;

        case RefreshType.RefreshOverviewNodeChildren:
          try
          {
            RefreshNodeChildren(new NodeId(str));
          }
          catch (Exception)
          {
            // ignore
          }
          break;

        case RefreshType.RefreshOverviewNodeInfo:
          try
          {
            RefreshNodeInfo(new NodeId(str));
          }
          catch (Exception)
          {
            //ignore
          }
          break;

        case RefreshType.RefreshCloseEditor:
          CloseEditorsForObject(str);
          break;
      }
    }

    public void PerformCommand(String command)
    {
      switch (command)
      {
        case "view_user_datatypes":
          ShowUserDatatypes();
          break;
        case "view_object_description":
          ShowDescriptions();
          break;
        case "view_undo_history":
          ShowUndoHistory();
          break;
        case "wb.sidebarHide":
          if (mainSplitContainer.Panel1.Controls.Contains(sideSplitContainer))
            mainSplitContainer.Panel1Collapsed = !mainSplitContainer.Panel1Collapsed;
          else
            mainSplitContainer.Panel2Collapsed = !mainSplitContainer.Panel2Collapsed;
          break;
      }
    }

    public DockablePlugin FindPluginOfType(Type type)
    {
      foreach (ITabDocument content in bottomTabControl.Documents)
        if (content is ObjectEditorView && (content as ObjectEditorView).EditorPlugin.GetType() == type)
          return content as DockablePlugin;
      return null;
    }

    public bool ClosePluginOfType(Type type)
    {
      foreach (ITabDocument content in bottomTabControl.Documents)
        if (content is ObjectEditorView && (content as ObjectEditorView).EditorPlugin.GetType() == type)
        {
          // Unregister plugin from back end.
          wbContext.close_gui_plugin((content as ObjectEditorView).EditorPlugin.GetFixedPtr());

          content.Close();
          if (bottomTabControl.TabCount == 0)
            contentSplitContainer.Panel2Collapsed = true;
          return true;
        }

      return false;
    }

    #endregion

    #region Event Handling

    private void bottomTabControl_TabClosing(object sender, TabClosingEventArgs e)
    {
      ITabDocument document = (sender as FlatTabControl).DocumentFromPage(e.page);
      if (document is IWorkbenchDocument)
        e.canClose = (document as IWorkbenchDocument).CanCloseDocument();
      else
        if (document is MySQL.Forms.AppViewDockContent)
        {
          MySQL.Forms.AppViewDockContent content = document as MySQL.Forms.AppViewDockContent;
          e.canClose = content.GetAppView().DocumentClosing();
        }
    }

    private void bottomTabControl_TabClosed(object sender, MySQL.Controls.TabClosedEventArgs e)
    {
      if (bottomTabControl.TabCount == 0)
        contentSplitContainer.Panel2Collapsed = true;

      ITabDocument document = (sender as FlatTabControl).DocumentFromPage(e.page);
      if (document is IWorkbenchDocument)
        (document as IWorkbenchDocument).CloseDocument();
      else
        if (document is MySQL.Forms.AppViewDockContent)
        {
          // This type of document is already closed (in TabClosing).
          // TODO: adjust code to support OnClosing/OnClose duality also in AppView.
        }
      e.page.Controls.Clear();
      e.page.Dispose();
      GC.Collect();
    }

    #endregion

    #region Public functions

    public void SearchAndFocusNode(String text)
    {
      if ((lastSearchText == null) || !text.StartsWith(lastSearchText))
        lastFoundNode = null;

      lastSearchText = text;

      lastFoundNode = wbOverview.search_child_item_node_matching(null, lastFoundNode, text);

      if (lastFoundNode != null)
        FocusNode(lastFoundNode);
    }

    public void RefreshNodeInfo(NodeId node)
    {
      int nodeType;

      // if we're refreshing a node, first check what type is it, then update it
      // through its container or the item itself
      if (wbOverview.get_field(node, (int)Overview.Columns.NodeType, out nodeType))
      {
        wbOverview.refresh_node(node, false);

        if (nodeType == (int)Overview.NodeType.Item)
        {
          // find the object in its container
          NodeId parent = wbOverview.get_parent(node);
          if (listsByNode.ContainsKey(parent.repr()))
          {
            ListView list = listsByNode[parent.repr()];
            UpdateListViewNode(node, list);
          }
        }
        else
        {
          // find the control for the id
          if (nodeType == (int)Overview.NodeType.Section)
          {
            // a CollapsingPanel
          }
          else if (nodeType == (int)Overview.NodeType.Group)
          {
            // schema tabs
            if (panelsByNode.ContainsKey(wbOverview.get_parent(node).repr()))
            {
              CollapsingPanel panel = panelsByNode[wbOverview.get_parent(node).repr()];

              panel.Refresh();
              panel.Update();
            }
          }
          else
            throw new Exception("not handled");
        }
      }
    }

    public void RefreshNodeChildren(NodeId node)
    {
      int nodeType;

      if ((null == node) || !node.is_valid())
      {
        RebuildModelContents();
        return;
      }

      if (overviewInvalid)
        return;

      // find the container and refresh it
      if (wbOverview.get_field(node, (int)Overview.Columns.NodeType, out nodeType))
      {
        wbOverview.refresh_node(node, true);

        if (nodeType == (int)Overview.NodeType.Section)
        {
          RefreshItemList(node, (Overview.NodeType)nodeType);
        }
        else if (nodeType == (int)Overview.NodeType.Group)
        {
          CollapsingPanel panel = panelsByNode[node.repr()];
        }
        else if (nodeType == (int)Overview.NodeType.Division)
        {
          int childNodeType;
          wbOverview.get_field(node, (int)Overview.Columns.ChildNodeType, out childNodeType);

          switch ((Overview.NodeType)childNodeType)
          {
            case Overview.NodeType.Group:
              // Group as children of Division means a tabview
              RefreshGroupTabs(node);
              break;

            case Overview.NodeType.Section:
              break;

            case Overview.NodeType.Item:
              RefreshItemList(node, (Overview.NodeType)nodeType);
              break;
          }
        }
      }
    }


    private void RefreshGroupTabs(NodeId node)
    {
      if (panelsByNode.ContainsKey(node.repr()))
      {
        CollapsingPanel panel = panelsByNode[node.repr()];

        panel.SuspendLayout();

        panel.Controls.Clear();
        PopulateSections(panel, currentOverviewDisplayMode, false);

        Refresh();

        panel.ResumeLayout();
      }
    }

    private void RefreshItemList(NodeId node, Overview.NodeType nodeType)
    {
      if (listsByNode.ContainsKey(node.repr()))
      {
        ListView list = listsByNode[node.repr()];

        if (nodeType == Overview.NodeType.Section)
        {
          int childCount = wbOverview.count_children(node) - 1;
          String info = "(" + childCount + ((childCount == 1) ? " item" : " items") + ")";
          SetInfoLabelForList(list, info);
        }

        if (list.View == View.Details)
          SaveColumnStates(list);

        PopulateListView(node, list);

        if (list.View == View.Details)
          RestoreColumnStates(list);
      }
    }

    public void BeginRenameSelection()
    {
      ListView list = ActiveControl as ListView;
      if (list != null)
      {
        if (list.SelectedItems.Count > 0)
        {
          NodeId itemNodeId = (list.SelectedItems[0].Tag as Identifier).id;
          if (itemNodeId != null && wbOverview.is_editable(itemNodeId))
            list.SelectedItems[0].BeginEdit();
        }
      }
    }

    public void RebuildModelContents()
    {
      overviewInvalid = true;

      // Don't reload the entire page if we are just about to refresh it anyway.
      if (!states.ContainsKey(RefreshState.Schema))
      {
        NodeId rootNodeId = wbOverview.get_root();

        mainPanel.SuspendLayout();
        try
        {
          ResetDocument(true);

          int count = wbOverview.count_children(rootNodeId);
          for (int i = 0; i < count; i++)
          {
            NodeId panelNodeId = wbOverview.get_child(rootNodeId, i);
            string caption;
            int expanded;
            //int height;
            Overview.DisplayMode displayMode;
            int temp;

            if (!wbOverview.get_field(panelNodeId, (int)Overview.Columns.Label, out caption))
              throw new Exception("Invalid node");
            wbOverview.get_field(panelNodeId, (int)Overview.Columns.Expanded, out expanded);
            //wbOverview.get_field(panelNodeId, (int)Overview.Columns.Height, out height);
            wbOverview.get_field(panelNodeId, (int)Overview.Columns.DisplayMode, out temp);
            displayMode = (Overview.DisplayMode)temp;
            bool isExpansionDisabled = wbOverview.is_expansion_disabled();
            bool initialExpandState = !isExpansionDisabled;

            CollapsingPanel overviewPanel = new CollapsingPanel(caption, CollapsingPanelDisplayMode.Normal,
              CollapsingPanelStyle.Flat, initialExpandState, 0);
            overviewPanel.DisableExpansionIcon = isExpansionDisabled;
            overviewPanel.HeaderFont = new Font("Tahoma", 9.75f, FontStyle.Bold);
            overviewPanel.Padding = new Padding(12, 4, 0, 0);
            overviewPanel.BackColor = Color.White;

            panelsByNode[panelNodeId.repr()] = overviewPanel;

            overviewPanel.Tag = CreateIdentifier(panelNodeId);

            // Add the collapsing panel to the form. Move every panel explicitely to the top position
            // (moving all others down one step) to make stacking working properly.
            mainPanel.Controls.Add(overviewPanel);
            mainPanel.Controls.SetChildIndex(overviewPanel, 0);
            overviewPanel.Dock = DockStyle.Top;

            // Add panel to the list of panels for this model type so it can be deleted later.
            panelList.Add(overviewPanel);

            // If this is the first panel then remove its header.
            // The parent header panel will provide one.
            if (panelList.Count == 1)
              overviewPanel.DisplayMode = CollapsingPanelDisplayMode.NoHeader;

            // Check if there are child items
            FillPanelContent(panelNodeId, displayMode, overviewPanel);
            overviewPanel.Expanded = expanded == 1;

            if (skipHeader)
            {
              overviewPanel.DisplayTabActionButtons = false;
              overviewPanel.DisplayCustomButtons = false;
              overviewPanel.DisplayMode = CollapsingPanelDisplayMode.TabsOnly;
            }
          }
        }
        finally
        {
          mainPanel.ResumeLayout(true);
        }
      }
    }

    #endregion

    #region Helper functions

    /// <summary>
    /// Creates a key that can be used for later lookup in a dictonary consisting of
    /// a GRT icon id and a view mode (large icon, small icon).
    /// </summary>
    /// <param name="id"></param>
    /// <param name="view"></param>
    /// <returns></returns>
    private int CreateIconKey(int id, View view)
    {
      switch (view)
      {
        case View.LargeIcon:
          return 3 * id;
        case View.Tile:
          return 3 * id + 1;
        case View.SmallIcon:
        case View.Details:
          return 3 * id + 2;
      }
      return 0;
    }

    /// <summary>
    /// Returns the image index for a given node and view mode as cached in the mapper.
    /// If such a mapping does not exist yet then one is created.
    /// 
    /// Note: wbOverview must be assigned already to make this work.
    /// </summary>
    /// <returns>
    /// The index of the image within the associated image list.
    /// </returns>
    private int FindImageIndex(NodeId id, View view)
    {
      IconSize size = IconSize.Icon16;
      if (view == View.LargeIcon)
        size = IconSize.Icon48;
      int iconId = wbOverview.get_field_icon(id, (int)Overview.Columns.Label, size);
      int iconKey = CreateIconKey(iconId, view);

      if (!imageIndexMapper.ContainsKey(iconKey))
      {
        // There is no image index stored yet for this node.
        // Add a new mapping for it.
        // This is necessary as we cannot guarantee that both image indices are the same for
        // small and large icons (which would be necessary if we want to use the same index for
        // both, large and small icon view). So we have to set the index depending on the view mode.
        int listIndex = GrtIconManager.get_instance().add_icon_to_imagelist(iconId);
        imageIndexMapper.Add(iconKey, listIndex);
      }

      return imageIndexMapper[iconKey];
    }

    private void SetViewMode(CollapsingPanel panel, View mode)
    {
      foreach (Control control in panel.Controls)
        if (control is ListView)
          SetViewMode(control as ListView, mode);

      panel.PerformLayout();
    }

    private void SetViewMode(CollapsingPanel panel, Overview.DisplayMode mode)
    {
      foreach (Control control in panel.Controls)
        if (control is ListView)
          SetViewMode(control as ListView, mode);

      panel.PerformLayout();
    }

    private View[] displayMode2ViewMode = { View.Details, View.LargeIcon, View.Tile, View.Details };

    private void SetViewMode(ListView view, Overview.DisplayMode mode)
    {
      View newViewMode = displayMode2ViewMode[(int)mode];
      SetViewMode(view, newViewMode);
    }

    private void SetViewMode(ListView view, View newViewMode)
    {
      // Keep the old column states if we go away from details mode.
      // They will be restored later when we go back to details view and have 
      // added the columns again.
      if (view.View == View.Details)
        SaveColumnStates(view);

      if (view.View != newViewMode)
      {
        switch (newViewMode)
        {
          case View.Tile:
            view.View = newViewMode;
            view.TileSize = new Size(140, 17);
            view.LargeImageList = GrtIconManager.ImageList16;
            break;
          case View.LargeIcon:
            view.View = newViewMode;
            view.LargeImageList = GrtIconManager.ImageList48;
            break;
          case View.Details:
            // This call will make the scrollbars visible for a moment but works around a more serious problem.
            // When the listview is switched to report mode without scrollbars being visible then it will not adjust
            // its client area for the header and parts of the first items are hidden behind the header.
            view.Scrollable = true;
            view.View = newViewMode;
            view.Scrollable = false;
            break;
        }

        // Adjust item image indices, as they might not be the same for large and small icons.
        for (int i = 0; i < view.Items.Count; i++)
        {
          NodeId id = (view.Items[i].Tag as Identifier).id;
          view.Items[i].ImageIndex = FindImageIndex(id, view.View);
        }
        view.PerformLayout();
      }
    }


    private void FocusNode(NodeId node)
    {
      NodeId parent = wbOverview.get_parent(node);
      int index = node.end();

      if (listsByNode.ContainsKey(parent.repr()))
      {
        ListView list = listsByNode[parent.repr()];

        list.Focus();
        list.SelectedIndices.Clear();
        list.Items[index].Selected = true;
      }
    }

    #endregion

    #region Form implementation

    private int modelTypeCollapsingPanel_TabCountNeeded(object sender)
    {
      if (wbOverview != null)
      {
        NodeId rootNode = wbOverview.get_root();
        if (rootNode != null)
          return wbOverview.count_children(rootNode);
      }

      return 0;
    }

    private bool modelTypeCollapsingPanel_TabInfoNeeded(object sender, int index, out int iconId, out string caption, out string description)
    {
      iconId = 0;
      caption = "";
      description = "";

      NodeId nodeId = wbOverview.get_root();
      if (nodeId != null)
      {
        wbOverview.get_field(nodeId, (int)Overview.Columns.Label, out caption);
        return true;
      }
      else
        return false;
    }

    private Identifier CreateIdentifier(NodeId nodeId)
    {
      Identifier identifier = new Identifier(nodeId);
      identifier.objectId = wbOverview.get_node_unique_id(nodeId);
      return identifier;
    }

    private void FillPanelContent(NodeId panelNodeId, Overview.DisplayMode displayMode, CollapsingPanel overviewPanel)
    {
      // Check the node type of the child items
      int itemNodeType;
      wbOverview.get_field(panelNodeId, (int)Overview.Columns.ChildNodeType, out itemNodeType);

      switch ((Overview.NodeType)itemNodeType)
      {
        case Overview.NodeType.Group:
          {
            // If the child items are of type Group, add them as tabs.
            EnableTabViewForPanel(displayMode, overviewPanel);

            // Fill child items
            if (wbOverview.count_children(panelNodeId) > 0)
              FillPanelContent(wbOverview.get_child(panelNodeId, 0), currentOverviewDisplayMode, overviewPanel);
          }
          break;
        case Overview.NodeType.Section:
          // If they are of item type Section populate the list view with sections and items.
          PopulateSections(overviewPanel, displayMode, true);
          break;
        case Overview.NodeType.Item:
          {
            // If the child entry is an item then we don't have any intermediate structure.
            ListView sectionListview = GetSectionListview(panelNodeId, overviewPanel, false, displayMode, "", "");
            cacheListview(panelNodeId.repr(), sectionListview);
            PopulateListView(panelNodeId, sectionListview);
            break;
          }
      }
    }

    /// <summary>
    /// Helper class to make sorting in our list views case insensitive.
    /// </summary>
    private class InsensitiveListviewComparer : IComparer
    {
      #region IComparer Members

      private CaseInsensitiveComparer comparer = new CaseInsensitiveComparer();

      public int column; // The column to sort on.
      public SortOrder order = SortOrder.None;

      public InsensitiveListviewComparer(int column, SortOrder order)
      {
        this.column = column;
        this.order = order;
      }

      public int Compare(object x, object y)
      {
        if (order == SortOrder.None)
          return 0;

        // Compare text case-insensitively, but make the default entry always the first one.
        if (((ListViewItem)x).Index == 0)
          return -1;
        else
          if (((ListViewItem)y).Index == 0)
            return 1;
          else
          {
            if (order == SortOrder.Ascending)
              return comparer.Compare(
                (x as ListViewItem).SubItems[column].Text,
                (y as ListViewItem).SubItems[column].Text);
            else
              return comparer.Compare(
                (y as ListViewItem).SubItems[column].Text,
                (x as ListViewItem).SubItems[column].Text);
          }
      }

      #endregion
    }

    private void SetInfoLabelForList(ListView list, String info)
    {
      CollapsingPanel overviewPanel = list.Parent as CollapsingPanel;
      if (null == overviewPanel)
        return;

      Panel panel = null;
      foreach (Control control in overviewPanel.Controls)
      {
        if (control == list)
          break;
        panel = control as Panel;
      }

      if (panel != null)
      {
        Label label = panel.Controls[0] as Label;
        if (label != null)
          label.Text = info;
      }
    }

    /// <summary>
    /// Returns the listview for a given section. If it does not exist yet it gets created.
    /// Optionally a simple panel is added as header for this section.
    /// </summary>
    private ListView GetSectionListview(NodeId controlNodeId, CollapsingPanel overviewPanel, bool addSectionHeader,
      Overview.DisplayMode displayMode, String caption, String info)
    {
      // Iterate over all sections we have already and find the one with the given control id.
      String objectId = wbOverview.get_node_unique_id(controlNodeId);
      foreach (Control control in overviewPanel.Controls)
      {
        if (control.Tag != null)
        {
          String currentObjectId = (control.Tag as Identifier).objectId;
          if (currentObjectId != null && currentObjectId == objectId)
          {
            ListView listview = control as ListView;
            SetViewMode(listview, displayMode);
            return listview;
          }
        }
      }

      // If we come here then the section does not exist yet, so add it. Start with a header.
      if (addSectionHeader)
      {
        Panel panel = new Panel();
        panel.BorderStyle = BorderStyle.None;
        panel.Padding = new Padding(5, 2, 5, 0);
        panel.BackgroundImage = Resources.header_bar_blue;
        panel.BackgroundImageLayout = ImageLayout.None;

        panel.Height = 24;

        // Insert client controls in reverse order.
        // Info label.
        Label infoLabel = new Label();
        infoLabel.Text = info;
        infoLabel.ForeColor = Color.Gray;
        infoLabel.Font = overviewPanel.Font;
        infoLabel.AutoSize = true;
        infoLabel.Margin = new Padding(10, 0, 0, 0);
        infoLabel.Dock = DockStyle.Left;
        panel.Controls.Add(infoLabel);

        overviewPanel.Controls.Add(panel);

        // Caption label.
        Label captionLabel = new Label();
        captionLabel.Text = caption;
        //captionLabel.Font = new Font(overviewPanel.Font, FontStyle.Bold);
        captionLabel.AutoSize = true;
        captionLabel.Dock = DockStyle.Left;
        captionLabel.ForeColor = Color.Black;
        panel.Controls.Add(captionLabel);

        overviewPanel.Controls.Add(panel);
      }

      // Then the content view.
      ListView sectionListview = new ListView();
      sectionListview.BorderStyle = BorderStyle.None;
      sectionListview.AllowColumnReorder = true;
      sectionListview.ShowItemToolTips = true;

      // Undocumented feature: enabling AutoSize will indeed make the control automatically resize
      // (the docs say it wouldn't) though without considering long captions. For them to work 
      // additionally Scrollable can be set to true to show a scrollbar, but only in this special case.
      sectionListview.AutoSize = true;
      sectionListview.Scrollable = true;
      sectionListview.Visible = true;
      sectionListview.Font = overviewPanel.Font;
      sectionListview.Tag = CreateIdentifier(controlNodeId);
      sectionListview.Sorting = SortOrder.None; // We do custom sort.

      sectionListview.DoubleClick += new EventHandler(listViewDoubleClick);
      sectionListview.ItemDrag += new ItemDragEventHandler(listViewItemDrag);
      sectionListview.SelectedIndexChanged += new EventHandler(listViewSelectedIndexChanged);
      sectionListview.KeyDown += new KeyEventHandler(listViewKeyDown);
      sectionListview.MouseUp += new MouseEventHandler(listViewMouseUp);
      sectionListview.ColumnClick += new ColumnClickEventHandler(ListviewColumnClick);

      // Renaming of overview nodes
      sectionListview.LabelEdit = true;
      sectionListview.AfterLabelEdit += new LabelEditEventHandler(listViewAfterLabelEdit);
      sectionListview.BeforeLabelEdit += new LabelEditEventHandler(listViewBeforeLabelEdit);

      // Add it to the panel. Usually the layout engine of the panel should take care for
      // the layout of the controls, but just to be sure we set here a dock style.
      overviewPanel.Controls.Add(sectionListview);
      sectionListview.Dock = DockStyle.Top;

      // Set Image Lists
      sectionListview.SmallImageList = GrtIconManager.ImageList16;
      sectionListview.LargeImageList = GrtIconManager.ImageList48;

      SetViewMode(sectionListview, displayMode);

      return sectionListview;
    }

    void ListviewColumnClick(object sender, ColumnClickEventArgs e)
    {
      ListView listview = (sender as ListView);
      SortOrder newOrder = SortOrder.Ascending;
      if (!(listview.ListViewItemSorter is InsensitiveListviewComparer))
        listview.ListViewItemSorter = new InsensitiveListviewComparer(e.Column, newOrder);
      else
      {
        // Adjust sort order depending on which column was clicked on.
        // A previously not selected column always sorts ascending.
        if ((listview.ListViewItemSorter as InsensitiveListviewComparer).column == e.Column)
        {
          // Another click on the same column. Reverse the sort order.
          if ((listview.ListViewItemSorter as InsensitiveListviewComparer).order == SortOrder.Ascending)
            newOrder = SortOrder.Descending;
        }
        listview.ListViewItemSorter = new InsensitiveListviewComparer(e.Column, newOrder);
      }
      Win32.SetSortIcon(listview, e.Column, newOrder);
    }

    /// <summary>
    /// Sets the given panel up so that it shows tabs on top that can be used to switch content.
    /// </summary>
    /// <param name="displayMode"></param>
    /// <param name="overviewPanel"></param>
    private void EnableTabViewForPanel(Overview.DisplayMode displayMode, CollapsingPanel overviewPanel)
    {
      overviewPanel.TabHeaderImageList = new ImageList();
      overviewPanel.TabHeaderImageList.ColorDepth = ColorDepth.Depth32Bit;
      overviewPanel.TabHeaderImageList.ImageSize = new Size(32, 32);

      overviewPanel.TabHeaderImageList = GrtIconManager.ImageList32;

      overviewPanel.DisplayMode = CollapsingPanelDisplayMode.HeaderAndTab;

      overviewPanel.TabCountNeeded += new TabCountHandler(overviewPanel_TabCountNeeded);
      overviewPanel.TabInfoNeeded += new TabInfoHandler(overviewPanel_TabInfoNeeded);
      overviewPanel.TabChanged += new TabChangedHandler(overviewPanel_TabChanged);
      overviewPanel.TabDoubleClicked += new MouseEventHandler(overviewPanel_TabDoubleClicked);
      overviewPanel.TabHeaderMouseUp += new MouseEventHandler(overviewPanel_TabHeaderMouseUp);

      // Select the first tab to populate the ListView
      overviewPanel.SelectedTabIndex = 0;

      overviewPanel.DisplayTabActionButtons = true;
      overviewPanel.TabAddButtonClicked += new EventHandler(overviewPanel_TabAddButtonClicked);
      overviewPanel.TabDelButtonClicked += new EventHandler(overviewPanel_TabDelButtonClicked);

      // Buttons for display mode.
      overviewPanel.DisplayCustomButtons = true;
      overviewPanel.CustomButtonClicked += new CustomButtonEvent(overviewPanel_CustomButtonClicked);

      overviewPanel.AddCustomButton(26, 23, "collapsing_panel_grid_large_icons",
        displayMode == Overview.DisplayMode.LargeIcon);
      overviewPanel.AddCustomButton(26, 23, "collapsing_panel_grid_small_icons",
        displayMode == Overview.DisplayMode.SmallIcon);
      overviewPanel.AddCustomButton(26, 23, "collapsing_panel_grid_details",
        displayMode == Overview.DisplayMode.List);
    }

    /// <summary>
    /// Save the state of the current columns in the given list view so it can be restored later.
    /// </summary>
    /// <param name="listview"></param>
    private void SaveColumnStates(ListView listview)
    {
      String id = (listview.Tag as Identifier).objectId;
      if (id != null)
      {
        List<int> widths = new List<int>();
        for (int i = 0; i < listview.Columns.Count; i++)
          widths.Add(listview.Columns[i].Width);
        columnStates[id] = widths;
      }
    }

    /// <summary>
    /// Restores a saved column state of the given listview (if it exists).
    /// </summary>
    /// <param name="listview"></param>
    private void RestoreColumnStates(ListView listview)
    {
      String id = (listview.Tag as Identifier).objectId;
      if (id != null && columnStates.ContainsKey(id))
      {
        List<int> widths = (List<int>)columnStates[id];
        for (int i = 0; i < widths.Count && i < listview.Columns.Count; i++)
          listview.Columns[i].Width = widths[i];
      }
    }

    /// <summary>
    /// Fills the given panel with a number of sections (simple panel + listview pair) per section
    /// entry in wbOverview.
    /// </summary>
    private void PopulateSections(CollapsingPanel overviewPanel, Overview.DisplayMode displayMode, bool setDefaultTabPage)
    {
      if (overviewPanel != null && overviewPanel.Tag != null)
      {
        overviewPanel.SuspendLayout();
        try
        {
          NodeId parentNodeId = null;

          // If this panel has tabs, choose the NodeId of the selected tab as parent.
          if (overviewPanel.DisplayMode == CollapsingPanelDisplayMode.HeaderAndTab ||
            overviewPanel.DisplayMode == CollapsingPanelDisplayMode.TabsOnly)
          {
            // make sure the selected index is still valid
            int count = wbOverview.count_children((overviewPanel.Tag as Identifier).id);
            if (count > 0)
            {
              int default_tab_page_index = wbOverview.get_default_tab_page_index();
              if (setDefaultTabPage && default_tab_page_index != -1)
              {
                overviewPanel.SelectedTabIndex = default_tab_page_index;
              }
              else if (overviewPanel.SelectedTabIndex > count - 1)
              {
                if (count > 0)
                  overviewPanel.SelectedTabIndex = count - 1;
                else
                  overviewPanel.SelectedTabIndex = 0;
              }

              parentNodeId = wbOverview.get_child((overviewPanel.Tag as Identifier).id,
                overviewPanel.SelectedTabIndex);

              // reselect the tab
              wbOverview.focus_node(parentNodeId);
            }
            else
            {
              overviewPanel.SelectedTabIndex = 0;
              parentNodeId = null;
            }
          }
          else
            // If this panel has no tabs, choose the NodeId of the panel itself as parent
            if (overviewPanel.DisplayMode == CollapsingPanelDisplayMode.Normal)
              parentNodeId = (overviewPanel.Tag as Identifier).id;

          if (parentNodeId != null)
          {
            int itemCount = wbOverview.count_children(parentNodeId);
            if (itemCount > 0)
            {
              NodeId itemNodeId = wbOverview.get_child(parentNodeId, 0);

              // Check the node type of the child items
              int nodeType;
              wbOverview.get_field(itemNodeId, (int)Overview.Columns.NodeType, out nodeType);

              // If the node type is secion, loop over all sections and add them
              if (nodeType == (int)Overview.NodeType.Section)
              {
                for (int i = 0; i < itemCount; i++)
                {
                  NodeId sectionNodeId = wbOverview.get_child(parentNodeId, i);

                  string caption;
                  wbOverview.get_field(sectionNodeId, (int)Overview.Columns.Label, out caption);

                  // Determine child count for info display but don't consider the default entry.
                  int childCount = wbOverview.count_children(sectionNodeId) - 1;
                  String info = "(" + childCount + ((childCount == 1) ? " item" : " items") + ")";
                  ListView sectionListview = GetSectionListview(sectionNodeId, overviewPanel, true, displayMode, caption, info);

                  cacheListview(sectionNodeId.repr(), sectionListview);

                  PopulateListViewColumns(sectionNodeId, sectionListview, displayMode);

                  PopulateListView(sectionNodeId, sectionListview);

                  if (sectionListview.View == View.Details)
                    RestoreColumnStates(sectionListview);
                }
              }
              else
              {
                // If there are no sections, simple add the items.
                ListView sectionListview = GetSectionListview(parentNodeId, overviewPanel, false, displayMode, "", "");
                cacheListview(parentNodeId.repr(), sectionListview);
                PopulateListView(parentNodeId, sectionListview);
              }
            }
          }
        }
        finally
        {
          overviewPanel.ResumeLayout();
        }
      }
    }

    private void PopulateListViewColumns(NodeId sectionNodeId, ListView sectionListview, Overview.DisplayMode displayMode)
    {
      // Refresh columns for this view.
      sectionListview.Columns.Clear();
      sectionListview.Columns.Add("Name", 200);

      // Only add more columns if we are not in tile view mode.
      // We add columns also for large icon view, even though we don't see them, as we can then
      // quicker switch between large icon and tile view (no need to re-populate the view then).
      if (displayMode == Overview.DisplayMode.List)
      {
        int columnCount = wbOverview.get_details_field_count(sectionNodeId);
        for (int counter = 0; counter < columnCount; counter++)
        {
          String columnCaption = wbOverview.get_field_name(sectionNodeId,
            (int)Overview.Columns.FirstDetailField + counter);
          sectionListview.Columns.Add(columnCaption, 100);
        }
      }
    }


    private void PopulateListView(NodeId parentNodeId, ListView listView)
    {
      listView.BeginUpdate();
      try
      {
        int itemCount = wbOverview.count_children(parentNodeId);
        ListViewItem[] items = new ListViewItem[itemCount];

        int detail_fields_count = wbOverview.get_details_field_count(parentNodeId);

        for (int i = 0; i < itemCount; i++)
        {
          NodeId itemNodeId = wbOverview.get_child(parentNodeId, i);
          string caption;

          wbOverview.get_field(itemNodeId, (int)Overview.Columns.Label, out caption);
          ListViewItem item = new ListViewItem(caption, FindImageIndex(itemNodeId, listView.View));
          item.Tag = CreateIdentifier(itemNodeId);
          item.ToolTipText = caption;

          // Add details for that item, if available.
          for (int counter = 0; counter < detail_fields_count; counter++)
          {
            String detailText;
            wbOverview.get_field(itemNodeId, (int)Overview.Columns.FirstDetailField + counter, out detailText);
            item.SubItems.Add(detailText.Replace(Environment.NewLine, " / "));
          }

          items[i] = item;
        }
        listView.Items.Clear();
        listView.Items.AddRange(items);

        // Setting the sorter also starts sorting.
        listView.ListViewItemSorter = new InsensitiveListviewComparer(0, SortOrder.Ascending);
        Win32.SetSortIcon(listView, 0, SortOrder.Ascending);

        // select stuff
        List<int> selected = wbOverview.get_selected_children(parentNodeId);
        foreach (int i in selected)
          listView.SelectedIndices.Add(i);
      }
      finally
      {
        listView.EndUpdate();
        listView.PerformLayout();
      };
    }

    /// <summary>
    /// Updates all fields of the given node.
    /// </summary>
    private void UpdateListViewNode(NodeId nodeId, ListView listView)
    {
      // Find the listview item that corresponds to the given node id and update it.
      // Don't wrap this with BeginUpdate/EndUpdate. That will produce more flicker than
      // just updating the item.
      String objectId = wbOverview.get_node_unique_id(nodeId);
      foreach (ListViewItem item in listView.Items)
      {
        String currentObjectId = (item.Tag as Identifier).objectId;
        if (currentObjectId != null && currentObjectId == objectId)
        {
          string caption;

          wbOverview.get_field(nodeId, (int)Overview.Columns.Label, out caption);
          item.Text = caption;
          item.ToolTipText = caption;

          // Note: the first sub item is the same as the main item.
          int offset = -1;
          foreach (ListViewItem.ListViewSubItem subItem in item.SubItems)
          {
            if (offset > -1)
            {
              String detailText;
              wbOverview.get_field(nodeId, (int)Overview.Columns.FirstDetailField + offset, out detailText);
              subItem.Text = detailText.Replace(Environment.NewLine, " / ");
            }
            offset++;
          }
          break;
        }
      }
    }

    void PopupActionHandler(object sender, EventArgs e)
    {
      ToolStripItem item = sender as ToolStripItem;

      if (item != null)
      {
        String name = item.Name;

        if (name == "builtin:rename")
        {
          if (item.Owner.Tag is CollapsingPanel)
          {
            CollapsingPanel panel = item.Owner.Tag as CollapsingPanel;
          }
          else if (item.Owner.Tag is ListView)
          {
            ListView list = item.Owner.Tag as ListView;

            if (list.SelectedItems.Count > 0)
              list.SelectedItems[0].BeginEdit();
          }
        }
        else if (name == "drop")
        {
          List <NodeId> node_ids = new List<NodeId>();

          if (item.Owner.Tag is ListView)
          {
            ListView list = item.Owner.Tag as ListView;
            foreach (ListViewItem list_item in list.SelectedItems)
              node_ids.Add((list_item.Tag as Identifier).id);
          }
          else
            node_ids.Add((item.Tag as Identifier).id);

          wbOverview.activate_popup_item_for_nodes(name, node_ids);
        }
        else
          wbOverview.activate_popup_item_for_nodes(name, new List<NodeId>() { (item.Tag as Identifier).id });
      }
    }

    void overviewPanel_TabHeaderMouseUp(object sender, MouseEventArgs e)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null)
      {
        if (panel != null && panel.Tag != null)
        {
          List<MySQL.Grt.MenuItem> items;
          List<NodeId> nodes = new List<NodeId>();

          NodeId itemNodeId= new NodeId();
          int tab = panel.GetTabAtPosition(e.X, e.Y);
          if (tab >= 0)
          {
            itemNodeId = wbOverview.get_child((panel.Tag as Identifier).id, tab);
            nodes.Add(itemNodeId);
          }

          items = wbOverview.get_popup_items_for_nodes(nodes);

          if (items != null && items.Count > 0)
          {
            System.Windows.Forms.ContextMenuStrip menu;

            menu = workbenchMenuManager.ShowContextMenu(panel, items, e.X, e.Y, new EventHandler(PopupActionHandler));
            menu.Tag = panel;

            SetMenuItemsTag(menu.Items, CreateIdentifier(itemNodeId));
          }
        }
      }
    }

    void SetMenuItemsTag(ToolStripItemCollection menuItems, object tagObject)
    {
      foreach (ToolStripItem tsitem in menuItems)
      {
        if (tsitem is ToolStripMenuItem)
        {
          ToolStripMenuItem item = tsitem as ToolStripMenuItem;
          if (item.Tag != null)
            throw new Exception("item.Tag was expected to be unset");
          if ((item.DropDownItems != null) && (item.DropDownItems.Count > 0))
            SetMenuItemsTag(item.DropDownItems, tagObject);
          else
            item.Tag = tagObject;
        }
      }
    }

    void listViewKeyDown(object sender, KeyEventArgs e)
    {
      ListView listView = sender as ListView;

      if (e.Modifiers == Keys.None && e.KeyCode == Keys.F2)
      {
        if (listView.SelectedItems.Count > 0)
          listView.SelectedItems[0].BeginEdit();
      }
    }

    void listViewMouseUp(object sender, MouseEventArgs e)
    {
      ListView listView = sender as ListView;

      if (listView != null && e.Button == MouseButtons.Right)
      {
        List<MySQL.Grt.MenuItem> items = new List<MySQL.Grt.MenuItem>();
        List<NodeId> item_nodes = new List<NodeId>();

        if (listView.SelectedItems.Count == 0)
        {
          item_nodes.Add((listView.Tag as Identifier).id); // nodeid for the group represented by the listview
        }
        else
        {
          foreach (ListViewItem item in listView.SelectedItems)
            item_nodes.Add((item.Tag as Identifier).id);
        }

        if (item_nodes.Count != 0)
        {
          items = wbOverview.get_popup_items_for_nodes(item_nodes);
          if (items.Count > 0)
          {
            System.Windows.Forms.ContextMenuStrip menu;

            menu = workbenchMenuManager.ShowContextMenu(listView, items, e.X, e.Y, new EventHandler(PopupActionHandler));
            menu.Tag = listView;

            SetMenuItemsTag(menu.Items, CreateIdentifier(item_nodes[0]));
          }
        }
      }
    }

    void listViewSelectedIndexChanged(object sender, EventArgs e)
    {
      ListView listView = sender as ListView;

      // If the parent of the listview is null then is it currently being destroyed.
      // Strangely enough, listView.Disposing is still false.
      if (listView != null && listView.Parent != null)
      {
        wbOverview.begin_selection_marking();

        foreach (ListViewItem item in listView.SelectedItems)
        {
          NodeId itemNodeId = (item.Tag as Identifier).id;
          try
          {
            wbOverview.select_node(itemNodeId);
          }
          catch
          {
          }
        }

        wbOverview.end_selection_marking();
      }
    }

    void listViewBeforeLabelEdit(object sender, LabelEditEventArgs e)
    {
      ListView listView = sender as ListView;
      ListViewItem item = listView.Items[e.Item];
      if (item == null)
        return;

      if (item.Tag == null)
        e.CancelEdit = true;
      else
      {
        NodeId itemNodeId = (item.Tag as Identifier).id;
        if (itemNodeId == null || !wbOverview.is_editable(itemNodeId))
          e.CancelEdit = true;
      }
    }

    void listViewAfterLabelEdit(object sender, LabelEditEventArgs e)
    {
      ListView listView = sender as ListView;

      if (listView != null && e.Item >= 0 && e.Label != null && !e.Label.Equals(""))
      {
        ListViewItem item = listView.Items[e.Item];
        if (item != null && item.Tag != null)
        {
          NodeId itemNodeId = (item.Tag as Identifier).id;
          if (itemNodeId != null)
          {
            if (!wbOverview.set_field(itemNodeId, (int)Overview.Columns.Label, e.Label))
              e.CancelEdit = true;
          }
        }
      }
      else
        if (e.Label != null)
          e.CancelEdit = true;
    }

    void listViewItemDrag(object sender, ItemDragEventArgs e)
    {
      ListView listView = sender as ListView;

      if (listView != null)
      {
        ListView.SelectedListViewItemCollection selItems = listView.SelectedItems;
        List<GrtValue> selObjects = new List<GrtValue>();

        foreach (ListViewItem item in selItems)
        {
          if (item.Tag != null)
          {
            NodeId itemNodeId = (item.Tag as Identifier).id;
            GrtValue val = wbOverview.get_grt_value(itemNodeId, 0);
            if (itemNodeId != null)
              selObjects.Add(val);
          }
        }

        if (selObjects.Count > 0)
          DoDragDrop(selObjects, DragDropEffects.Copy);
      }
    }

    void listViewDoubleClick(object sender, EventArgs e)
    {
      ListView view = sender as ListView;
      if (view != null && view.SelectedItems.Count > 0 && view.SelectedItems[0].Tag != null)
      {
        NodeId nodeId = (view.SelectedItems[0].Tag as Identifier).id;
        if (nodeId != null)
          wbOverview.activate_node(nodeId);
      }
    }

    private void overviewPanel_TabDoubleClicked(object sender, MouseEventArgs e)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null && panel.Tag != null)
        wbOverview.activate_node(
          wbOverview.get_child((panel.Tag as Identifier).id, panel.SelectedTabIndex));
    }

    private void overviewPanel_TabChanged(object sender, int index)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null && panel.Tag != null && index >= 0)
        wbOverview.select_node(wbOverview.get_child((panel.Tag as Identifier).id, panel.SelectedTabIndex));

      // Delete all sections in the panel and re-populate it.
      SuspendLayout();
      try
      {
        foreach (Control control in panel.Controls)
          if (control is ListView)
          {
            ListView view = control as ListView;
            if (view.View == View.Details)
              SaveColumnStates(view);
          }
        panel.Controls.Clear();
        PopulateSections(panel, currentOverviewDisplayMode, false);
      }
      finally
      {
        ResumeLayout();
      }
    }

    private int overviewPanel_TabCountNeeded(object sender)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null && panel.Tag != null)
        return wbOverview.count_children((panel.Tag as Identifier).id);
      else
        return 0;
    }

    private bool overviewPanel_TabInfoNeeded(object sender, int index, out int iconId, out string caption, out string description)
    {
      iconId = 0;
      description = "";
      caption = "";

      CollapsingPanel panel = sender as CollapsingPanel;
      if (panel != null && panel.Tag != null)
      {
        NodeId tabNodeId = wbOverview.get_child((panel.Tag as Identifier).id, index);
        if (tabNodeId != null)
        {
          wbOverview.get_field(tabNodeId, (int)Overview.Columns.Label, out caption);
          description = wbOverview.get_field_description(tabNodeId, 0);
          int grtIconId = wbOverview.get_field_icon(tabNodeId, (int)Overview.Columns.Label, IconSize.Icon32);
          iconId = GrtIconManager.get_instance().add_icon_to_imagelist(grtIconId);
        }
      }

      return true;
    }

    void overviewPanel_TabDelButtonClicked(object sender, EventArgs e)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null && panel.Tag != null)
      {
        NodeId groupNodeId = (panel.Tag as Identifier).id;

        // Delete section object.
        wbOverview.request_delete_object(wbOverview.get_focused_child(groupNodeId));
      }
    }

    void overviewPanel_TabAddButtonClicked(object sender, EventArgs e)
    {
      CollapsingPanel panel = sender as CollapsingPanel;

      if (panel != null && panel.Tag != null)
      {
        NodeId groupNodeId = (panel.Tag as Identifier).id;

        // Add section object.
        wbOverview.request_add_object(groupNodeId);
        wbOverview.refresh_node(groupNodeId, true);
      }
    }

    void overviewPanel_CustomButtonClicked(object sender, int index)
    {
      CollapsingPanel panel = sender as CollapsingPanel;
      Overview.DisplayMode newMode = (Overview.DisplayMode)(index + 1);
      if (newMode != currentOverviewDisplayMode)
      {
        bool fullRefresh = (newMode == Overview.DisplayMode.SmallIcon) ||
          (currentOverviewDisplayMode == Overview.DisplayMode.SmallIcon);
        currentOverviewDisplayMode = newMode;

        // Unfortunately, we cannot just switch the view mode here as this would leave all columns
        // intact, which in turn means we have unwanted info in tile mode.
        // So we have to fully re-populate the listviews in cases where we switch from or to tile mode.
        if (fullRefresh)
          PopulateSections(panel, currentOverviewDisplayMode, false);
        else
          SetViewMode(panel, currentOverviewDisplayMode);
      }
    }

    private void ModelOverviewForm_Shown(object sender, EventArgs e)
    {
      LoadFormState();
    }

    internal void ResetDocument(bool removePanels)
    {
      overviewInvalid = false;

      SuspendLayout();

      try
      {
        foreach (CollapsingPanel panel in panelList)
        {
          panel.Controls.Clear();
          if (removePanels && panel.Parent != null)
            panel.Parent.Controls.Remove(panel);
        }

        if (removePanels)
          panelList.Clear();
      }
      finally
      {
        ResumeLayout();
      }
    }

    /// <summary>
    /// Returns the current view mode of the listview that represents the content of the given panel.
    /// </summary>
    /// <param name="panel"></param>
    /// <returns></returns>
    private View GetViewMode(CollapsingPanel panel)
    {
      foreach (Control control in panel.Controls)
        if (control is ListView)
          return (control as ListView).View;

      return View.LargeIcon;
    }

    #endregion

    #region Other Implementation

    private void SetupSideBar()
    {
      mainSplitContainer.SuspendLayout();
      sideSplitContainer.SuspendLayout();
      try
      {
        // Rebuild side bar.
        sideTopTabControl.TabPages.Clear();
        DockSideDocument(modelObjectDescriptionForm, true, true);

        sideBottomTabControl.TabPages.Clear();
        DockSideDocument(userDatatypesForm, false, true);
        DockSideDocument(historyForm, false, false);

        // Some values constantly crash the designer because code generation is buggy.
        // So we better set that here.
        contentSplitContainer.Panel2MinSize = 300;
        sideSplitContainer.SplitterWidth = 6;
      }
      finally
      {
        mainSplitContainer.ResumeLayout(true);
        sideSplitContainer.ResumeLayout(true);
      }
    }

    private void DockSideDocument(ITabDocument document, bool top, bool activate)
    {
      if (top)
      {
        if (!sideTopTabControl.HasDocument(document))
        {
          int index = sideTopTabControl.AddDocument(document);
          sideTopTabControl.TabPages[index].BackColor = Color.White;
        }
      }
      else
      {
        if (!sideBottomTabControl.HasDocument(document))
        {
          int index = sideBottomTabControl.AddDocument(document);
          sideBottomTabControl.TabPages[index].BackColor = Color.White;
        }
      }
      if (activate)
        document.Activate();

      if (sideBottomTabControl.TabCount == 0)
        sideSplitContainer.Panel2Collapsed = true; // This will implicitly expand panel1.
      else
        sideSplitContainer.Panel1Collapsed = false;
      if (sideTopTabControl.TabCount == 0)
        sideSplitContainer.Panel1Collapsed = true; // This will implicitly expand panel2.
      else
        sideSplitContainer.Panel2Collapsed = false;
    }

    /// <summary>
    /// Docks the given document to the bottom tab control (like object editors etc.).
    /// </summary>
    /// <param name="document"></param>
    public void DockDocument(ITabDocument document, bool activate)
    {
      if (!bottomTabControl.HasDocument(document))
        bottomTabControl.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)
    {
      if (bottomTabControl.HasDocument(document))
        bottomTabControl.RemoveDocument(document);
    }

    public void CloseEditorsForObject(string oid)
    {
      ITabDocument[] documents = bottomTabControl.DocumentsToArray();

      // loop over all documents
      for (int i = documents.Length - 1; i >= 0; i--)
      {
        if (documents[i] is ObjectEditorView)
        {
          ObjectEditorView editor = documents[i] as ObjectEditorView;
          if (oid == "" || editor.EditorPlugin.ShouldCloseOnObjectDelete(oid))
            bottomTabControl.CloseDocument(editor);
        }
      }
    }

    /// <summary>
    /// Determines if this document can be closed, i.e. all sub documents are queried for closing
    /// as well. Returns false if this form cannot be closed.
    /// </summary>
    /// <returns></returns>
    public bool CanCloseDocument()
    {
      foreach (ITabDocument document in bottomTabControl.Documents)
        if (document is IWorkbenchDocument)
          if (!(document as IWorkbenchDocument).CanCloseDocument())
            return false;

      if (!BackendForm.can_close())
        return false;

      return true;
    }

    public void CloseDocument()
    {
      foreach (ITabDocument document in bottomTabControl.Documents)
        if (document is IWorkbenchDocument)
          (document as IWorkbenchDocument).CloseDocument();
      BackendForm.close();
    }

    private void ShowUserDatatypes()
    {
      mainSplitContainer.Panel2Collapsed = false;
      userDatatypesForm.Activate();
    }

    private void ShowDescriptions()
    {
      mainSplitContainer.Panel2Collapsed = false;
      modelObjectDescriptionForm.Activate();
    }

    private void ShowUndoHistory()
    {
      mainSplitContainer.Panel2Collapsed = false;
      historyForm.Activate();
    }

    private void LoadFormState()
    {
      int sidebarWidth = wbContext.read_state("sidebar_width", "model_overview", 200);
      bool left_aligned = wbContext.read_option_value("", "Sidebar:RightAligned", "0") == "0";

      mainSplitContainer.Panel1.Controls.Clear();
      mainSplitContainer.Panel2.Controls.Clear();
      if (left_aligned)
      {
        mainSplitContainer.FixedPanel = FixedPanel.Panel1;
        mainSplitContainer.Panel1.Controls.Add(sideSplitContainer);
        mainSplitContainer.Panel2.Controls.Add(contentSplitContainer);
        if (mainSplitContainer.Width > sidebarWidth)
          mainSplitContainer.SplitterDistance = sidebarWidth;
      }
      else
      {
        mainSplitContainer.FixedPanel = FixedPanel.Panel2;
        mainSplitContainer.Panel2.Controls.Add(sideSplitContainer);
        mainSplitContainer.Panel1.Controls.Add(contentSplitContainer);
        if (mainSplitContainer.Width > sidebarWidth)
          mainSplitContainer.SplitterDistance = mainSplitContainer.Width - mainSplitContainer.SplitterWidth - sidebarWidth;
      }

      NodeId nodeId = wbOverview.get_root();
      String caption;
      wbOverview.get_field(nodeId, (int)Overview.Columns.Label, out caption);
      String domain = caption + "-overview";
      foreach (CollapsingPanel panel in panelList)
      {
        panel.Expanded = wbContext.read_state(panel.HeaderCaption + "-expanded", domain, panel.Expanded);
        View viewMode = (View)wbContext.read_state(panel.HeaderCaption + "-mode", domain, (int)GetViewMode(panel));
        SetViewMode(panel, viewMode);

        switch (viewMode)
        {
          case View.LargeIcon:
            panel.ActiveCustomButton = 0;
            break;
          case View.Tile:
            panel.ActiveCustomButton = 1;
            break;
          case View.Details:
            panel.ActiveCustomButton = 2;
            break;
        }
      }
    }

    private void SaveFormState()
    {
      NodeId nodeId = wbOverview.get_root();
      String caption;
      wbOverview.get_field(nodeId, (int)Overview.Columns.Label, out caption);
      String domain = caption + "-overview";
      foreach (CollapsingPanel panel in panelList)
      {
        wbContext.save_state(panel.HeaderCaption + "-expanded", domain, panel.Expanded);
        wbContext.save_state(panel.HeaderCaption + "-mode", domain, (int)GetViewMode(panel));
      }
    }

    private void mainSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
    {
      if (splitterMovePending)
      {
        splitterMovePending = false;

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

    private void mainSplitContainer_SplitterMoving(object sender, SplitterCancelEventArgs e)
    {
      // This event only comes up when the splitter is moved with the mouse (i.e. by the user).
      // We can use it to differentiate between user initiated and programmatic splitter changes.
      splitterMovePending = true;
    }

    private void ModelOverviewForm_FormClosing(object sender, FormClosingEventArgs e)
    {
      SaveFormState();
    }

    /// <summary>
    /// Keeps an association between a node (id) and a listview for later quick access.
    /// Frees any resource links to the backend if a listview is removed.
    /// </summary>
    /// <param name="id"></param>
    private void cacheListview(String id, ListView listview)
    {
      if (listsByNode.ContainsKey(id))
      {
        if (listsByNode[id] != listview)
        {
          listsByNode[id].SmallImageList = null;
          listsByNode[id].LargeImageList = null;
          listsByNode[id].Dispose();
          listsByNode[id] = listview;
        }
      }
      else
        listsByNode[id] = listview;
    }

    #endregion

  }
}
