/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util.editlist;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.basic.BasicListUI;
import com.servoy.j2db.ui.BaseEventExecutor;
import com.servoy.j2db.ui.IEventExecutor;
import com.servoy.j2db.ui.IFieldComponent;
import com.servoy.j2db.ui.IFormUI;
import com.servoy.j2db.ui.ILabel;
import com.servoy.j2db.ui.ISupportEventExecutor;
import com.servoy.j2db.util.UIUtils;
public class EditListUI extends BasicListUI
{
public EditListUI()
{
super();
}
@Override
public void installUI(JComponent c)
{
super.installUI(c);
list.getCellRenderer();
}
@Override
protected MouseInputListener createMouseInputListener()
{
return new MouseInputHandler();
}
public void redrawList() //override / not overload!!
{
if (list.isVisible()) //if not visible just forget it :-)
{
list.revalidate();
list.repaint();
}
}
@Override
public int locationToIndex(JList list, Point location)
{
int index = super.locationToIndex(list, location);
int nrows = list.getModel().getSize();
if (index == (nrows - 1))
{
Rectangle rect = getCellBounds(list, 0, index);
if (rect == null || location == null || !rect.contains(location))
{
return -1;
}
}
return index;
}
private Image bufferImage = null;
private void createCachedRendererImage(int x, int y, int width, int height)
{
ListCellRenderer lcr = list.getCellRenderer();
Component rendererComponent = lcr.getListCellRendererComponent(list, null, 0, false, false);
rendererComponent.setEnabled(false);
// rendererPane.add(rendererComponent);
// rendererComponent.invalidate();
// rendererPane.validate();
bufferImage = rendererPane.createImage(width, height);
// rendererComponent.setLocation(0,0);
if (bufferImage != null)
{
Graphics g2 = bufferImage.getGraphics();
rendererPane.paintComponent(g2, rendererComponent, null, x, y, width, height);
}
// rendererComponent.paint(g2); // paint changes in component settings
// g2.setClip(0,0,width,height);
// rendererPane.remove(rendererComponent);
rendererComponent.setEnabled(true);
}
private boolean missedPaints = false;
/**
* Paint one List cell: compute the relevant state, get the "rubber stamp" cell renderer component, and then use the CellRendererPane to paint it.
* Subclasses may want to override this method rather than paint().
*
* @see #paint
*/
@Override
protected void paintCell(Graphics g, int row, Rectangle rowBounds, ListCellRenderer cellRenderer, ListModel dataModel, ListSelectionModel selModel,
int leadIndex)
{
JEditList eList = (JEditList)list;
if (eList.getEditingRow() == row)
{
Component comp = eList.getEditorComponent();
Dimension dim = comp.getSize();
if (dim.width != rowBounds.width || dim.height != rowBounds.height)
{
comp.setSize(rowBounds.width, rowBounds.height);
comp.doLayout();
}
return;
}
if (!eList.isRendererSameAsEditor())
{
super.paintCell(g, row, rowBounds, cellRenderer, dataModel, selModel, leadIndex);
return;
}
int cx = rowBounds.x;
int cy = rowBounds.y;
int cw = rowBounds.width;
int ch = rowBounds.height;
if (eList.isEditing() && bufferImage != null)
{
//stop painting if editing
g.drawImage(bufferImage, cx, cy, list);
missedPaints = true;
}
else
{
if (missedPaints)
{
missedPaints = false;
list.paintImmediately(0, 0, list.getWidth(), list.getHeight());
return;
}
Object value = dataModel.getElementAt(row);
boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
boolean isSelected = selModel.isSelectedIndex(row);
Component rendererComponent = cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
}
if (bufferImage == null) createCachedRendererImage(cx, cy, cw, ch);
}
/**
* Mouse input, and focus handling for JList. An instance of this class is added to the appropriate java.awt.Component lists at installUI() time. Note
* keyboard input is handled with JComponent KeyboardActions, see installKeyboardActions().
* <p>
* <strong>Warning:</strong> Serialized objects of this class will not be compatible with future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running the same version of Swing. As of 1.4, support for long term storage of all
* JavaBeans<sup><font size="-2">TM</font></sup> has been added to the <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
*
* @see #createMouseInputListener
* @see #installKeyboardActions
* @see #installUI
*/
public class MouseInputHandler implements MouseInputListener
{
// Component receiving mouse events during editing.
// May not be editorComponent.
private Component dispatchComponent;
private boolean selectedOnPress;
// private MouseEvent event;
public void mouseClicked(MouseEvent e)
{
// see comment Container -> processMouseEvent; jdk 1.6_13
// 4508327: MOUSE_CLICKED should never be dispatched to a Component
// other than that which received the MOUSE_PRESSED event. If the
// mouse is now over a different Component, don't dispatch the event.
// The previous fix for a similar problem was associated with bug
// 4155217.
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
if (e.isConsumed())
{
selectedOnPress = false;
return;
}
selectedOnPress = true;
adjustFocusAndSelection(e); // this might also repost the event to correct child of editor component
}
void adjustFocusAndSelection(final MouseEvent e)
{
if (!list.isEnabled())
{
return;
}
JEditList lst = (JEditList)list;
if (lst.isEditing())
{
IEditListEditor editor = lst.getCellEditor();
if (!editor.stopCellEditing()) return;
}
else if (!lst.hasFocus()) lst.requestFocus();
/*
* Request focus before updating the list selection. This implies that the current focus owner will see a focusLost() event before the lists
* selection is updated IF requestFocus() is synchronous (it is on Windows). See bug 4122345
*/
// int row = convertLocationToModel(e.getX(), e.getY());
if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP || list.getLayoutOrientation() == JList.VERTICAL_WRAP)
{
// make sure layout state is correct before calculating the editing row
updateLayoutState();
}
final int row = locationToIndex(list, new Point(e.getX(), e.getY()));
if (row == -1)
{
list.requestFocus();
return;
}
int oldSelectedRow = list.getSelectedIndex();
if (row != -1)
{
// boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
// list.setValueIsAdjusting(adjusting);
int anchorIndex = list.getAnchorSelectionIndex();
if (UIUtils.isCommandKeyDown(e))
{
if (list.isSelectedIndex(row))
{
list.removeSelectionInterval(row, row);
}
else
{
list.addSelectionInterval(row, row);
}
}
else if (e.isShiftDown() && (anchorIndex != -1))
{
list.setSelectionInterval(anchorIndex, row);
}
else
{
list.setSelectedIndex(row);
if (row != list.getSelectedIndex())
{
// selected index not actually changed, probably due to validation failed
return;
}
}
}
if (oldSelectedRow != row && ((JEditList)list).getMustSelectFirst())
{
return;
}
// IF you enable this then DataChoice in a editor is going wrong because MouseReleased is not followed by MousePressed.
// Runnable run = new Runnable()
// {
// public void run()
// {
if (((JEditList)list).editCellAt(row, e))
{
missedPaints = false;
setDispatchComponent(e);
if (dispatchComponent != null && SwingUtilities.isLeftMouseButton(e)) dispatchComponent.requestFocus();
repostEvent(e);
// if (event != null && event.getID() == MouseEvent.MOUSE_RELEASED && event.getWhen() > e.getWhen() && dispatchComponent != null)
// {
// repostEvent(event);
// }
// event = null;
}
else if (!list.hasFocus() && list.isRequestFocusEnabled())
{
list.requestFocus();
}
// }
// };
// SwingUtilities.invokeLater(run);
}
public void mouseDragged(MouseEvent e)
{
if (e.isConsumed())
{
return;
}
if (!SwingUtilities.isLeftMouseButton(e))
{
return;
}
if (!list.isEnabled())
{
return;
}
if (e.isShiftDown() || UIUtils.isCommandKeyDown(e))
{
return;
}
// int row = convertLocationToModel(e.getX(), e.getY());
int row = list.locationToIndex(new Point(e.getX(), e.getY()));
if (row != -1)
{
Rectangle cellBounds = getCellBounds(list, row, row);
if (cellBounds != null)
{
list.scrollRectToVisible(cellBounds);
// list.setSelectionInterval(row, row);
}
}
}
public void mouseMoved(MouseEvent e)
{
}
private void setDispatchComponent(MouseEvent e)
{
Component editorComponent = ((JEditList)list).getEditorComponent();
Point p = e.getPoint();
Point p2 = SwingUtilities.convertPoint(list, p, editorComponent);
if (editorComponent instanceof Container)
{
if (((Container)editorComponent).getComponentCount() > 0)
{
dispatchComponent = ((Container)editorComponent).findComponentAt(p2.x, p2.y);
}
else
{
dispatchComponent = editorComponent;
}
}
else
{
dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, p2.x, p2.y);
}
Component eventExecutorComponent = dispatchComponent;
while (!(eventExecutorComponent instanceof ISupportEventExecutor || eventExecutorComponent == null || eventExecutorComponent == list))
{
eventExecutorComponent = eventExecutorComponent.getParent();
}
if (eventExecutorComponent instanceof ISupportEventExecutor && eventExecutorComponent != list)
{
IEventExecutor executor = ((ISupportEventExecutor)eventExecutorComponent).getEventExecutor();
if (executor instanceof BaseEventExecutor)
{
((BaseEventExecutor)executor).setFormName(getFormName(eventExecutorComponent));
}
}
//Disabled: this breaks editing stopped !!! (if clicked besides a field)
// //try to focus a component if panel or label
// if (/*dispatchComponent == null || disabled, becouse cannot just focus one... */
// (dispatchComponent != null && (dispatchComponent instanceof JPanel || dispatchComponent instanceof JLabel)))
// {
// dispatchComponent = ((DefaultFocusManager) DefaultFocusManager.getCurrentManager()).getFirstComponent((Container) editorComponent);
// }
}
private boolean repostEvent(MouseEvent e)
{
// Check for isEditing() in case another event has
// caused the editor to be removed. See bug #4306499.
if (dispatchComponent == null || !((JEditList)list).isEditing())
{
// event = e;
// this is a fix for issue 110629
JEditList lst = (JEditList)list;
IEditListEditor editor = lst.getCellEditor();
if (editor != null)
{
Point p = e.getPoint();
int row = locationToIndex(list, new Point(e.getX(), e.getY()));
if (row >= 0)
{
Component comp = editor.getListCellEditorComponent(lst, lst.getModel().getElementAt(row), true, row);
if (comp != null)
{
Rectangle recOldBounds = comp.getBounds();
comp.setBounds(lst.getCellBounds(row, row));
boolean componentAdded = false;
if (comp.getParent() != lst)
{
lst.add(comp);
componentAdded = true;
}
comp.doLayout();
Point p2 = SwingUtilities.convertPoint(lst, p, comp);
Component dispatchComponent = null;
if (comp instanceof Container)
{
dispatchComponent = ((Container)comp).findComponentAt(p2.x, p2.y);
}
else
{
dispatchComponent = SwingUtilities.getDeepestComponentAt(comp, p2.x, p2.y);
}
if (isFormElement(dispatchComponent))
{
Point p3 = SwingUtilities.convertPoint(comp, p2, dispatchComponent);
MouseEvent e2 = new MouseEvent(dispatchComponent, e.getID(), e.getWhen(), e.getModifiers(), p3.x, p3.y, e.getClickCount(),
e.isPopupTrigger());
dispatchComponent.dispatchEvent(e2);
}
if (componentAdded)
{
lst.remove(comp);
}
}
}
}
return false;
}
MouseEvent e2 = null;
if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK)
{
// hack around bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988798
Point p = SwingUtilities.convertPoint(list, new Point(e.getX(), e.getY()), dispatchComponent);
e2 = new MouseEvent(dispatchComponent, e.getID(), e.getWhen(), e.getModifiers() | InputEvent.ALT_DOWN_MASK, p.x, p.y, e.getClickCount(),
e.isPopupTrigger());
}
else
{
e2 = SwingUtilities.convertMouseEvent(list, e, dispatchComponent);
}
dispatchComponent.dispatchEvent(e2);
return true;
}
private boolean isFormElement(Component component)
{
Component parent = component;
while (parent != null)
{
if (parent instanceof ILabel || parent instanceof IFieldComponent)
{
return true;
}
parent = parent.getParent();
}
return false;
}
private String getFormName(Component component)
{
for (Component container = component; container != null; container = container.getParent())
{
if (container instanceof IFormUI)
{
return ((IFormUI)container).getController().getName();
}
}
return null;
}
public void mouseReleased(MouseEvent e)
{
if (selectedOnPress)
{
selectedOnPress = false;
repostEvent(e);
}
else
{
adjustFocusAndSelection(e); // this might also repost the event to correct child of editor component
}
}
}
}