/*******************************************************************************
 * Copyright (c) 2004, 2018 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * Contributors:
 *     IBM Corporation - Initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *******************************************************************************/
package org.eclipse.wst.common.ui.internal.viewers;

import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableCursor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;


  /**
   * Adds a TableCursor to a StructuredViewer - for keyboard navigation of the table
   * The intent of this class is to provide the standard listeners for using F2 to
   * activate cell editors.
   *
   * Due to a current bug in the TableCursor, TableViewers using this class must make
   * a call similar to the TableNavigator method moveCellEditorsAbove(cellEditors)
   * whenever a setCellEditors call is made in the StructuredViewer.  This is so that the
   * cell editor control shows up above the table cursor control.
   */

public class TableNavigator extends TableCursor
{
  private static final String TABLETREEITEM_ID = "TableTreeItemID";

   final Table table;

   public TableNavigator(Table table, StructuredViewer viewer)
   {
      super(table, SWT.NONE);
      this.table = table;
      final Table currentTable = table;
      final StructuredViewer sViewer = viewer;

      // Linux index out of bounds fix.  See defect 253429, 253433, and more
      setVisible(false);

      addPaintListener(viewer);
      addKeyListeners(viewer);
      addMouseListeners(viewer);
      addSelectionListener(new SelectionAdapter()
      {
      	/**
		 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(SelectionEvent)
		 */
		public void widgetSelected(SelectionEvent e) {
			super.widgetSelected(e);
			     if (sViewer instanceof AbstractTreeViewer)
                 {
                   Item item = (Item)getRow().getData(TABLETREEITEM_ID);
                   StructuredSelection selection = new StructuredSelection(item.getData());
                   sViewer.setSelection(selection, true);
                 }
		}


      });
      addFocusListener(new FocusAdapter(){
         public void focusGained(FocusEvent e)
         {
         	// if e.source is not a child of the table then set selection - this is for tab into viewer
         	Object eventSource = e.getSource();
         	if (eventSource instanceof Control)
         	{
         	if (!isChild(currentTable, (Control)eventSource))
         	{
         	  if (currentTable.getItemCount() > 0 && currentTable.getSelectionCount() <= 0)
              {
                 if (sViewer instanceof AbstractTreeViewer)
                 {
                   Item tableTreeItem = (Item)getRow().getData(TABLETREEITEM_ID);
                   StructuredSelection selection = new StructuredSelection(tableTreeItem.getData());
                   sViewer.setSelection(selection, true);
                 }
                 else
                 {
               	   currentTable.setSelection(0); 
               	   setSelection(0,0);
                 }              
              }              
       	    }
       	    else 
       	    {
       	      if (currentTable.getItems().length > 0)
       	      {
       	      	 // cursor can end up on a non-existent table row 
       	         //   currently no way to get the current table cursor row 
       	      	 //   so for now just catch the exception since it doesn't
       	      	 //   cause any side effects.
       	      	 try
       	      	 {
                   setVisible(true);
       	      	 }
       	      	 catch (Exception ee)
       	      	 {     	            	      	
       	      		currentTable.setSelection(0);       	      		         
       	      		setSelection(0,0);
       	      	 }
       	       }       	    
       	       else  // do not show table cursor if there are no elements in the table - avoid repaint
       	       {
       	         setVisible(false);
       	       }
              }
         	}          
          }
          
          protected boolean isChild(Control parent, Control child)
          {
            Control tempChild = child;
            while (tempChild != null)
            {
              if (tempChild == parent)
              {
                return true;
              }
              tempChild = tempChild.getParent();
            }
            return false;
          }
          
          /**
           * @see org.eclipse.swt.events.FocusAdapter#focusLost(FocusEvent)
           */
          public void focusLost(FocusEvent e)
          {
            // Set the table navigator to be not visible if the the table
            // is not in focus and a child of the table is not in focus
            // note that we do this asynchronously so we don't mess up the
            // current focus handling.
            Display.getDefault().asyncExec(new Runnable()
            { 
              /**
               * @see java.lang.Runnable#run()
               */
              public void run()
              {
                if (currentTable != null && !currentTable.isDisposed() && !currentTable.isFocusControl() &&
                    !isChild(currentTable, Display.getDefault().getFocusControl()))
                {
                  setVisible(false);
                }
              }
            });
          }

      }); 
      
     
      

      table.addFocusListener(new FocusAdapter()
      {
        /**
         * @see org.eclipse.swt.events.FocusListener#focusGained(FocusEvent)
         */
        public void focusGained(FocusEvent e)
        {
          // only display navigator if there are items in the table 
          // and if the focus wasn't gained from our own table navigator
          // (ie focus came from outside)
          if (currentTable.getItemCount() > 0 &&
              (Display.getDefault().getFocusControl() != null) &&
              !Display.getDefault().getFocusControl().equals(TableNavigator.this))
          {
            // note that we do this asynchronously so we don't mess up the
            // current focus handling.
            Display.getDefault().asyncExec(new Runnable()            
            {
              /**
               * @see java.lang.Runnable#run()
               */
              public void run()
              {
                if (!isVisible())
                {
                  try
                  {
                    setVisible(true);
                    setFocus();
                  }
                  catch (Exception e)
                  {
                      // catch IllegalArgumentExceptions here - index out of bounds on tableviewer
                      if (currentTable.getItemCount() > 0)
                      {
                      currentTable.setSelection(0);                     
                      setSelection(0,0);                   
                      }
                      else  // do not show table cursor if there are no elements in the table - avoid repaint
                      {
                        setVisible(false);
                      }
                  }
                }
              }
            });
          }
        }
      });           
   }

   public Table getTable()
   {
     return table;
   }

