Files
secondo/Javagui/viewer/hoese/GraphWindow.java

668 lines
19 KiB
Java
Raw Normal View History

2026-01-23 17:03:45 +08:00
//This file is part of SECONDO.
//Copyright (C) 2004, University in Hagen, Department of Computer Science,
//Database Systems for New Applications.
//SECONDO is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//(at your option) any later version.
//SECONDO 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 General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with SECONDO; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package viewer.hoese;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.JPopupMenu;
import javax.swing.JOptionPane;
import javax.swing.JMenuItem;
import java.util.ListIterator;
import java.util.Vector;
import java.awt.geom.*;
import java.awt.BasicStroke;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JToggleButton;
import tools.Reporter;
import viewer.HoeseViewer;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.TreeSet;
import java.util.Iterator;
/**
* This class implements the layer-stack for the graphical objects. The super class is a
* JLayeredPane, which offers a lot of functionality needed here.
*/
public class GraphWindow extends JLayeredPane
implements Cloneable {
/** The internal no. ob the highest layer */
int highest = 0;
/** Listens for events send by the layer-buttons */
ActionListener LayerButtonListener;
/** Main-application */
HoeseViewer mw;
/** The background object - start with a SimpleBackground */
Background background = new SimpleBackground();
/** a flag for ignoring a repaint message.
This can be used to make all changes without new drawing.
**/
private boolean ignorePaint = false;
/** a additional object for painting **/
DsplGraph additionalGraphObject;
/** a listener playing sound if the mouse goes over an object */
SoundMouseListener soundListener;
/** flag determining whether sound should be played for
* all objects under the cursor or only for the first object found.
**/
boolean multisound = false;
PreloadDialog preloadDialog;
/*
The selected rectangle.
*/
boolean selection = false;
int sel_x = 0;
int sel_y = 0;
int sel_w = 0;
int sel_h = 0;
BasicStroke sel_stroke = new BasicStroke(2.0f);
public synchronized void setSelection(int x, int y, int w, int h){
if(selection){
if( sel_x == x && sel_y == y
&& sel_w == w && sel_h == h){
// selection already painted at this position,
// ignore
return;
}
}
if(selection){
// remove old rectangle
paintSelection();
}
sel_x = x;
sel_y = y;
sel_w = w;
sel_h = h;
// paint new rectangle
paintSelection();
selection = true;
}
/** paints or removes selection **/
private synchronized void paintSelection(){
Graphics2D g = (Graphics2D) getGraphics();
g.setXORMode(Color.WHITE);
g.setStroke(sel_stroke);
g.fillRect(sel_x, sel_y, sel_w, sel_h);
}
public void removeSelection(){
if(selection) {
paintSelection();
selection = false;
}
}
private class SoundMouseListener implements MouseMotionListener{
public SoundMouseListener(){
lastCats = new TreeSet<Category>();
}
public void mouseDragged(MouseEvent evt){
}
public void mouseMoved(MouseEvent e){
int x = e.getX();
int y = e.getY();
// collect the categories of objects under cursor
GraphWindow gw = GraphWindow.this;
boolean done = false;
Point2D.Double p = new Point2D.Double();
try {
p = (Point2D.Double)CurrentState.transform.inverseTransform(e.getPoint(), p);
} catch (Exception ex) {
return;
}
TreeSet<Category> currentCats = new TreeSet<Category>();
double scalex = 1/Math.abs(CurrentState.transform.getScaleX());
double scaley = 1/Math.abs(CurrentState.transform.getScaleY());
for(int i=0;i<gw.getComponentCount()&& !done; i++){
Layer layer = (Layer) gw.getComponent(i);
TreeSet layerCats = layer.getCats(p,scalex,scaley, !gw.multisound);
currentCats.addAll(layerCats);
if(!gw.multisound && currentCats.size()>0){
done = true;
}
}
Iterator<Category> it = lastCats.iterator();
while(it.hasNext()){
Category cat = it.next();
if(!currentCats.contains(cat)){
cat.stopSound();
}
}
it = currentCats.iterator();
while(it.hasNext()){
Category cat = it.next();
cat.startSound();
}
lastCats = currentCats;
}
private TreeSet<Category> lastCats;
}
/** Creates a Graphwindow without any layer
* @see <a href="Categorysrc.html#GraphWindow">Source</a>
*/
public GraphWindow (HoeseViewer main) {
super();
mw = main;
setDoubleBuffered(true);
LayerButtonListener = new java.awt.event.ActionListener() {
/**
* Shows /hides the layer associatd with the layer-button responsable for this event
* @param evt
* @see <a href="Categorysrc.html#actionPerformed">Source</a>
*/
public void actionPerformed (java.awt.event.ActionEvent evt) {
int laynr = Integer.parseInt(evt.getActionCommand());
Component[] com = getComponentsInLayer(laynr);
if(com.length>0)
((JComponent)com[0]).setVisible(((JToggleButton)evt.getSource()).isSelected());
}
};
soundListener = new SoundMouseListener();
}
public void enableSound(boolean on){
if(on){
addMouseMotionListener(soundListener);
} else {
removeMouseMotionListener(soundListener);
}
}
public void startPreload(){
preloadDialog = new PreloadDialog(mw.getViewerControl().getMainFrame());
if(!background.supportsPreload()){
return;
}
// compute clipRect
Rectangle2D.Double clipRect = getClipRect();
int fileNumber = background.numberOfPreloadFiles(clipRect);
if(fileNumber<1){
return;
}
if(JOptionPane.showConfirmDialog(mw.getViewerControl().getMainFrame(), "This will download " + fileNumber + " files \n continue?")!=JOptionPane.YES_OPTION){
return;
}
preloadDialog.setTarget(fileNumber);
CancelListener cl = new CancelListener(){
public void cancel(){
background.cancelPreload();
preloadDialog.finish(false);
}
};
preloadDialog.setCancelListener(cl);
background.preload(clipRect,preloadDialog);
preloadDialog.setVisible(true);
}
/**
* Gets the applications transformation
* @return The actual Transformation
* @see <a href="Categorysrc.html#getProjection">Source</a>
*/
public AffineTransform getProjection () {
return CurrentState.transform;
}
/** Set the flag for switching off repainting of the screen.
**/
public void setIgnorePaint(boolean enabled){
ignorePaint=enabled;
}
/** sets a new shape for painting */
public void paintAdditional(DsplGraph g){
additionalGraphObject=g;
}
/**
* This method creates a random category, used when automatic category is selected in the menu
* A manual object-category association is not neccessaary.
* @return A Category with name AutoXX, where XX is a running no.
* @see <a href="Categorysrc.html#createAutoCat">Source</a>
*/
public Category createAutoCat () {
Category defCat = new Category();
int r, g, b;
defCat.setLineColor(Color.black);
defCat.setName("Auto" + Integer.toString(CategoryEditor.CpCnt++));
defCat.setLineWidth((float)Math.abs(Math.random()*5.0f + 0.5f));
defCat.setLineStyle((int)(Math.random()*7));
r = (int)(Math.random()*256);
g = (int)(Math.random()*256);
b = (int)(Math.random()*256);
defCat.setFillStyle(new Color(r, g, b));
defCat.setAlphaStyle(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
defCat.setPointSize((int)(Math.random()*15 + 6));
defCat.setPointasRect((Math.random() > 0.5));
mw.Cats.add(defCat);
return defCat;
}
/**
* Adds the graph. objects in Vector grob to a new layer on the top
* @param grob
* @return The layertoggle of this layer
*/
public JToggleButton addLayerObjects (QueryResult qr, boolean changeZoom) {
Vector grob = qr.getGraphObjects();
handleCategory(grob);
Layer lay = new Layer(grob, this);
int Laynr = ++highest;
qr.setResultLayer(lay);
add(lay, new Integer(Laynr));
mw.updateViewParameter(changeZoom);
JToggleButton res = lay.CreateLayerButton(LayerButtonListener, Laynr);
return res;
}
/**
* Sets the Category by the provided selection mode
* @param grob
*/
private void handleCategory(Vector grob) {
Category acat;
switch(CurrentState.getCatSelectionMode()){
case CurrentState.CATEGORY_AUTO: {
acat = createAutoCat();
ListIterator li = grob.listIterator();
while (li.hasNext()) {
DsplGraph dg = ((DsplGraph)li.next());
if(dg==null){
Reporter.writeError("viewer.hoese.GraphWindow .addLayerObjects"+
" has received a null object");
} else {
dg.setCategory(acat);
}
}
break;
}
case CurrentState.CATEGORY_MANUAL: {
newQueryRepresentation(grob);
break;
}
case CurrentState.CATEGORY_BY_NAME: {
int size = grob.size();
Vector remaining = new Vector(size); // objects which have no category with
// the same name
for(int i=0;i<size;i++){
DsplGraph dg = (DsplGraph)grob.get(i);
if(dg==null){
Reporter.writeError("GraphWindow.addLayerObjects received a NULL object");
} else{
String attrName = dg.getAttrName();
Category cat = getCategory(attrName);
if(cat==null){
remaining.add(dg);
} else {
dg.setCategory(cat);
}
}
}
// process remaining objects
if(remaining.size()>0){
newQueryRepresentation(remaining);
}
break;
}
default : {
Reporter.writeError("GraphWindow.addLayerObjects: unknown category selection mode");
newQueryRepresentation(grob);
}
}
}
/** Computes the category with the given name.
* If not such category is found, NULL is returned.
*/
Category getCategory(String name){
Vector cats = mw.Cats;
int size = cats.size();
for(int i=0;i<size;i++){
Category Cat = (Category)cats.get(i);
if(Cat.hasName(name))
return Cat;
}
// nothing found
return null;
}
/**
* Adds the Layer l to the top of layerstack
* @param l
* @return The layertoggle of this layer
* @see <a href="Categorysrc.html#addLayer">Source</a>
*/
public JToggleButton addLayer (Layer l, boolean changeZoom) {
// add (lay,new Integer(highestLayer()+1));
Layer lay = new Layer(l.getGeoObjects(), this);
int Laynr = highest++;
add(lay, new Integer(Laynr));
mw.updateViewParameter(changeZoom);
return lay.CreateLayerButton(LayerButtonListener, Laynr);
}
/**
* This method makes it possible to add categories to each graph. attribute of a query
* @param go A Vector with graphical objects of a query
*/
public void newQueryRepresentation (Vector go) {
//acat=Category.getDefaultCat();
try{
ListIterator li = go.listIterator();
Vector v = new Vector(5, 1);
while (li.hasNext()) {
String aname = ((DsplGraph)li.next()).getAttrName();
if (!v.contains(aname)) {
v.add(aname);
JComboBox cb = mw.TextDisplay.getQueryCombo();
QueryResult Query = (QueryResult)cb.getSelectedItem();
ViewConfig VCfg = Query.getViewConfigAt(Query.getViewConfigIndex(aname));
if (VCfg==null){
VCfg = new ViewConfig(mw,aname);
Query.addViewConfig(VCfg);
}
VCfg.readPool();
VCfg.setVisible(true);
}
}
}
catch(Exception e){
Reporter.writeError("Exception in : GraphWindow.newQueryRep "+e );
Reporter.debug(e);
}
}
/**
* Gets the internal no. of the highest layer
* @return No. as int
*/
public int getTop () {
return highest;
}
/**
* Sets a new size for all layers
* @param asize
* @see <a href="Categorysrc.html#updateLayersSize">Source</a>
*/
public void updateLayersSize (Rectangle asize) {
setPreferredSize(new Dimension((int)(asize.getX() + asize.getWidth()),
(int)(asize.getY() + asize.getHeight()))); //updateLayerSize();
for (int i = 0; i < getComponentCount(); i++)
//if (getLayer(getComponent(i))<=10000)
getComponent(i).setBounds(asize);
revalidate();
}
/* scales an rectangle by scaleFactor from ProjectionManager */
/*Rectangle2D.Double scale(Rectangle2D.Double r, boolean reverse){
double sf = ProjectionManager.getScaleFactor();
if(!reverse){
sf = 1/sf;
}
r.setRect( r.x*sf, r.y*sf, r.width*sf, r.height*sf);
return r;
}
*/
/**
* Recalculates the boundingbox of all graph. objects
* @see <a href="Categorysrc.html#updateBoundingBox">Source</a>
*/
public void updateBoundingBox () {
Rectangle2D.Double r;
Background bgi = getBackgroundObject();
if(bgi.useForBoundingBox()){
r = (Rectangle2D.Double) bgi.getBBox().clone();
Point2D.Double aPoint = new Point2D.Double();
double x1 = r.getX();
double y1 = r.getY();
if(ProjectionManager.project(x1,y1,aPoint)){
double x2 = x1 + r.getWidth();
double y2 = y1 + r.getHeight();
x1 = aPoint.getX();
y1 = aPoint.getY();
if(ProjectionManager.project(x2,y2,aPoint)){
r.setRect(x1,y1,aPoint.getX()-x1, aPoint.getY()-y1);
}
}
}else{
r = null;
}
for (int i = 0; i < getComponentCount(); i++)
if (getComponent(i) instanceof Layer) {
Layer l = (Layer)getComponent(i);
Rectangle2D.Double layer_box = l.getWorldCoordBounds();
if (r == null)
r = layer_box;
else{
if(layer_box!=null)
r = (Rectangle2D.Double)r.createUnion(layer_box);
}
}
if (r != null){
mw.BBoxWC = r;
} else {
mw.BBoxWC=new Rectangle2D.Double(-170,-80,340,160);
}
}
public void updateBackground(){
Background bgi = getBackgroundObject();
if(bgi==null){
repaint();
if(tools.Environment.DEBUG_MODE){
Reporter.writeError("cannot find the background ");
}
return;
}
if(bgi.useForBoundingBox()) {
ignorePaint=true;
updateBoundingBox();
ignorePaint=false;
}
mw.updateViewParameter(true);
repaint();
}
/**
* draws all layers
* @param g
* @see <a href="Categorysrc.html#paintChildren">Source</a>
*/
/** computes the currently displayed rectangle in world coordinates **/
private Rectangle2D.Double getClipRect(){
AffineTransform at = CurrentState.transform;
try{
AffineTransform at1 = new AffineTransform(at);
AffineTransform iat = at1.createInverse();
Rectangle vpbounds1 = mw.GeoScrollPane.getViewport().getViewRect();
Rectangle2D.Double vpbounds = new Rectangle2D.Double(vpbounds1.x, vpbounds1.y,vpbounds1.width, vpbounds1.height);
Rectangle2D cbbi1 = iat.createTransformedShape(vpbounds.getBounds2D()).getBounds2D();
Rectangle2D.Double cbbi;
if(cbbi1 instanceof Rectangle2D.Double){
cbbi = (Rectangle2D.Double)cbbi1;
} else {
cbbi=new Rectangle2D.Double( cbbi1.getMinX(), cbbi1.getMinY(), cbbi1.getWidth(), cbbi1.getHeight());
}
return cbbi;
} catch(Exception e){
return null;
}
}
public void paintChildren (Graphics g) {
if (ignorePaint) {
return;
}
AffineTransform at = CurrentState.transform;
Graphics2D g2 = (Graphics2D) g;
// paint the background
// first transform the bounding box for the background
// into screen coordinates
try{
Rectangle2D.Double clipRect = getClipRect();
double antiScale = background.getAntiScale(CurrentState.transform,clipRect);
if(Auxiliary.almostEqual(antiScale,1.0)){
antiScale = 1.0;
}
if(antiScale!=1){
mw.zoom(antiScale);
}
if(background != null) {
background.paint(this,g2,CurrentState.transform, clipRect);
}
} catch(Exception e){
Reporter.writeError("Problem while drawing background");
Reporter.debug(e);
}
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
super.paintChildren(g2);
// paint the selected object again to be visible even if it is behind
// other objects
DsplGraph dg = mw.getSelGO();
if ((dg != null) && (dg.getVisible())) {
Composite C = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
0.0f));
Layer.draw(dg, g2, CurrentState.ActualTime, at);
g2.setComposite(C);
}
// draw addinitional objects from objectvcreation
if (additionalGraphObject != null) {
Layer.draw(additionalGraphObject, g2, CurrentState.ActualTime, at);
}
if(selection){
// an old selection is overwritten by repainting the scene
// draw the selection
paintSelection();
}
}
public void addMouseListener(MouseListener ML){
MouseListener[] MLs = getMouseListeners();
boolean found = false;
for(int i=0;i<MLs.length && ! found ; i++)
found = ML.equals(MLs[i]);
if(!found)
super.addMouseListener(ML);
}
/**
* returns the actual background object
*/
public Background getBackgroundObject() {
return background;
}
/**
* set the background object
*/
public void setBackgroundObject(Background b) {
if(b != null){
background = b;
} else if (background == null){
background = new SimpleBackground(Color.PINK);
}
}
}