gerv%gerv.net 8b69962ee3 Bug 236613: change to MPL/LGPL/GPL tri-license.
git-svn-id: svn://10.0.0.236/trunk@155500 18797224-902f-48f8-a5cc-f745e15eee43
2004-04-25 21:07:34 +00:00

1283 lines
35 KiB
Java

/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package netscape.plugin.composer.mapedit;
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.net.URL;
import java.io.*;
import netscape.plugin.composer.*;
import netscape.plugin.composer.io.*;
import java.util.*; // For ResourceBundle, etc.
import netscape.security.PrivilegeManager;
/* ****************** The Plugin *************************************************/
public class MapEdit extends Plugin {
public static void main(String []args) {
Debug.println("in MapEdit.main()" + args);
netscape.test.plugin.composer.Test.perform(args,new MapEdit());
}
/** Returns whether resources are now loaded. */
boolean loadResources() {
if (!resLoaded) {
// Load resources. international.
try {
Debug.println("Loading resources");
// This method not supported in the Navigator yet.
// res = ResourceBundle.getResourceBundle("netscape.test.plugin.composer.MapEditResource",null);
res = new MapEditResource();
resLoaded = true;
}
catch (MissingResourceException e) {
}
}
return resLoaded;
}
private boolean resLoaded = false;
private ResourceBundle res = null;
public String getName() {
if (loadResources())
return res.getString("image_map_editor");
else
return "Image Map Editor";
}
public String getCategory() {
if (loadResources())
return res.getString("html_tools");
else
return "HTML Tools";
}
public String getHint() {
if (loadResources())
return res.getString("the_hint");
else
return super.getHint();
}
public boolean perform(Document document) throws IOException {
////// --------------------------------////
Debug.println("Image Map Editor test 8");
////// --------------------------------////
if (!loadResources()) {
System.err.println("Map Editor could not load resources.");
return false;
}
MapEditApp app = new MapEditApp(document,res);
// Catch any errors in init().
if (app.initSuccessful()) {
app.show();
}
boolean ret = app.waitForExit();
// Let Cafe users see the output before the window goes away.
if (false) {
// if (Debug.debug()) {
try {
if (true) {
Debug.println("Hit return");
System.in.read();
}
else {
Debug.println("SLEEP for a few seconds");
Thread.sleep(10000);
}
}
catch (Exception e) {}
}
return ret;
}
}
/* ********************* The actual Application ***************/
class MapEditApp extends Frame implements OkCallback {
private boolean initOk = false;
public boolean initSuccessful() {return initOk;}
public MapEditApp(Document d,ResourceBundle rB) {
super(rB.getString("frame_title"));
res = rB;
document = d;
areas = new Vector();
if (document == null) {
failure(res.getString("doc_is_null"));
return;
}
// Try to find the first image in the selection.
if (!parseDoc(true)) {
// Next, look for any image in the document.
if (!parseDoc(false)) {
// Give up, no images.
failure(res.getString("no_images_in_document"));
return;
}
}
// imageName, imageLocation, and mapName should now be valid.
// Security stuff
PrivilegeManager.getPrivilegeManager().enablePrivilege("UniversalFileAccess");
PrivilegeManager.getPrivilegeManager().enablePrivilege("UniversalConnect");
Debug.println("got privileges");
// Load image
URL imageURL = null;
try {
imageURL = new URL(document.getBase(),imageName);
} catch (java.net.MalformedURLException e) {
failure(e.toString());
return;
}
Debug.println("created dummy URL");
try {
image = Toolkit.getDefaultToolkit().getImage(imageURL);
}
catch (Exception e) {
failure(e.toString());
return;
}
Debug.println("start image load");
// Force image loading.
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(image,0);
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
image = null;
}
if (image != null) {
imageDim = new Dimension(image.getWidth(this),image.getHeight(this));
imageRect = new Rectangle(imageDim);
}
if (image == null || imageDim.width < 0 || imageDim.height < 0) {
failure(Format.formatLookup(res,"could_not_load_image",imageName));
return;
}
Debug.println("finish image load");
createUI();
syncAreaList();
resize(MapEditApp.APP_WIDTH,MapEditApp.APP_HEIGHT);
initOk = true;
// Flag so we can stop running the app.
isRunning = true;
}
synchronized void failure(String message) {
if (failureDialog == null)
failureDialog = new InfoDialog(this,res,"Map Editor Error",message,this);
if (!failureDialog.isVisible())
failureDialog.show();
Debug.println("FAILURE:" + message);
}
private InfoDialog failureDialog = null;
// Callback from the failure InfoDialog. Quit the application.
public synchronized void ok() {
Debug.println("ok callback called");
if (isRunning) {
// success will be false.
stopRunning();
}
}
synchronized public boolean waitForExit() {
while ( isRunning ) {
try {
Debug.println("MapEditApp.waitForExit is wait()ing.");
wait();
} catch ( InterruptedException e){
}
}
hide();
return success;
}
synchronized void stopRunning() {
Debug.println("stop running");
isRunning = false;
notifyAll();
}
/*********************** PARSING/UNPARSING CODE *********************/
// Find first image in selection or in document.
// Sets imageName, mapName, imageLocation.
// Returns whether an image was found.
private boolean parseDoc(boolean onlyInSelection) {
// Start from beginning of document.
LexicalStream inStream = null;
try {
// Read entire document into a string and use the string
// for the LexicalStream.
Reader in = document.getInput();
StringBuffer b = new StringBuffer();
int bufSize = 1000;
char[] buf = new char[bufSize];
for(;;) {
int moved = in.read(buf);
if ( moved < 0 )
break;
b.append(buf, 0, moved);
}
in.close();
inStream = new LexicalStream(b.toString());
} catch (IOException e) {
failure(e.toString());
}
imageLocation = -1;
boolean inSelection = false;
Token token;
try {
while ((token = inStream.next()) != null) {
imageLocation++; // 0 for the first token.
// Keep track of selection start/end.
if (token instanceof Comment) {
Comment comment = (Comment)token;
if (!inSelection && comment.isSelectionStart())
inSelection = true;
if (inSelection && comment.isSelectionEnd())
inSelection = false;
}
// Find first IMG tag.
if ((!onlyInSelection || inSelection) &&
token instanceof Tag) {
Tag tag = (Tag)token;
if (tag.getName().equals("IMG")) {
// Image found.
if (!tag.isOpen() || !tag.containsAttribute("SRC")) {
failure(res.getString("first_img_invalid"));
}
String value = tag.lookupAttribute("USEMAP");
// If image has an image map, read in it's <area> tags and
// use its name.
if (value != null && value.length() > 0) {
mapName = StripPound(value);
parseMap();
}
// Else, a new map, generate a map name.
else {
// NSMAP + 6 digits.
mapName = "NSMAP" + (int)Math.floor((Math.random() * 999999));
}
imageName = tag.lookupAttribute("SRC");
if (imageName == null || imageName.length() == 0) {
// failure(new MessageFormat(res.getString("invalid_img_tag")).format(args));
failure(Format.formatLookup(res,"invalid_img_tag",imageName));
}
return true;
} // if "IMG"
} // if Tag
} // while
} catch (IOException e) {
failure(e.toString());
}
return false;
}
/** Search from beginning of doc, find first map with name mapName. Use it
to construct the Vector areas. */
private void parseMap() {
// Note: Starting from the beginning.
LexicalStream stream = null;
try {
stream = new LexicalStream(document.getInput());
} catch (IOException e) {
failure(e.toString());
}
Token t;
try {
while ((t = stream.next()) != null) {
if (t instanceof Tag) {
Tag tag = (Tag)t;
if (tag.getName().equals("MAP") && tag.isOpen() &&
mapName.equals(tag.lookupAttribute("NAME"))) {
parseAreas(stream);
}
}
}
} catch (IOException e) {
failure(e.toString());
}
// If we don't find an image map corresponding to mapName, areas will just
// be an empty Vector.
}
/** Read in all <AREA> tags until a closing </map> tag is hit. */
private void parseAreas(LexicalStream stream) {
try {
Token token;
while ((token = stream.next()) != null) {
if (token instanceof Tag) {
Tag tag = (Tag)token;
if (tag.getName().equals("MAP") && tag.isClose())
return;
// Give each Area class a chance to parse the tag.
Area area;
if ((area = DefaultArea.fromTag(tag,document.getBase())) != null) {
// We could make the default area a real area, for now just store the
// default URL.
defaultURL.str = area.getURL();
continue;
}
if ((area = RectArea.fromTag(tag,document.getBase())) != null) {
// area.clip(imageRect);
areas.addElement(area);
continue;
}
if ((area = CircleArea.fromTag(tag,document.getBase())) != null) {
// area.clip(imageRect);
areas.addElement(area);
continue;
}
if ((area = PolyArea.fromTag(tag,document.getBase())) != null) {
// area.clip(imageRect);
areas.addElement(area);
continue;
}
} // if
} // while
} catch (IOException e) {
failure(e.toString());
}
}
/** Strips leading '#' if found. */
private String StripPound(String in) {
if (in.startsWith("#"))
return in.substring(1);
else
return in;
}
/** Write document to the Document output stream. */
private void writeDoc() {
// Start from beginning of document.
LexicalStream inStream = null;
Writer outStream = null;
try {
inStream = new LexicalStream(document.getInput());
outStream = document.getOutput();
} catch (IOException e) {
failure(e.toString());
}
// Between <map> and </map> of the <map> corresponding to the first image.
boolean insideMap = false;
int location = -1; // so is 0 for first token.
Token token;
try {
while ((token = inStream.next()) != null) {
// At top so gets incremented even if we "continue" inside body of "while".
location++;
if (token instanceof Tag) {
Tag tag = (Tag)token;
// Change IMG tag to use new map. May just set it to the prev value.
if (location == imageLocation) {
if (!tag.getName().equals("IMG")) {
failure(res.getString("error_reading_twice"));
}
// Only write out if at least one area or the default area.
if (!areas.isEmpty() || defaultURL.notEmpty()) {
tag.addAttribute("USEMAP","#" + mapName);
outStream.write(tag.toString() + "\n");
// Write the image map right after the image.
writeMap(outStream);
}
// If no areas, don't output image map.
else {
// Remove USEMAP if it was there.
tag.removeAttribute("USEMAP");
outStream.write(tag.toString());
}
continue;
}
// Eliminate beginning <map> corresponding to the first image.
if (!insideMap &&
tag.getName().equals("MAP") &&
tag.isOpen() &&
mapName.equals(tag.lookupAttribute("NAME"))) {
insideMap = true;
continue;
}
// Eliminate trailing </map>
if (insideMap &&
tag.getName().equals("MAP") &&
tag.isClose()) {
insideMap = false;
continue;
}
// Kill all <area> tags between <map> and </map>
if (insideMap &&
tag.getName().equals("AREA")) {
continue;
}
} // Tag
outStream.write(token.toString());
} // while
outStream.close();
} catch (IOException e) {
if (outStream != null)
try {outStream.close();} catch (IOException e2) {}
failure(e.toString());
}
}
private void writeMap(Writer out) throws IOException {
Tag map = new Tag("MAP");
map.addAttribute("NAME",mapName);
out.write(map.toString() + "\n");
Enumeration e = areas.elements();
while (e.hasMoreElements()) {
Tag area = ((Area)e.nextElement()).toTag();
out.write(area.toString() + "\n");
}
// Write default area last.
if (defaultURL.notEmpty()) {
out.write(new DefaultArea(defaultURL.str).toTag().toString() + "\n");
}
out.write(new Tag("MAP",false).toString() + "\n");
}
/***************** END PARSING/UNPARSING CODE ******************************/
/************************** UI CODE ************************************/
static private final boolean areaListOnDefault = false;
private void createUI() {
setLayout(new BorderLayout());
header = new Panel();
header.setBackground(Color.lightGray);
IconCheckBoxGroup grp = new IconCheckBoxGroup();
header.setLayout(new FlowLayout(FlowLayout.LEFT,3,4));
header.add(arrowCheckBox =
new ArrowCheckBox(grp,mode == SELECT,
new OkCallback() {public void ok() { select();} }));
header.add(new RectCheckBox(grp,mode == CREATE_RECT_AREA,
new OkCallback() {public void ok() { createArea(CREATE_RECT_AREA);} }));
header.add(new CircleCheckBox(grp,mode == CREATE_CIRCLE_AREA,
new OkCallback() {public void ok() { createArea(CREATE_CIRCLE_AREA);} }));
header.add(new PolyCheckBox(grp,mode == CREATE_POLY_AREA,
new OkCallback() {public void ok() { createArea(CREATE_POLY_AREA);} }));
Label l = new Label(res.getString("url"),Label.RIGHT);
header.add(l);
urlComponent = new TextField(40);
urlComponent.setEditable(true);
urlComponent.setText(new String(defaultURL.str));
header.add(urlComponent);
header.add(areaListButton = new Checkbox(res.getString("area_list"),null,areaListOnDefault));
add("North",header);
canvas = new BlankCanvas(this);
canvas.setBackground(Color.white);
add("Center",canvas);
areaList = new SizedList();
add("East",areaList);
if (!areaListOnDefault) {
areaList.hide();
}
doneButton = new Button(res.getString("done"));
cancelButton = new Button(res.getString("cancel"));
Panel footer = new Panel();
footer.setBackground(Color.lightGray);
footer.setLayout(new FlowLayout(FlowLayout.CENTER, 30, 9));
footer.add(doneButton);
footer.add(cancelButton);
add("South",footer);
}
// offset is vector from canvas origin to image origin.
private Dimension offset = new Dimension(0,0);
private BlankCanvas canvas;
private Panel header;
private TextComponent urlComponent;
private Button doneButton;
private Button cancelButton;
private Checkbox areaListButton;
private List areaList;
private IconCheckBox arrowCheckBox;
// Keep image centered if canvas is bigger than image.
private static int reshapeHelper(int canvasSize,int imageSize,int offset) {
int offsetNew;
if (canvasSize > imageSize) {
offsetNew = (canvasSize - imageSize) / 2;
}
else {
offsetNew = Math.min(offset,0);
}
return offsetNew;
}
/** The image canvas has been reshaped to this width and height. */
void reshapeCanvas(int width,int height) {
offset.width = reshapeHelper(width,imageDim.width,offset.width);
offset.height = reshapeHelper(height,imageDim.height,offset.height);
}
public synchronized void paintMe(Graphics g) {
// Debug.println("painting");
g.drawImage(image,offset.width,offset.height,this);
// Draw region for default.
DefaultArea.draw(g,offset,imageRect,selected == -1);
// Draw in reverse order, so that lowest Area is on top.
// First area in list takes precedence for selection.
for (int index = areas.size() - 1; index >= 0; index--) {
Area a = (Area)areas.elementAt(index);
a.draw(g,offset,index == selected);
}
}
// Use <unspecified> instead of empty or null string.
private String listString(Object o) {
String url = ((Area)o).getURL();
if (url != null && url.length() > 0) {
return url;
}
else {
return res.getString("unspecified_area");
}
}
private void syncAreaList() {
areaList.clear();
Enumeration e = areas.elements();
while (e.hasMoreElements()) {
areaList.addItem(listString(e.nextElement()));
}
if (defaultURL.notEmpty()) {
areaList.addItem("> " + defaultURL.str);
}
selectAreaList(selected);
}
private void selectAreaList(int val) {
// Just to be sure, unselect all. MAC won't do it for you.
int current = areaList.getSelectedIndex();
if (current != -1) {
areaList.deselect(current);
}
if (val == -1) {
if (defaultURL.notEmpty()) {
Debug.assert(areas.size() + 1 == areaList.countItems(),"list not synced");
// select last item.
areaList.select(areas.size());
}
else {
Debug.assert(areas.size() == areaList.countItems(),"list not synced");
}
}
else {
areaList.select(val);
}
Debug.println("selectAreaList: val was " + val + " List had" + current + " List is now " + areaList.getSelectedIndex());
}
/* ************** Helper code for dealing with areas ************** */
private void setSelected(int n) {
setSelected(n,false);
}
// fromAreaList prevents recursion with the areaList
private void setSelected(int n,boolean fromAreaList) {
Debug.println("setSelected " + n);
if (selected < -1 || selected >= areas.size()) {
Debug.println("Attempting to select invalid index.");
return;
}
// first copy current value from the UI text element to the internal
// representation.
flushURLComponent();
if (completing()) {
Debug.println("MapEditApp.select() when not done with prev operation.");
// Don't allow changing the selection while completing an area.
return;
}
selected = n;
// Update UI.
if (n == -1)
urlComponent.setText(new String(defaultURL.str));
else
urlComponent.setText(((Area)areas.elementAt(n)).getURL().toString());
// Make areaList match current selection.
if (!fromAreaList) {
selectAreaList(selected);
}
}
/** Are we in the process of completing an area. */
private boolean completing() {
// Something is selected and it is not fully created.
return (selected != -1 && !((Area)areas.elementAt(selected)).completed());
}
private void deleteSelected() {
deleteSelected(false);
}
private void deleteSelected(boolean fromAreaList) {
Debug.println("Deleted selected = " + selected);
if (selected < -1 || selected >= areas.size()) {
Debug.println("Attempting to delete with invalid index.");
return;
}
if (selected == -1) {
if (defaultURL.notEmpty()) {
// Just clear out the current string.
defaultURL.str = "";
}
}
else {
// This should remove the currently selected element.
areas.removeElementAt(selected);
}
selected = -1;
urlComponent.setText(new String(defaultURL.str));
syncAreaList();
}
// Copy current value from the UI text element to the internal
// representation.
private void flushURLComponent() {
String url = urlComponent.getText();
Debug.println("FLUSH, component has <" +
((url == null) ? "-NULL-" : url) + ">");
if (selected != -1) {
Area area = (Area)areas.elementAt(selected);
// Only change if necessary.
if (!area.getURL().equals(url)) {
((Area)areas.elementAt(selected)).setURL(new String(url));
syncAreaList();
}
}
else {
// Set default url
if (!defaultURL.str.equals(url)) {
defaultURL.str = new String(url);
syncAreaList();
}
}
}
/* *************** End helper code ******************** */
// Translate an index from an AWT event to the index for the
// Vector. -1 means the extraString was selected.
public int translateEventIndex(int val) {
if (val >= areas.size()) {
return -1;
}
return val;
}
public synchronized boolean handleEvent(Event e) {
if (e.id == Event.WINDOW_DESTROY) {
cancel();
return true;
}
else if (e.target == doneButton) {
if (e.id == Event.ACTION_EVENT) {
done();
return true;
}
}
else if (e.target == cancelButton) {
if (e.id == Event.ACTION_EVENT) {
cancel();
return true;
}
}
// Show/hide areaList.
else if (e.target == areaListButton) {
if (e.id == Event.ACTION_EVENT) {
boolean val = ((Boolean)e.arg).booleanValue();
if (val) {
areaList.show();
}
else {
areaList.hide();
}
validate();
return true;
}
}
else if (e.target == areaList) {
// Select according to the areaList.
if (e.id == Event.LIST_SELECT) {
int val = ((Integer)e.arg).intValue();
int translated = translateEventIndex(val);
Debug.println("LIST_SELECT event received, evt " + val + ", translate " + translated);
setSelected(translated,true);
canvas.repaint();
return true;
}
// Delete selected area.
if (e.id == Event.KEY_PRESS &&
(e.key == Event.BACK_SPACE)) {
// Not Event.DELETE, because it produces "?" on the MAC.
Debug.println("DELETE in area list event");
deleteSelected(true);
canvas.repaint();
return true;
}
}
// Flush the text field into the area.
else if (e.target == urlComponent) {
if (e.id == Event.ACTION_EVENT || e.id == Event.LOST_FOCUS) {
flushURLComponent();
}
}
else if (e.target == this && (e.id == Event.KEY_PRESS || e.id == Event.KEY_ACTION)) {
// Cafe 1.53 sends key events here.
return keyDown(e);
}
return super.handleEvent(e);
}
synchronized boolean keyDown(Event e) {
Debug.println("keyDown: " + e.key);
checkKillDoubleClick(e);
// Move/delete selected
if (selected != -1) {
if (e.key == Event.BACK_SPACE) {
// Not Event.DELETE, because it produces "?" on the MAC.
deleteSelected();
canvas.repaint();
return true;
}
boolean arrow = false;
int dx = 0;
int dy = 0;
if (e.key == Event.UP) {
dy = -1;
arrow = true;
}
else if (e.key == Event.RIGHT) {
dx = 1;
arrow = true;
}
else if (e.key == Event.DOWN) {
dy = 1;
arrow = true;
}
else if (e.key == Event.LEFT) {
dx = -1;
arrow = true;
}
if (arrow) {
if (e.modifiers != 0) {
// Don't care about return value.
((Area)areas.elementAt(selected)).resizeBy(dx,dy,imageRect);
}
else {
// Don't care about return value.
((Area)areas.elementAt(selected)).moveBy(dx,dy,imageRect);
}
canvas.repaint();
return true;
}
}
// Force completion, delete area if failure.
if (e.key == ' ') {
if (completing()) {
if (! ((Area)areas.elementAt(selected)).forceCompletion() ) {
deleteSelected();
canvas.repaint();
return true;
}
// Finished completing an area, go back to selection mode.
if (((Area)areas.elementAt(selected)).completed()) {
// This will call MapEditApp.select()
arrowCheckBox.setState(true);
}
}
}
return false;
}
// p is in image coordinates
private int areaAt(Point p) {
// Find first area that contains p.
for (int n = 0; n < areas.size(); n++) {
if (((Area)areas.elementAt(n)).containsPoint(p.x,p.y)) {
return n;
}
}
return -1;
}
/* Clip p (in image coords) to the image rectangle. */
private void clipImage(Point p) {
p.x = Math.max(p.x,0);
p.y = Math.max(p.y,0);
p.x = Math.min(p.x,imageDim.width);
p.y = Math.min(p.y,imageDim.height);
}
synchronized boolean mouseUp(Event e) {
Debug.println("mouseUp");
Point pImage = new Point(e.x - offset.width,e.y - offset.height);
if (completing()) {
clipImage(pImage);
// In the process of completing an area, let the area handle it.
((Area)areas.elementAt(selected)).mouseUp(pImage,imageRect);
// Finished completing an area, go back to selection mode.
if (((Area)areas.elementAt(selected)).completed()) {
// This will call MapEditApp.select()
arrowCheckBox.setState(true);
}
}
canvas.repaint();
return true;
}
Point doubleClick = null;
synchronized boolean mouseDown(Event e) {
Debug.println("mouseDown");
// Just for the fun of it.
requestFocus();
Point pImage = new Point(e.x - offset.width,e.y - offset.height);
// Select
if (mode == SELECT) {
int n = areaAt(pImage);
// If n == -1, we select the background.
setSelected(n);
if (n != -1) {
// Get point in the Area's coordinate system
// Used for dragging selected areas.
Rectangle r = ((Area)areas.elementAt(selected)).boundingBox();
dragPoint.x = pImage.x - r.x;
dragPoint.y = pImage.y - r.y;
}
}
else {
clipImage(pImage);
if (completing()) {
if (doubleClick != null) {
// On double click, try to force completion
if (!((Area)areas.elementAt(selected)).forceCompletion()) {
deleteSelected();
}
// Finished completing an area, go back to selection mode.
if (((Area)areas.elementAt(selected)).completed()) {
// This will call MapEditApp.select()
arrowCheckBox.setState(true);
}
}
else {
// In the process of completing an area, let the area handle it.
((Area)areas.elementAt(selected)).mouseDown(pImage,imageRect);
}
}
else {
// Start creating a new area.
switch (mode) {
case CREATE_RECT_AREA: {
Debug.println("calling RectArea first mouse down");
areas.addElement(RectArea.firstMouseDown(pImage));
syncAreaList();
}
break;
case CREATE_CIRCLE_AREA: {
areas.addElement(CircleArea.firstMouseDown(pImage));
syncAreaList();
}
break;
case CREATE_POLY_AREA: {
areas.addElement(PolyArea.firstMouseDown(pImage));
syncAreaList();
}
break;
default:
Debug.println("invalid mode");
}
setSelected(areas.size() - 1); // Last element.
}
}
canvas.repaint();
doubleClick = new Point(e.x,e.y);
return true;
}
void checkKillDoubleClick(Event e) {
// Don't kill doubleClick if event (x,y) hasn't changed.
if (doubleClick != null &&
(doubleClick.x != e.x || doubleClick.y != e.y)) {
doubleClick = null;
}
}
synchronized boolean mouseDragged(Event e) {
Debug.println("mouseDragged");
checkKillDoubleClick(e);
Point pImage = new Point(e.x - offset.width,e.y - offset.height);
boolean dirty = false;
// Move region with pointer
if (mode == SELECT && selected != -1) {
// Move so that pImage is a dragPoint with respect to the Area.
Area area = (Area)areas.elementAt(selected);
Rectangle r = area.boundingBox();
int dx = pImage.x - r.x - dragPoint.x;
int dy = pImage.y - r.y - dragPoint.y;
if (area.moveBy(dx,dy,imageRect)) {
dirty = true;
}
}
if (completing()) {
clipImage(pImage);
// In the process of completing an area, let the area handle it.
((Area)areas.elementAt(selected)).mouseDragged(pImage,imageRect);
dirty = true;
}
if (dirty) {
canvas.repaint(REPAINT_TIME);
}
return true;
}
synchronized boolean mouseMoved(Event e) {
Debug.println("mouseMoved");
checkKillDoubleClick(e);
Point pImage = new Point(e.x - offset.width,e.y - offset.height);
// In the process of completing an area, let the area handle it.
if (completing()) {
clipImage(pImage);
((Area)areas.elementAt(selected)).mouseMoved(pImage,imageRect);
canvas.repaint(REPAINT_TIME);
}
return true;
}
private void checkAreas() {
Enumeration e = areas.elements();
while (e.hasMoreElements()) {
Area a = (Area)e.nextElement();
if (a.getURL() == null || a.getURL().length() == 0) {
// TODO: Need to warn the user at this point.
Debug.println("Found an area with invalid URL.");
}
}
}
private void done() {
flushURLComponent();
if (completing()) {
deleteSelected();
}
// Don't setSelected(-1) because that won't do anything if completing() is true.
checkAreas();
writeDoc();
success = true;
stopRunning();
}
private void cancel() {
success = false; // redundant
stopRunning();
}
// public so can be called by callbacks.
public void select() {
mode = SELECT;
// Unselect everything.
flushURLComponent();
if (completing()) {
deleteSelected();
}
// May want to edit just created area.
// setSelected(-1);
canvas.repaint();
}
// public so can be called by callbacks.
public void createArea(int newMode) {
mode = newMode;
// Unselect everything.
flushURLComponent();
if (completing()) {
deleteSelected();
}
setSelected(-1);
canvas.repaint();
}
public void setDocument(Document doc) {
document = doc;
}
private boolean isRunning = false; // Are we inside Application.run().
private String imageName;
private int imageLocation; // In tokens, starting with 0.
private String mapName;
private Document document;
private Image image;
private Dimension imageDim;
private Rectangle imageRect; // For convenience, (0,0,imageDim.width,imageDim.height)
public static final int APP_WIDTH = 600;
public static final int APP_HEIGHT = 400;
private final static int REPAINT_TIME = 50;
// All international strings.
private ResourceBundle res = null;
/** List of all areas. */
private Vector areas;
// Index into areas, -1 means none selected.
private int selected = -1;
private StringRef defaultURL = new StringRef();
// What the user is doing.
private final static int SELECT = 0;
private final static int CREATE_RECT_AREA = 1;
private final static int CREATE_CIRCLE_AREA = 2;
private final static int CREATE_POLY_AREA = 3;
private int mode = SELECT;
// Used for dragging selected areas.
private Point dragPoint = new Point(0,0);
// Did plugin succeed.
private boolean success = false;
}
/** Double buffered, asks the MapEditApp to redraw for it. */
class BlankCanvas extends Canvas {
BlankCanvas(MapEditApp mEdtApp) {
mapEditApp = mEdtApp;
}
// Just send it to the MapEditApp
synchronized public boolean handleEvent(Event e) {
if (e.target == this) {
switch (e.id) {
case Event.KEY_PRESS:
case Event.KEY_ACTION:
return mapEditApp.keyDown(e);
case Event.MOUSE_DOWN:
return mapEditApp.mouseDown(e);
case Event.MOUSE_UP:
return mapEditApp.mouseUp(e);
case Event.MOUSE_MOVE:
return mapEditApp.mouseMoved(e);
case Event.MOUSE_DRAG:
return mapEditApp.mouseDragged(e);
}
}
return false;
}
public synchronized void reshape(int x, int y, int width, int height) {
// do the real stuff
super.reshape(x, y, width, height);
mapEditApp.reshapeCanvas(width,height);
// Create offscreen image for double buffer.
offscreen = createImage(width,height);
offDim.width = width;
offDim.height = height;
repaint();
}
synchronized public void paint(Graphics g) {
mapEditApp.paintMe(g);
}
public synchronized void update(Graphics g) {
if (offscreen != null) {
Debug.println("BlankCanvas.paint");
Graphics gOff = offscreen.getGraphics();
gOff.setColor(getBackground());
gOff.fillRect(0,0,offDim.width,offDim.height);
paint(gOff);
g.drawImage(offscreen,0,0,mapEditApp);
}
/*
g.setColor(getBackground());
g.fillRect(0,0,offDim.width,offDim.height);
paint(g);
*/
}
private Image offscreen = null;
private Dimension offDim = new Dimension();
private MapEditApp mapEditApp;
}