/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.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.osedu.org/licenses/ECL-2.0
*
* 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 tufts.vue;
import tufts.Util;
import tufts.vue.NodeTool.NodeModeTool;
import static tufts.vue.LWSlide.*;
import java.util.*;
import java.awt.Color;
/**
*
* Utility methods for auto-generating and synchronizing slides for pathway entries.
* (A pathway entry usually pairs a node with a slide, although they don't require a slide).
*
* @author Scott Fraize
* @version $Revision: 1.13 $ / $Date: 2010-02-03 19:17:40 $ / $Author: mike $
*/
class Slides {
protected static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(Slides.class);
// private static LWNode createNode(Resource r) {
// LWNode newNode = NodeModeTool.createNewNode(r.getTitle());
// newNode.setResource(r);
// return newNode;
// }
// todo: resource should always be from the node, so can eventually have this take just one arg when done refactoring:
private static LWNode createNode(LWComponent node, Resource r) {
if (!r.equals(node.getResource()))
Log.warn("createNode: resource not on node: " + node + "; r=" + r);
String label;
if (node.hasLabel())
label = node.getLabel();
else
label = r.getTitle();
LWNode newNode = NodeModeTool.createNewNode(label);
newNode.setResource(r);
return newNode;
}
protected enum Sync { ALL, TO_NODE, TO_SLIDE };
private static LWSlide CreatePathwaySlide(LWPathway.Entry entry)
{
final LWSlide s = LWSlide.instance();
s.setStrokeWidth(0f);
s.setFillColor(null);
s.setPathwayEntry(entry);
return s;
}
// public static LWSlide CreateForPathwayEntry(LWPathway.Entry e) {
// return CreateForPathwayEntry(e, e.node.getDisplayLabel(), e.node, e.node.getAllDescendents(), false);
// }
// public static LWSlide CreateForPathwayEntry(LWPathway.Entry entry,
// String titleText,
// LWComponent mapNode,
// Iterable<LWComponent> contents,
// boolean syncTitle)
// {
// final LWSlide slide = CreatePathwaySlide(entry);
// final LWNode title = NodeModeTool.buildTextNode(titleText);
// final MasterSlide master = entry.pathway.getMasterSlide();
// final CopyContext cc = new CopyContext(false);
// final LinkedList<LWComponent> toLayout = new java.util.LinkedList();
public static LWSlide CreateForPathwayEntry(LWPathway.Entry entry)
{
final LWSlide slide = buildPathwaySlide(entry);
entry.pathway.ensureID(slide);
return slide;
}
private static List<LWComponent> getContentToCopy(LWComponent node) {
final List<LWComponent> content = new ArrayList();
node.getAllDescendents(ChildKind.PROPER, content);
// remove any image that's a node icon, as it'll be
// added via it's node:
Iterator<LWComponent> i = content.iterator();
while (i.hasNext()) {
LWComponent c = i.next();
if (c instanceof LWImage && ((LWImage)c).isNodeIcon())
i.remove();
else if (c instanceof LWLink)
i.remove();
}
return content;
}
/** helpder class used during auto-slide layout that pairs an image with a title for it */
private static class TitledImage {
final LWImage image;
final LWNode title;
TitledImage(LWComponent imageNode, LWImage i) {
image = i;
image.setSyncSource(imageNode);
if (imageNode != null) {
String txt;
if (imageNode.hasLabel())
txt = imageNode.getLabel();
else
txt = i.getLabel();
if (txt == null || txt.length() == 0)
txt = "Image";
title = new LWNode(txt);
title.setAsTextNode(true);
title.setSyncSource(imageNode);
} else
title = null;
}
TitledImage(LWImage syncSource, LWImage i) {
image = i;
title = new LWNode(image.getLabel());
title.setAsTextNode(true);
title.setSyncSource(syncSource);
}
}
private static LWSlide buildPathwaySlide(LWPathway.Entry entry) {
return buildPathwaySlide(CreatePathwaySlide(entry), entry);
}
private static LWSlide buildPathwaySlide(final LWSlide slide, final LWPathway.Entry entry)
{
//final LWSlide slide = CreatePathwaySlide(entry);
final LWNode title = NodeModeTool.buildTextNode(entry.node.getDisplayLabel());
final MasterSlide master = entry.pathway.getMasterSlide();
final CopyContext cc = new CopyContext(false);
final List<LWComponent> added = new ArrayList();
title.setStyle(master.getTitleStyle());
// SMF: No syncing of slide titles, as per Melanie as of 2007-11-13
// if (entry.node != null) {
// if (LWNode.isImageNode(entry.node) || entry.node instanceof LWImage)
// ; // don't sync titles of images
// else
// title.setSyncSource(entry.node);
// }
// we must apply slide styles to all components before laying them out,
// as the style may change their size
title.setLocation(SlideMargin, SlideMargin);
slide.applyStyle(title);
added.add(title);
//final List<LWImage> images = new ArrayList();
//final Map<LWImage,LWComponent> imageLabels = new HashMap();
final List<TitledImage> images = new ArrayList();
final List<LWComponent> text = new ArrayList();
for (LWComponent c : getContentToCopy(entry.node)) {
if (LWNode.isImageNode(c)) {
Log.debug(" SPLITTING " + c);
images.add(new TitledImage(c, ((LWNode)c).getImage().duplicate(cc)));
} else {
Log.debug(" COPYING " + c);
final LWComponent copy = c.duplicate(cc);
copy.setScale(1);
copy.setSyncSource(c);
if (copy instanceof LWImage)
images.add(new TitledImage((LWImage)c, (LWImage)copy));
else
text.add(copy);
}
}
// TODO: if the source node is an image node, need to make sure
// it's image gets added (is filtered by getContentToCopy)
if (LWNode.isImageNode(entry.node)) {
if (images.size() == 0)
images.add(new TitledImage((LWComponent)null, ((LWNode)entry.node).getImage().duplicate(cc)));
else
images.add(new TitledImage(entry.node, ((LWNode)entry.node).getImage().duplicate(cc)));
} else if (entry.node.hasResource()) {
LWNode titleLink = new LWNode(entry.node.getLabel()); // could use resource title instead
titleLink.setResource(entry.node.getResource());
titleLink.setAsTextNode(true);
text.add(titleLink);
}
//-------------------------------------------------------
// Now lay everything out
//-------------------------------------------------------
int x, y;
// Add as children, providing seed relative locations
// for layout, and establish z-order (images under text)
// Nodes will be auto-styled with the master slide style
// in addView.
x = y = SlideMargin;
for (TitledImage t : images) {
if (t.title != null) {
t.title.takeLocation(x++,y++);
slide.applyStyle(t.title);
}
t.image.takeLocation(x++,y++);
slide.applyStyle(t.image);
added.add(t.image);
if (t.title != null)
added.add(t.title); // keep titles over images (add after)
}
// differences in font sizes mean text below title looks to left of title unless slighly indented
final int textIndent = 2;
x = SlideMargin + textIndent;
y = SlideMargin * 2 + (int) title.getHeight();
for (LWComponent c : text) {
c.takeLocation(x++,y++);
slide.applyStyle(c);
}
added.addAll(text);
if (DEBUG.PRESENT || DEBUG.STYLE) Log.debug("LAYING OUT CHILDREN in " + slide + "; parent=" + slide.getParent());
slide.setSize(SlideWidth, SlideHeight);
if (text.size() > 1)
layoutText(slide, text);
// TODO: change image layouts to create a label right-aligned node (and do same for drop)
// TODO: on restore, traverse all children (cept thru groups) and set slide-style bits
if (images.size() > 0)
layoutImages(slide, images);
slide.takeScale(SlideIconScale);
slide.addChildren(added);
return slide;
}
private static void layoutText(LWSlide slide, List<? extends LWComponent> text) {
final LWSelection selection = new LWSelection(text);
// Better to distribute text items in their total height + a border IF they'd all fit on the slide:
// selection.setSize((int) bounds.getWidth(),
// (int) bounds.getHeight() + 15 * selection.size());
selection.setSize(SlideWidth - SlideMargin*2,
(int) (slide.getHeight() - SlideMargin*1.5 - text.get(0).getY()));
selection.setTo(text);
Actions.DistributeVertically.act(selection);
Actions.AlignLeftEdges.act(selection);
}
private static void layoutImages(LWSlide slide, List<TitledImage> images)
{
final float imageRegionTop = SlideMargin * 2;
final float imageRegionBottom = slide.getHeight();
final float imageRegionHeight = imageRegionBottom - imageRegionTop;
if (images.size() == 1) {
//-------------------------------------------------------
// Special layout for a SINGLE IMAGE:
//-------------------------------------------------------
final LWImage image = images.get(0).image;
// Note: the old aspect preserving properties of userSetSize are sometimes
// being foiled by the aspect not yet being known in the ImageRef -- using
// setMaxDimension fixes this (it will pull width/height from the resource
// if the ImageRef doesn't have it's aspect yet).
// image.userSetSize(0,
// slide.getHeight() - SlideMargin * 6, null); // aspect preserving
image.setMaxDimension(slide.getHeight() - SlideMargin * 6);
image.setLocation((slide.getWidth() - image.getWidth()) / 2,
imageRegionTop + (imageRegionHeight - image.getHeight()) / 2);
LWComponent label = images.get(0).title;
if (label != null) {
label.setLocation(image.getX() + (image.getWidth() - label.getWidth()) / 2,
image.getY() + image.getHeight());
}
//image.getY() - label.getHeight());
} else if (images.size() > 1) {
// TitledImage.title should never be null if there is more than one image
// to layout
final float labelHeight = images.get(0).title.getHeight();
final float verticalMargin = labelHeight / 2;
final float maxImageHeight = (imageRegionHeight -
(((images.size()-1)*verticalMargin) + images.size() * labelHeight))
/ images.size();
for (TitledImage t : images) {
t.image.setMaxDimension(maxImageHeight);
//t.image.userSetSize(0, maxImageHeight, null); // aspect preserving
}
float y = imageRegionTop / 2;
for (TitledImage t : images) {
float imageX = slide.getWidth() - t.image.getWidth() - SlideMargin;
float titleX = slide.getWidth() - t.title.getWidth() - SlideMargin + 5;
t.title.setLocation(titleX, y+3);
y += t.title.getHeight();
t.image.setLocation(imageX, y);
y+= t.image.getHeight() + verticalMargin;
}
}
}
public static void synchronizeAll(LWSlide slide) {
synchronizeResources(slide, Sync.ALL);
}
public static void synchronizeSlideToNode(LWSlide slide) {
synchronizeResources(slide, Sync.TO_NODE);
}
public static void synchronizeNodeToSlide(LWSlide slide) {
synchronizeResources(slide, Sync.TO_SLIDE);
}
protected static void synchronizeResources(final LWSlide slide, Sync type) {
if (slide.getSourceNode() == null) {
Log.warn("Can't synchronize a slide w/out a source node: " + slide);
return;
}
if (slide.getEntry() != null && slide.getEntry().isMapView()) {
Util.printStackTrace("cannot synchronize virtual slides");
return;
}
if (!slide.canSync()) {
Util.printStackTrace("Sync not permitted: " + slide + " type(" + type + ")");
return;
}
if (slide.numChildren() == 0 && slide.getEntry() != null && (type == Sync.ALL || type == Sync.TO_SLIDE)) {
// special case: completely rebuild this slide:
buildPathwaySlide(slide, slide.getEntry());
return;
}
final LWComponent node = slide.getSourceNode();
if (DEBUG.Enabled) outf("NODE/SILDE SYNCHRONIZATION; type(%s) ---\n\t NODE: %s\n\tSLIDE: %s", type, node, slide);
final Set<Resource> slideUnique = new LinkedHashSet();
final Set<Resource> nodeUnique = new LinkedHashSet();
final Map<Resource,LWComponent> slideSources = new HashMap();
final Map<Resource,LWComponent> nodeSources = new HashMap();
// First add all resources in any descendent of the node to nodeUnique (include
// the node's resource itself), then iterate through all the resources found
// anywhere inside the slide, removing duplicates from nodeUnique, and adding the
// remainder to slideUnique.
if (node.hasResource()) {
if (nodeUnique.add(node.getResource()))
nodeSources.put(node.getResource(), node);
}
for (LWComponent c : node.getAllDescendents()) {
if (c.hasResource()) {
if (nodeUnique.add(c.getResource()))
nodeSources.put(c.getResource(), c);
}
}
if (DEBUG.Enabled) {
for (Resource r : nodeUnique)
outf("%40s: %s", "UNIQUE NODE RESOURCE", Util.tags(r));
}
final Set<Resource> nodeDupes = new HashSet();
for (LWComponent c : slide.getAllDescendents()) {
if (c.hasResource()) {
final Resource r = c.getResource();
if (nodeUnique.contains(r)) {
nodeDupes.add(r);
if (DEBUG.Enabled) outf("%40s: %s", "ALREADY ON NODE, IGNORE FOR SLIDE", Util.tags(r));
} else {
if (slideUnique.add(r)) {
slideSources.put(r, c);
if (DEBUG.Enabled) outf("%40s: %s", "ADDED UNIQUE SLIDE", Util.tags(r));
}
}
}
}
nodeUnique.removeAll(nodeDupes);
if (DEBUG.Enabled) {
//this.outf(" NODE DUPES: " + nodeDupes + "\n");
outf("SLIDE UNIQUE: " + slideUnique);
outf(" NODE UNIQUE: " + nodeUnique);
}
// TODO: if a resource was added to BOTH the slide and the node
// extra-sync (e.g., cut/paste or drag/drop), and then during sync
// we also probably want to connect these up via sync-source.
if (type == Sync.ALL || type == Sync.TO_NODE) {
for (Resource r : slideUnique) {
// TODO: merge MapDropTarget & NodeModeTool node creation code into NodeTool, including resource handling
LWNode newNode = createNode(slideSources.get(r), r);
slideSources.get(r).setSyncSource(newNode);
node.addChild(newNode);
}
}
if (type == Sync.ALL || type == Sync.TO_SLIDE) {
List<LWComponent> added = new ArrayList();
for (Resource r : nodeUnique) {
final LWComponent newNode;
// TODO: merge this code into something common with buildPathwaySlide
if (false && r.isImage()) {
newNode = new LWImage(r);
} else {
newNode = createNode(nodeSources.get(r), r);
newNode.setSyncSource(nodeSources.get(r));
if (LWNode.isImageNode(newNode))
((LWNode)newNode).getImage().setSyncSource(nodeSources.get(r));
}
added.add(newNode);
}
if (added.size() > 0) {
LWSelection s = new LWSelection(added);
float
x = slide.getWidth() / 4,
y = SlideMargin;
// seed locations
for (LWComponent c : added) {
slide.applyStyle(c);
c.setLocation(x, y++);
}
if (added.size() > 1) {
s.setSize(1, (int) slide.getHeight() - SlideMargin * 2);
Actions.DistributeVertically.act(s);
}
slide.addChildren(added);
VUE.getSelection().setTo(added);
}
}
}
protected static void outf(String format, Object ... args) {
Util.outf(Log, format, args);
}
}