/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import java.util.ArrayList;
import java.util.List;
/**
* A palette composite for the {@link GraphicalEditorPart}.
* <p/>
* The palette contains several groups, each with a UI name (e.g. layouts and views) and each
* with a list of element descriptors.
* <p/>
*
* @since GLE2
*
* TODO list:
* - The available items should depend on the actual GLE2 Canvas selection. Selected android
* views should force filtering on what they accept can be dropped on them (e.g. TabHost,
* TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.
* - Optional: a text filter
* - Optional: have icons that depict the element and/or automatically rendered icons
* based on a rendering of the widget.
* - Optional: have context-sensitive tools items, e.g. selection arrow tool,
* group selection tool, alignment, etc.
* - Different view strategies: big icon, small icons, text vs no text, compact grid.
* - This would only be useful with meaningful icons. Out current 1-letter icons are not enough
* to get rid of text labels.
*/
public class PaletteComposite extends Composite {
/** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */
private Composite mRoot;
/**
* Create the composite.
* @param parent The parent composite.
*/
public PaletteComposite(Composite parent) {
super(parent, SWT.BORDER | SWT.V_SCROLL);
}
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
/**
* Loads or reloads the palette elements by using the layout and view descriptors from the
* given target data.
*
* @param targetData The target data that contains the descriptors. If null or empty,
* no groups will be created.
*/
public void reloadPalette(AndroidTargetData targetData) {
for (Control c : getChildren()) {
c.dispose();
}
setGridLayout(this, 2);
mRoot = new Composite(this, SWT.NONE);
setGridLayout(mRoot, 0);
if (targetData != null) {
addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());
addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());
}
layout(true);
final ScrollBar vbar = getVerticalBar();
vbar.setMaximum(getSize().y);
for (Listener listener : vbar.getListeners(SWT.Selection)) {
vbar.removeListener(SWT.Selection, listener);
}
vbar.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
Point p = mRoot.getLocation();
p.y = - vbar.getSelection();
mRoot.setLocation(p);
}
});
}
private void setGridLayout(Composite parent, int spacing) {
GridLayout gl = new GridLayout(1, false);
gl.horizontalSpacing = 0;
gl.verticalSpacing = 0;
gl.marginHeight = spacing;
gl.marginBottom = spacing;
gl.marginLeft = spacing;
gl.marginRight = spacing;
gl.marginTop = spacing;
gl.marginBottom = spacing;
parent.setLayout(gl);
}
private void addGroup(Composite parent,
String uiName,
List<ElementDescriptor> descriptors) {
Composite group = new Composite(parent, SWT.NONE);
setGridLayout(group, 0);
Toggle toggle = new Toggle(group, uiName);
for (ElementDescriptor desc : descriptors) {
Item item = new Item(group, desc);
toggle.addItem(item);
GridData gd = new GridData();
item.setLayoutData(gd);
}
}
/**
* A Toggle widget is a row that is the head of a group.
* <p/>
* When clicked, the toggle will show/hide all the {@link Item} widgets that have been
* added to it using {@link #addItem(Item)}.
*/
private static class Toggle extends CLabel implements MouseTrackListener, MouseListener {
private boolean mMouseIn;
private DragSource mSource;
private ArrayList<Item> mItems = new ArrayList<Item>();
public Toggle(Composite parent, String groupName) {
super(parent, SWT.NONE);
mMouseIn = false;
setData(null);
String s = String.format("-= %s =-", groupName);
setText(s);
setToolTipText(s);
//TODO use triangle icon and swap it -- setImage(desc.getIcon());
addMouseTrackListener(this);
addMouseListener(this);
}
public void addItem(Item item) {
mItems.add(item);
}
@Override
public void dispose() {
if (mSource != null) {
mSource.dispose();
mSource = null;
}
super.dispose();
}
@Override
public int getStyle() {
int style = super.getStyle();
if (mMouseIn) {
style |= SWT.SHADOW_IN;
}
return style;
}
// -- MouseTrackListener callbacks
public void mouseEnter(MouseEvent e) {
if (!mMouseIn) {
mMouseIn = true;
redraw();
}
}
public void mouseExit(MouseEvent e) {
if (mMouseIn) {
mMouseIn = false;
redraw();
}
}
public void mouseHover(MouseEvent e) {
// pass
}
// -- MouseListener callbacks
public void mouseDoubleClick(MouseEvent arg0) {
// pass
}
public void mouseDown(MouseEvent arg0) {
// pass
}
public void mouseUp(MouseEvent arg0) {
for (Item i : mItems) {
if (i.isVisible()) {
Object ld = i.getLayoutData();
if (ld instanceof GridData) {
GridData gd = (GridData) ld;
i.setData(gd.heightHint != SWT.DEFAULT ?
Integer.valueOf(gd.heightHint) :
null);
gd.heightHint = 0;
}
} else {
Object ld = i.getLayoutData();
if (ld instanceof GridData) {
GridData gd = (GridData) ld;
Object d = i.getData();
if (d instanceof Integer) {
gd.heightHint = ((Integer) d).intValue();
} else {
gd.heightHint = SWT.DEFAULT;
}
}
}
i.setVisible(!i.isVisible());
}
getParent().getParent().layout(true /*changed*/);
}
}
/**
* An Item widget represents one {@link ElementDescriptor} that can be dropped on the
* GLE2 canvas using drag'n'drop.
*/
private static class Item extends CLabel implements MouseTrackListener {
private boolean mMouseIn;
private DragSource mSource;
public Item(Composite parent, ElementDescriptor desc) {
super(parent, SWT.NONE);
mMouseIn = false;
setText(desc.getUiName());
setImage(desc.getIcon());
setToolTipText(desc.getTooltip());
addMouseTrackListener(this);
// DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
mSource = new DragSource(this, DND.DROP_COPY);
mSource.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });
mSource.addDragListener(new DescDragSourceListener(desc));
}
@Override
public void dispose() {
if (mSource != null) {
mSource.dispose();
mSource = null;
}
super.dispose();
}
@Override
public int getStyle() {
int style = super.getStyle();
if (mMouseIn) {
style |= SWT.SHADOW_IN;
}
return style;
}
public void mouseEnter(MouseEvent e) {
if (!mMouseIn) {
mMouseIn = true;
redraw();
}
}
public void mouseExit(MouseEvent e) {
if (mMouseIn) {
mMouseIn = false;
redraw();
}
}
public void mouseHover(MouseEvent e) {
// pass
}
}
/**
* A {@link DragSourceListener} that deals with drag'n'drop of
* {@link ElementDescriptor}s.
*/
private static class DescDragSourceListener implements DragSourceListener {
private final ElementDescriptor mDesc;
public DescDragSourceListener(ElementDescriptor desc) {
mDesc = desc;
}
public void dragStart(DragSourceEvent e) {
if (mDesc == null) {
e.doit = false;
}
}
public void dragSetData(DragSourceEvent e) {
// Provide the data for the drop when requested by the other side.
if (ElementDescTransfer.getInstance().isSupportedType(e.dataType)) {
e.data = mDesc;
}
}
public void dragFinished(DragSourceEvent e) {
// Nothing to do here.
}
}
}