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

using WeifenLuo.WinFormsUI.Docking;

using MySQL.Grt;
using MySQL.Workbench;
using MySQL.Utilities;
using MySQL.Utilities.SysUtils;
using MySQL.GUI.Workbench.Plugins;
using MySQL.GUI.Workbench.Properties;

namespace MySQL.GUI.Workbench
{
  public partial class WorkbenchOverviewForm : DockContent, IWorkbenchDocument
  {
    #region Member Variables
    
    // Flags to avoid recursive refreshs.
    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 menuManager;

    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;

    #endregion

    #region Constructors

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

      wbContext = WbContext;
      wbOverview = be;

      Text = be.get_title();
      TabText = be.get_title();

      menuManager = new WorkbenchMenuManager(wbContext);

      Application.AddMessageFilter(new WheelMessageFilter(this));
    }

    #endregion

    #region Internal classes

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

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

      public bool PreFilterMessage(ref Message m)
      {
        if (m.Msg == Win32.WM_MOUSEWHEEL && form.VerticalScroll.Visible)
        {
          // Only handle messages if the mouse pointer is in the form's client area.
          // Also check we don't have any other window (e.g. dialogs) in front.
          Rectangle rect = form.RectangleToScreen(form.ClientRectangle);
          Point cursorPoint = new Point();
          Win32.GetCursorPos(ref cursorPoint);
          IntPtr targetWindow = Win32.WindowFromPoint(cursorPoint);
          Control control = Form.FromHandle(targetWindow);
          if (control != null && (control.FindForm() == form) && rect.Contains(cursorPoint))
          {
            // Forward the message to the form.
            Win32.SendMessage(form.Handle, m.Msg, m.WParam, m.LParam);
            return true;
          }
        }

        return false;
      }
    }
    
    #endregion

    #region IWorkbenchDocument Interface

    public WorkbenchDocumentClosing OnWorkbenchDocumentClosing
    {
      get { return null; }
      set { ; }
    }

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


    #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
          if (listsByNode.ContainsKey(wbOverview.get_parent(node).repr()))
          {
            ListView list = listsByNode[wbOverview.get_parent(node).repr()];

            //XXX would be good if it updated only the required node instead of everything
            populateListView(wbOverview.get_parent(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 (!node.is_valid())
      {
        Text = wbOverview.get_title();
        TabText = wbOverview.get_title();

        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);

        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();

        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;

            CollapsingPanel overviewPanel = new CollapsingPanel(caption, CollapsingPanelDisplayMode.Normal,
              CollapsingPanelStyle.Flat, false, height);
            overviewPanel.HeaderFont = new Font("Tahoma", 9.75f, FontStyle.Bold);
            overviewPanel.Padding = new Padding(12, 4, 0, 0);

            panelsByNode[panelNodeId.repr()] = overviewPanel;

            overviewPanel.Tag = createIdentifier(panelNodeId);

            // Add the collapsing panel to the form.
            Controls.Add(overviewPanel);
            Controls.SetChildIndex(overviewPanel, 1);
            overviewPanel.Dock = DockStyle.Top;

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

            // Check if there are child items
            //int itemCount = wbOverview.count_children(panelNodeId);
            //if (itemCount > 0)
              FillPanelContent(panelNodeId, displayMode, overviewPanel);
            overviewPanel.Expanded = expanded == 1;
          }
        }
        finally
        {
          ResumeLayout();
        }
      }
    }

    #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.back();

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

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

    #endregion

    #region Form implementation

    protected override Point ScrollToControl(Control activeControl)
    {
      // Prevent the form to automatically scroll to the control that gets the focus.
      return DisplayRectangle.Location;
    }

    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
            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);
          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, "", "");
          listsByNode[panelNodeId.repr()] = sectionListview;
          populateListView(panelNodeId, sectionListview);
          break;
        }

        case Overview.NodeType.Special: // wb central splash
        {
          WorkbenchCentralForm form = new WorkbenchCentralForm(wbOverview);

          overviewPanel.Controls.Add(form);

          break;
        }
      }
    }

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

      private CaseInsensitiveComparer comparer = new CaseInsensitiveComparer();

      public int Compare(object x, object y)
      {
        // 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
            return comparer.Compare(((ListViewItem)x).Text, ((ListViewItem)y).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;
        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;
      sectionListview.AutoSize = true;
      sectionListview.Scrollable = false;
      sectionListview.Visible = true;
      sectionListview.Font = overviewPanel.Font;
      sectionListview.Tag = createIdentifier(controlNodeId);
      sectionListview.ListViewItemSorter = new InsensitiveListviewComparer();
      sectionListview.Sorting = SortOrder.None; // We do custom sort.

      sectionListview.DoubleClick += new EventHandler(panelListView_DoubleClick);
      sectionListview.ItemDrag += new ItemDragEventHandler(panelListView_ItemDrag);
      sectionListview.SelectedIndexChanged += new EventHandler(panelListView_SelectedIndexChanged);
      sectionListview.KeyDown += new KeyEventHandler(panelListView_KeyDown);
      sectionListview.MouseUp += new MouseEventHandler(panelListView_MouseUp);

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

      // 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;
    }

    /// <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)
    {
      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)
            {
              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);

                  listsByNode[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, "", "");
                listsByNode[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)
    {
      //why this is? if (viewPanelNodeId != null)
      {
        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);

            // 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);
          listView.Sort();

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

    void popupActionHandler(object sender, EventArgs e)
    {
      System.Windows.Forms.ToolStripMenuItem item = sender as System.Windows.Forms.ToolStripMenuItem;

      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
          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;
      int tab;

      if (panel != null && (tab = panel.GetTabAtPosition(e.X, e.Y)) >= 0)
      {
        if (panel != null && panel.Tag != null)
        {
          List<MySQL.Grt.MenuItem> items;
      	  List<NodeId> nodes= new List<NodeId>();
          NodeId 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 = menuManager.PopUpObjectContextMenu(panel, items, e.X, e.Y,
              new EventHandler(popupActionHandler));
            menu.Tag = panel;

            foreach (ToolStripItem item in menu.Items)
            {
              if (item.Tag != null) 
                throw new Exception("item.Tag was expected to be unset");
              item.Tag = createIdentifier(itemNodeId);
            }
          }
        }
      }
    }

    void panelListView_KeyDown(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 panelListView_MouseUp(object sender, MouseEventArgs e)
    {
      ListView listView = sender as ListView;

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

        if (listView.SelectedItems.Count != 0)
          itemNodeId = (listView.SelectedItems[0].Tag as Identifier).id;
        else 
//          if (listView.SelectedItems.Count == 0)
            itemNodeId = (listView.Tag as Identifier).id; // nodeid for the group represented by the listview

        if (itemNodeId != null)
        {
          items= wbOverview.get_popup_items_for_nodes(new List<NodeId>(){itemNodeId});
          if (items.Count > 0)
          {
            System.Windows.Forms.ContextMenuStrip menu;

            menu = menuManager.PopUpObjectContextMenu(listView, items, e.X, e.Y,
              new EventHandler(popupActionHandler));
            menu.Tag = listView;

            foreach (ToolStripItem item in menu.Items)
            {
              if (item.Tag != null) 
                throw new Exception("item.Tag was expected to be unset");
              item.Tag = createIdentifier(itemNodeId);
            }
          }
        }
      }
    }

    void panelListView_SelectedIndexChanged(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 panelListView_BeforeLabelEdit(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 panelListView_AfterLabelEdit(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 panelListView_ItemDrag(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 panelListView_DoubleClick(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.focus_node(wbOverview.get_child((panel.Tag as Identifier).id, panel.SelectedTabIndex));

      // Delete all sections in the panel and re-populate it.
      SuspendLayout();
      try
      {
        //xxxif (panel == schemataPanel)
        {
          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);
      }
      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();
        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);
        else
          setViewMode(panel, currentOverviewDisplayMode);
      }
    }

    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();
      }
    }

    private void WorkbenchOverviewForm_FormClosing(object sender, FormClosingEventArgs e)
    {
      // Save current state.
      //int count = modelTypeCollapsingPanel_TabCountNeeded(null);
      //for (int i = 0; i < count; i++)
      {
        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));
        }

      }
    }

    /// <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;
    }

    /// <summary>
    /// Loads the previously stored overview page state info and re-applies them.
    /// </summary>
    private void restoreState()
    {
      //int count = modelTypeCollapsingPanel_TabCountNeeded(null);
      //for (int i = 0; i < count; i++)
      {
        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;
          }
        }
      }
    }

    #endregion

  }
}