    public void addPaintListener(StructuredViewer viewer)
    {
     
      addPaintListener(new PaintListener() 
      {
         public void paintControl(PaintEvent e)
         {
         	TableItem[] selection = table.getSelection();
            final TableItem row = (selection.length == 0) ? table.getItem(table.getTopIndex()) : selection[0];
            final String cellText = row.getText(getColumn());
            final Image cellImage = row.getImage(getColumn());
            final int col = getColumn();

            Display.getCurrent().asyncExec(new Runnable()
            {
               public void run()
               {
                  if (!row.isDisposed())
                  {
                     String newText = row.getText(getColumn());
                     if (!newText.equals(cellText) || !(row.getImage(col) == cellImage)) 
                     {
                       redraw();
                     }
                  }
                }
             });
           }
       });
    }


    public SelectionKeyAdapter getKeyAdapter(StructuredViewer viewer)
    {
        if (keyAdapter == null)
        {
           return new SelectionKeyAdapter(viewer);
        }
        else return keyAdapter;
    }

    public void setKeyAdapter(SelectionKeyAdapter kAdapter)
    {
       keyAdapter = kAdapter;
    }

    protected SelectionKeyAdapter keyAdapter = null;

    public class SelectionKeyAdapter extends KeyAdapter
    {
       StructuredViewer structuredViewer;

       public SelectionKeyAdapter(StructuredViewer viewer)
       {
          super();
          this.structuredViewer = viewer;
       }

       int lastKeyPressed = -1;  // used to cache the last key for key combos

       public void keyPressed(KeyEvent e) 
       {       	
               TableItem row = getRow();
               int column = getColumn();                

               // hack to emulate SHIFT+F10 popup menu - otherwise table cursor
               //   obscures the table popup mechanism and it doesn't work.
               if (lastKeyPressed == SWT.SHIFT && e.keyCode == SWT.F10)
               {
                  Menu popup = getTable().getMenu();
                  popup.setVisible(true);
               }
               lastKeyPressed = e.keyCode;
               
               //jvh - look for + or - key
               // column == 0
               if (row.getData(TABLETREEITEM_ID) instanceof Item)
               {
	               if (column == 0 && e.character == '+') 
	               {
	               	  Item tableTreeItem = (Item)row.getData(TABLETREEITEM_ID);
	               	  ((AbstractTreeViewer)structuredViewer).setExpandedState(tableTreeItem.getData(), true);                       
	               	  refresh();
	               }
	               else if (column == 0 && e.character == '-') 
	               {
	               	  Item tableTreeItem = (Item)row.getData(TABLETREEITEM_ID);
	               	  ((AbstractTreeViewer)structuredViewer).setExpandedState(tableTreeItem.getData(), false);                       
                      refresh();
	               }               
               }
               // use F2 to invoke editing for a cell
               if (e.keyCode == SWT.F2)     
               {
               	  if (structuredViewer instanceof TableViewer)
               	  {
                    ((TableViewer)structuredViewer).editElement(row.getData(), column);   
               	  }
               	  else if (structuredViewer instanceof AbstractTreeViewer)
               	  {  
	               	  Item tableTreeItem = (Item)row.getData(TABLETREEITEM_ID);
               	  	 ((AbstractTreeViewer)structuredViewer).editElement(tableTreeItem.getData(), column);   
               	  }
               }
        }
    }

    public void addKeyListeners(StructuredViewer viewer)
    {
      final StructuredViewer structuredViewer = viewer;
                     
      addKeyListener(getKeyAdapter(structuredViewer));
    }      
    
   public void addMouseListeners(StructuredViewer viewer)
   {
      final StructuredViewer structuredViewer = viewer;

      addMouseListener(new MouseAdapter()
      {

         public void mouseUp(MouseEvent e) 
         {        
               TableItem row = getRow();
               int column = getColumn(); 
         
               // use mouse button 1 to invoke editing for a cell
               if (e.button == 1)     
               {
                  if (structuredViewer instanceof TableViewer)
                        {
                     ((TableViewer)structuredViewer).editElement(row.getData(), column);   
                        }
                        else if (structuredViewer instanceof AbstractTreeViewer && column == 1)
                        {
                                 TreeItem treeItem = (TreeItem)row.getData(TABLETREEITEM_ID);
                                ((AbstractTreeViewer)structuredViewer).editElement(treeItem.getData(), column);
                        }                                               
               
                 if (structuredViewer instanceof AbstractTreeViewer && row.getData(TABLETREEITEM_ID) instanceof Item)
                 {              
                                   if (column == 0)
                                   {
                                          TreeItem treeItem = (TreeItem)row.getData(TABLETREEITEM_ID);
                                          boolean expandState = treeItem.getExpanded();
                       ((AbstractTreeViewer)structuredViewer).setExpandedState(treeItem.getData(), !expandState);
                       refresh();
                    }
                 }
               }          
            }
      });
     }


  /**
   * Ensure that cell editor control shows up above the table cursor control.
   * Should be called whenever the table viewer makes a new call to setCellEditors
   * i.e. in constructor and in refreshCellEditors
   *
   * @param - array of cell editors for the StructuredViewer
   */

  public void moveCellEditorsAbove(CellEditor[] editorArray)
  {
    for (int i = 0; i < editorArray.length ; i++)
    {
      CellEditor cEd = editorArray[i];
      if (cEd != null && cEd.getControl() != null)
      {
        cEd.getControl().moveAbove(null);
      }
    }
  }

  public void refresh()
  {
     Display.getCurrent().asyncExec(new Runnable()
     {
        public void run()
        {
           if (!isDisposed() && isVisible())
             redraw();
        }
     });
  }
}
