package org.reldb.dbrowser.ui.content.rev; import java.util.Collection; import java.util.HashSet; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.wb.swt.SWTResourceManager; import org.reldb.dbrowser.ui.RevDatabase; public abstract class Visualiser extends Composite implements Comparable<Visualiser> { protected final static Color BaseColor = new Color(Display.getDefault(), 200, 200, 255); protected final static Color WarningColor = new Color(Display.getDefault(), 255, 255, 200); protected final static Color BackgroundColor = new Color(Display.getDefault(), 198, 198, 198); protected final static Color DropCandidateColor = new Color(Display.getDefault(), 100, 255, 100); private Color titleColor = BaseColor; private Label lblTitle; private int mouseOffsetX; private int mouseOffsetY; private boolean dragging = false; private Visualiser dropCandidate = null; private Model model; private String id; private String title; protected Button btnInfo; protected Button btnRun; protected Button btnEdit; private Vector<Argument> arguments = new Vector<Argument>(); /** Ctor */ protected Visualiser(Model model, String id, String title, int xpos, int ypos) { super(model, SWT.NONE); this.id = id; this.model = model; this.title = title; setBackground(BackgroundColor); setLocation(xpos, ypos); setLayout(new FormLayout()); lblTitle = new Label(this, SWT.NONE); lblTitle.setForeground(SWTResourceManager.getColor(SWT.COLOR_BLACK)); lblTitle.setBackground(titleColor); lblTitle.setAlignment(SWT.CENTER); FormData fd_lblTitle = new FormData(); fd_lblTitle.top = new FormAttachment(0); fd_lblTitle.left = new FormAttachment(0); fd_lblTitle.right = new FormAttachment(100); lblTitle.setLayoutData(fd_lblTitle); lblTitle.setText(title); if (!model.getRev().isReadOnly()) setupPopupMenu(); Composite mainPanel = new Composite(this, SWT.NONE); mainPanel.setBackground(BackgroundColor); mainPanel.setLayout(new FormLayout()); FormData fd_mainPanel = new FormData(); fd_mainPanel.top = new FormAttachment(lblTitle); fd_mainPanel.left = new FormAttachment(0); fd_mainPanel.right = new FormAttachment(100); mainPanel.setLayoutData(fd_mainPanel); Control controlPanel = obtainControlPanel(this); if (controlPanel != null) { FormData fd_controlPanel = new FormData(); fd_controlPanel.top = new FormAttachment(mainPanel); fd_controlPanel.left = new FormAttachment(0); fd_controlPanel.right = new FormAttachment(100); fd_controlPanel.bottom = new FormAttachment(100); controlPanel.setLayoutData(fd_controlPanel); } Composite buttonPanel = new Composite(mainPanel, SWT.NONE); buttonPanel.setBackground(BackgroundColor); FormData fd_buttonPanel = new FormData(); fd_buttonPanel.top = new FormAttachment(0); fd_buttonPanel.left = new FormAttachment(0); fd_buttonPanel.right = new FormAttachment(100); buttonPanel.setLayoutData(fd_buttonPanel); buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL)); btnInfo = new Button(buttonPanel, SWT.FLAT); btnInfo.setText("?"); btnInfo.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { (new ViewQueryDialog(Visualiser.this)).open(); } }); btnRun = new Button(buttonPanel, SWT.FLAT); btnRun.setText(">"); btnRun.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { String query = getQuery(); if (query != null) model.getRev().getCmdPanelOutput().go(query, true); else model.getRev().getCmdPanelOutput().badResponse("Unable to produce query."); } }); btnEdit = new Button(buttonPanel, SWT.FLAT); btnEdit.setText("+"); btnEdit.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { model.getRev().showEditorFor(title); } }); if (!model.getRev().isReadOnly()) { lblTitle.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { if (e.button != 1) return; mouseOffsetX = e.x; mouseOffsetY = e.y; dragging = true; } @Override public void mouseUp(MouseEvent e) { if (e.button != 1) return; dragging = false; if (dropCandidate!=null) { Point location = getLocation(); if (dropCandidate == model.getPossibleDropTarget(e.x + location.x, e.y + location.y, Visualiser.this)) { dropCandidate.setDropCandidate(false); dropCandidate.receiveDropOf(Visualiser.this); } dropCandidate = null; } } }); lblTitle.addMouseMoveListener(new MouseMoveListener() { @Override public void mouseMove(MouseEvent e) { model.disablePopupMenu(); if (dragging) { Point location = getLocation(); int newX = location.x + e.x - mouseOffsetX; int newY = location.y + e.y - mouseOffsetY; setLocation(newX, newY); // drop? Visualiser dropTarget = model.getPossibleDropTarget(location.x + e.x, location.y + e.y, Visualiser.this); if (dropCandidate != null && dropCandidate != dropTarget) dropCandidate.setDropCandidate(false); if (dropTarget != null) { dropCandidate = dropTarget; dropCandidate.setDropCandidate(true); } } else bringToFront(); } }); addControlListener(new ControlAdapter() { @Override public void controlMoved(ControlEvent e) { dispatchMovement(); } }); dispatchMovement(); } pack(); } public void setWarningColour() { titleColor = WarningColor; lblTitle.setBackground(titleColor); } public void setReadyColour() { titleColor = BaseColor; lblTitle.setBackground(titleColor); } protected void setupPopupMenu() { Menu menuBar = new Menu(getShell(), SWT.POP_UP); MenuItem disconnect = new MenuItem(menuBar, SWT.PUSH); disconnect.setText("Disconnect"); disconnect.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { disconnect(); } }); MenuItem delete = new MenuItem(menuBar, SWT.PUSH); delete.setText("Delete"); delete.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { delete(); } }); lblTitle.setMenu(menuBar); } public int compareTo(Visualiser v) { return getID().compareTo(v.getID()); } public boolean equals(Object o) { return compareTo((Visualiser)o) == 0; } public int hashCode() { return getID().hashCode(); } protected Control obtainControlPanel(Visualiser parent) { return null; } protected void disconnect() { Argument argumentArray[] = arguments.toArray(new Argument[0]); for (Argument argument: argumentArray) argument.setOperand(null); arguments.clear(); } protected void delete() { disconnect(); getDatabase().removeRelvar(getID()); dispose(); // If this visualiser was the last in this model, fireModelChangeEvent. if (model.getVisualiserCount() == 0) model.getRev().fireModelChangeEvent(); } private void bringToFront() { for (Argument argument: arguments) argument.bringToFront(); moveAbove(null); } public abstract String getQuery(); public RevDatabase getDatabase() { return model.getDatabase(); } public String getID() { return id; } public Model getModel() { return model; } public String getTitle() { return title; } public String toString() { return "Visualiser " + getTitle() + " (" + getID() + ")"; } /** Without running getQuery(), return true if getQuery() should return non-null. */ public boolean isQueryable() { return true; } public Argument[] getArguments() { return arguments.toArray(new Argument[0]); } protected void setDropCandidate(boolean b) { lblTitle.setBackground((b) ? DropCandidateColor : titleColor); } private boolean isSelfReference(Connector connector) { Argument argument = connector.getArguments()[0]; return (argument.getOperator() == this); } protected Collection<Visualiser> collectAllConnectedSources() { return new HashSet<Visualiser>(); } private boolean isCircularReference(Connector connector) { return collectAllConnectedSources().contains(connector.getArguments()[0].getOperator()); } protected boolean canReceiveDropOf(Visualiser visualiser) { if (!(visualiser instanceof Connector)) return false; Connector connector = (Connector)visualiser; return !(this instanceof Connector) && !isSelfReference(connector) && !isCircularReference(connector); } protected void receiveDropOf(Visualiser visualiser) { Connector connector = (Connector)visualiser; Argument argument = connector.getArguments()[0]; argument.setOperand(this); } private boolean fireModelChangeEventCheckDone = false; private void dispatchMovement() { (new Timer()).schedule(new TimerTask() { public void run() { if (isDisposed()) return; getDisplay().asyncExec(new Runnable() { public void run() { if (!isDisposed()) { visualiserMoved(); // If this visualiser is the first in this model, fireModelChangeEvent. if (fireModelChangeEventCheckDone) return; if (model.getVisualiserCount() == 1) model.getRev().fireModelChangeEvent(); fireModelChangeEventCheckDone = true; } } }); } }, 250); movement(); } /** Override to be notified of every movement. */ protected void movement() { if (arguments != null) for (Argument argument: arguments) argument.redraw(); } /** Override to be notified 250 milliseconds after movement has stopped. */ protected void visualiserMoved() { } public void addArgumentReference(Argument argument) { arguments.add(argument); movement(); visualiserMoved(); } public void removeArgumentReference(Argument argument) { arguments.remove(argument); movement(); visualiserMoved(); } private int getArgumentIndex(Argument argument) { int index = arguments.indexOf(argument); if (index == -1) System.out.println("Visualiser: can't find argument " + argument + " in visualiser " + this); return index; } public int getArgumentX(Argument argument) { if (isDisposed()) return 0; Rectangle bounds = getBounds(); int indexOfArgument = arguments.indexOf(argument); if (indexOfArgument==-1) return bounds.x + bounds.width / 2; int marginWidth = bounds.width / 5; // 5% int drawWidth = bounds.width - marginWidth; int drawStep = drawWidth / arguments.size(); if (drawStep < 0) return bounds.x + bounds.width / 2; return bounds.x + (bounds.width - drawWidth) / 2 + drawStep / 2 + indexOfArgument * drawStep; } public int getArgumentY(Argument argument) { if (isDisposed()) return 0; if (isConnectionAtBottom(argument)) return getBounds().y + getBounds().height; else return getBounds().y; } public int getExtensionLength(Argument argument) { return 10 + getArgumentIndex(argument) * 4; } public boolean isConnectionAtBottom(Argument argument) { if (isDisposed()) return false; return argument.getOperator().getBounds().y > getBounds().y + getBounds().height; } /** Invoked when the model changes, to allow the visualiser to update itself accordingly. */ public void verify() { for (Argument argument: arguments) argument.getOperator().verify(); } }