package eu.jucy.gui.texteditor;
import helpers.GH;
import helpers.PreferenceChangedAdapter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.custom.PaintObjectEvent;
import org.eclipse.swt.custom.PaintObjectListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.swt.IFocusService;
import eu.jucy.gui.ApplicationWorkbenchWindowAdvisor;
import eu.jucy.gui.GUIPI;
import eu.jucy.gui.GuiHelpers;
import uc.IHub;
import uc.IUser;
import uc.database.DBLogger;
import uc.database.ILogEntry;
import uihelpers.SUIJob;
/**
* viewer that will help handling a styled text
* @author Quicksilver
*
*/
public class StyledTextViewer {
private static final Logger logger = LoggerFactory.make();
public static final int HISTORY = 150;
private final StyledText text;
private final IHub hub;
private final List<Message> messages = new LinkedList<Message>();
private final List<ITextModificator> modificators = new CopyOnWriteArrayList<ITextModificator>();
private PreferenceChangedAdapter pfca;
private final boolean pm;
private final IUser usr;
private boolean scrollLock = false;
private final List<ObjectPoint<Image>> imagePoints = new ArrayList<ObjectPoint<Image>>();
private final List<ObjectPoint<Control>> controlPoints = new ArrayList<ObjectPoint<Control>>();
private final SortedMap<Integer,ObjectPoint<? extends Object>> allPointsByX = new TreeMap<Integer,ObjectPoint<? extends Object>>();
public StyledTextViewer(StyledText styledtext,IHub hub, boolean pm) {
this(styledtext,hub,pm,null,Long.MAX_VALUE);
}
public StyledTextViewer(StyledText styledtext,IHub hub, boolean pm,IUser usr,long loadbeforeTime) {
this.pm = pm;
this.usr = usr;
Assert.isTrue(pm ^ usr == null);
this.text = styledtext;
this.hub = hub;
text.addModifyListener(new ModifyListener() {//moves the text downwards
public void modifyText(final ModifyEvent e) {
new SUIJob(text) {
@Override
public void run() {
if (!text.isFocusControl() && !scrollLock) {
text.setSelection(text.getCharCount());
text.redraw();
}
}
}.schedule();
}
});
text.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent e) {
int start = e.start;
int replaceCharCount = e.end - e.start;
int newCharCount = e.text.length();
//start removal
SortedMap<Integer,ObjectPoint<? extends Object>> toRemoveM = allPointsByX.subMap(Integer.valueOf(e.start), Integer.valueOf(e.end));
for (Iterator<Map.Entry<Integer,ObjectPoint<? extends Object>>> it = toRemoveM.entrySet().iterator(); it.hasNext();) {
Map.Entry<Integer,ObjectPoint<?>> entry = it.next();
ObjectPoint<?> op = entry.getValue();
if (op.obj instanceof Control) {
((Control)op.obj).dispose();
controlPoints.remove(op);
} else {
imagePoints.remove(op);
}
it.remove();
}
//start modification
Map<Integer,ObjectPoint<? extends Object>> toAdjust = new HashMap<Integer,ObjectPoint<? extends Object>>(allPointsByX.tailMap(Integer.valueOf(start)));
for (ObjectPoint<? extends Object> point:toAdjust.values()) {
Integer oldKey = point.x;
point.x += newCharCount - replaceCharCount;
if (allPointsByX.get(oldKey) == point) {
allPointsByX.remove(oldKey);
}
allPointsByX.put(Integer.valueOf(point.x), point);
}
}
});
text.addPaintObjectListener(new PaintObjectListener() {
public void paintObject(PaintObjectEvent event) {
GC gc = event.gc;
StyleRange style = event.style;
int start = style.start;
// logger.debug("start: "+start);
ObjectPoint<?> p = allPointsByX.get(Integer.valueOf(start));
//for (ObjectPoint<Image> p: imagePoints) {
// logger.debug("p.x : "+p.x+ " "+p.obj.getClass().getSimpleName());
if (p != null) {
// logger.debug("asc:"+event.ascent+" bind:"+event.bulletIndex+" dsc:"+event.descent+
// " x:"+event.x+" y:"+event.y+" class:"+p.obj.getClass().getSimpleName());
if (p.obj instanceof Image ) {
Image image = (Image)p.obj;
int x = event.x;
int y = event.y + event.ascent - style.metrics.ascent;
gc.drawImage(image, x, y);
} else if (p.obj instanceof Control) {
Control c = ((Control)p.obj);
int x = event.x; // + ObjectPoint.MARGIN;
int y = event.y + event.ascent - style.metrics.ascent;
if (x >= -10 && y >= -15) {
c.setLocation(x, y);
if (!c.isVisible()) {
c.setVisible(true);
}
} else {
if (c.isVisible()) {
c.setLocation(-100, -100);
c.setVisible(false);
}
}
}
}
}
});
text.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
try {
int beginningPos = text.getOffsetAtLocation(new Point(0,0));
int bottomLine = text.getLineIndex(text.getClientArea().height);
int endPos= text.getOffsetAtLine(bottomLine)+text.getLine(bottomLine).length();
for (ObjectPoint<Control> p: controlPoints) {
boolean newState = beginningPos <= p.x && p.x <= endPos;
if (newState != p.obj.isVisible()) {
p.obj.setVisible(newState);
}
}
} catch (IllegalArgumentException iae) {
if (Platform.inDevelopmentMode()) {
logger.warn("iea: "+iae,iae);
} else {
logger.debug("iea: "+iae,iae);
}
}
}
});
text.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
dispose();
}
});
refreshSettings();
loadOldMessages(loadbeforeTime);
IFocusService ifc = (IFocusService)PlatformUI.getWorkbench().getService(IFocusService.class);
if (ifc != null) {
ifc.addFocusTracker(text, "MyTextViewer");
}
}
public boolean isScrollLock() {
return scrollLock;
}
public void setScrollLock(boolean scrollLock) {
this.scrollLock = scrollLock;
}
public StyledText getText() {
return text;
}
public void copyToClipboard() {
Point p = text.getSelection();
String t = text.getSelectionText();
int addToPos = 0;
for (Map.Entry<Integer,ObjectPoint<? extends Object>> e: allPointsByX.subMap(Integer.valueOf(p.x), Integer.valueOf(p.y)).entrySet()) {
String replacement = e.getValue().replacementText;
int startOfReplacement= e.getKey()-p.x +addToPos;
t = t.substring(0,startOfReplacement)+replacement+t.substring(startOfReplacement+e.getValue().length);
addToPos += replacement.length() - e.getValue().length;
}
GuiHelpers.copyTextToClipboard(t);
}
public void replaceSelection(String replacement,String expectedSelection) {
Point p = text.getSelection();
String t = text.getSelectionText();
logger.debug("is: "+t +" expected: "+expectedSelection);
if (expectedSelection.equals(t)) {
text.replaceTextRange(p.x, t.length(), replacement);
}
}
private void loadOldMessages(long loadBefore) {
if (pm) {
DBLogger entity = new DBLogger(usr,ApplicationWorkbenchWindowAdvisor.get());
List<ILogEntry> logentry = entity.loadLogEntrys(System.currentTimeMillis()-7L*24L*3600L*1000L ,System.currentTimeMillis());
//we have a problem here... if this was just opened by a message.. the message is already logged..
//so we will receive that message as old message.. -> remove last old message if it is way to new..
if (!logentry.isEmpty() && System.currentTimeMillis()-logentry.get(0).getDate() < 1000 ) {
logentry.remove(0);
}
if (logentry.isEmpty()) { //if now nothing was found... search older logs
logentry = entity.loadLogEntrys(HISTORY,0);
if (!logentry.isEmpty() && System.currentTimeMillis()-logentry.get(0).getDate() < 1000 ) {
logentry.remove(0);
}
}
String s = "";
int totalLines = 0;
for (ILogEntry le: logentry) {
if (le.getDate() < loadBefore && totalLines < HISTORY) {
OldMessage om = new OldMessage(le.getMessage(),new Date(le.getDate()));
messages.add(om);
String mes = om.toString();
totalLines += GH.getOccurences(mes, '\n');
s = mes+s;
}
}
text.setText(s);
//set all grey..
StyleRange sr = new StyleRange();
sr.start = 0;
sr.length = s.length();
sr.foreground = MessageType.OLD.getColour();
text.setStyleRange(sr);
}
}
// /**
// *
// * @param image
// * @param offset
// * @param length
// * @return
// *
// * @deprecated
// */
// ObjectPoint<Image> addImage(Image image,int offset,int length) {
// List<StyleRange> ranges = new ArrayList<StyleRange>();
// ObjectPoint<Image> ip = ObjectPoint.create(offset, length,"", image, ranges);
//
// if (allPointsByX.remove(Integer.valueOf(offset)) != null) {
// imagePoints.remove(ip);
// }
// imagePoints.add(ip);
// allPointsByX.put(Integer.valueOf(offset), ip);
// for (StyleRange range:ranges) {
// text.setStyleRange(range);
// }
// return ip;
// }
/**
*
* @param control
* @param offset
* @param ascentPerc
* @return
*/
ObjectPoint<Control> addControl(Control control, int offset,String replacementText,float ascentPerc) {
List<StyleRange> ranges = new ArrayList<StyleRange>();
ObjectPoint<Control> op = ObjectPoint.create(offset,replacementText, ascentPerc, control, ranges);
addControlPoint(op);
for (StyleRange range:ranges) {
text.setStyleRange(range);
}
return op;
}
@SuppressWarnings("unchecked")
private void addControlPoint(ObjectPoint<Control> op) {
ObjectPoint<Control> old = (ObjectPoint<Control>)allPointsByX.remove(Integer.valueOf(op.x));
if (old != null) {
controlPoints.remove(old);
old.obj.dispose();
}
controlPoints.add(op);
allPointsByX.put(Integer.valueOf(op.x), op);
}
private void addImagePoint(ObjectPoint<Image> ip) {
if (allPointsByX.remove(Integer.valueOf(ip.x)) != null) {
imagePoints.remove(ip);
}
imagePoints.add(ip);
allPointsByX.put(Integer.valueOf(ip.x), ip);
}
// ObjectPoint<Runnable> addRunnable(Runnable runnable,int offset,int length,Color foreground,Color background,Font font) {
// List<StyleRange> ranges = new ArrayList<StyleRange>();
// ObjectPoint<Runnable> rp = ObjectPoint.createRunnablePoint(offset, length, runnable, foreground,background,font, ranges);
//
// if (allPointsByX.remove(Integer.valueOf(offset)) != null) {
// clickablePoints.remove(rp);
// }
// clickablePoints.add(rp);
// allPointsByX.put(Integer.valueOf(offset), rp);
// for (StyleRange range:ranges) {
// text.setStyleRange(range);
// }
// return rp;
// }
private void refreshSettings() {
unloadModificators();
loadModificators();
for (ITextModificator mod: modificators) {
mod.init(text,this ,hub);
}
}
private void unloadModificators() {
for (ITextModificator textmod: modificators) {
textmod.dispose();
}
if (pfca != null) {
pfca.dispose();
}
modificators.clear();
}
private void loadModificators() {
IExtensionRegistry reg = Platform.getExtensionRegistry();
IConfigurationElement[] configElements = reg
.getConfigurationElementsFor(ITextModificator.ExtensionpointID);
List<String> allIds = new ArrayList<String>();
for (IConfigurationElement element : configElements) {
String id = element.getAttribute("id");
logger.debug("loading: "+id);
String fullid = GUIPI.IDForTextModificatorEnablement(id);
allIds.add(fullid);
if (GUIPI.getBoolean(fullid)) {
try {
modificators.add(0, (ITextModificator)element.createExecutableExtension("class"));
} catch (CoreException e) {
logger.error("Can't load TextModificator "+id,e);
}
}
}
pfca = new PreferenceChangedAdapter(GUIPI.get(),allIds.toArray(new String[]{})) {
@Override
public void preferenceChanged(String preference,String oldValue, String newValue) {
new SUIJob() {
@Override
public void run() {
refreshSettings();
refresh();
}
}.scheduleIfNotRunning(500,this);
}
};
}
/**
* clears the Text and deletes all messages
*/
public void clear() {
text.setText("");
messages.clear();
}
/**
* adds message to the end of the StyledText
* @param message What is to be added
* @param received The time the message was received
* @param usr A user that can be associated with the message
*/
public void addMessage(String message,IUser usr,Date received,MessageType type) {
Message m = new Message(message,usr,received,type);
messages.add(m);
if (messages.size() > HISTORY) {
messages.remove(0);
}
appendMessage(m);
}
private void appendMessage(Message m) {
if (!text.isDisposed()) {
m.append(text, this);
// int start = text.getCharCount();
// String message = m.toString();
// if (!GH.isEmpty(message)) {
// text.append(message);
// m.setStyleRanges(start, message);
// }
}
}
/**
* refreshes the styled text
* used when timeStamps change
*/
public void refresh() {
text.setText("");
for (Message m: messages) {
appendMessage(m);
}
}
public void dispose() {
unloadModificators();
}
public class Message {
protected final MessageType type;
protected final String message;
private final IUser usr;
private final Date received;
private Message(String message,IUser usr,Date received,MessageType type) {
this.type = type;
this.message = "\n"+message.replace("\n<", "\n- <").replace("\n[", "\n- [");
this.usr = usr;
this.received = received;
}
private Message(String message,Date received,MessageType type) {
this(message,null,received,type);
}
public void append(StyledText text,StyledTextViewer stv) {
List<TextReplacement> replacements = new ArrayList<TextReplacement>();
for (ITextModificator mod: modificators) {
mod.getMessageModifications(this, pm, replacements);
}
Collections.sort(replacements);
String renderedMessage = message;
int startOfMessage = text.getCharCount();
int addPos = 0;
List<StyleRange> ranges = new ArrayList<StyleRange>();
List<ObjectPoint<Image>> imagePoints = new ArrayList<ObjectPoint<Image>>();
List<ObjectPoint<Control>> controlPoints = new ArrayList<ObjectPoint<Control>>();
for (TextReplacement tr: replacements) {
renderedMessage = renderedMessage.substring(0, tr.position+addPos)
+ tr.replacement
+ renderedMessage.substring(tr.position+addPos+tr.lengthToReplace);
tr.apply(text, ranges, imagePoints, controlPoints, startOfMessage + addPos+tr.position , this);
addPos += tr.replacement.length()- tr.lengthToReplace;
}
StyleRange sr = new StyleRange();
sr.start = startOfMessage;
sr.length = renderedMessage.length();
sr.foreground = type.getColour();
if (sr.foreground != null) {
ranges.add(0, sr);
}
text.append(renderedMessage);
for (ObjectPoint<Image> ip:imagePoints) {
stv.addImagePoint(ip);
}
for (ObjectPoint<Control> cp:controlPoints) {
stv.addControlPoint(cp);
}
for (StyleRange range:ranges) {
text.setStyleRange(range);
}
}
public String toString() {
// String message = this.message;
// for (ITextModificator mod: modificators) {
// message = mod.modifyMessage(message, this, pm);
// if (message == null) {
// break;
// }
// }
if (message == null) {
return ""; //ignore message..
}
return message; // "\n"+message.replace("\n<", "\n- <").replace("\n[", "\n- ["); //replace helps showing the difference with pasted text..
}
public String getMessage() {
return message;
}
public IUser getUsr() {
return usr;
}
public Date getReceived() {
return received;
}
}
private static final SimpleDateFormat sdf = new SimpleDateFormat("[dd.MM. HH:mm]");
private class OldMessage extends Message {
private OldMessage(String message,Date received) {
super(message,received,MessageType.OLD);
}
@Override
public String toString() {
return "\n"+sdf.format(getReceived()) +this.message.substring(1);
}
public void append(StyledText text,StyledTextViewer stv) {
int start = text.getCharCount();
String renderedMessage = toString();
text.append(renderedMessage);
StyleRange sr = new StyleRange();
sr.start = start;
sr.length = renderedMessage.length();
sr.foreground = MessageType.OLD.getColour();
text.setStyleRange(sr);
}
}
public static class TextReplacement implements Comparable<TextReplacement> {
/**
* position relative to message
*/
protected final int position;
protected final int lengthToReplace;
protected final String replacement;
public TextReplacement(int position, int lengthToReplace,String replacement) {
this.position = position;
this.lengthToReplace = lengthToReplace;
this.replacement = replacement;
}
public void apply(StyledText st,List<StyleRange> toAdd,List<ObjectPoint<Image>> imagePoints,List<ObjectPoint<Control>> controlPoints ,int positionInText,Message message) {
addStyle(toAdd,positionInText,message);
}
/**
*
* @param toAdd where styleranges created should be added
* @param positionInText - position of the replacement relative to beginning of text
* @param message the original message
*/
protected void addStyle(List<StyleRange> toAdd,int positionInText,Message message) {}
public int compareTo(TextReplacement o) {
return GH.compareTo(position, o.position);
}
}
public static abstract class ObjectReplacement extends TextReplacement {
private static final String OBJTEXT = "\uFFFC";
protected final String replacedText;
public ObjectReplacement(int position, int length,String textToReplace) {
super(position, length, OBJTEXT);
this.replacedText = textToReplace;
}
}
public static class ImageReplacement extends ObjectReplacement {
private final Image img;
public ImageReplacement(int position, String textToReplace, Image img) {
this(position, textToReplace.length(),textToReplace,img);
}
/**
* here text to replace may defer
* @param position
* @param lengthToReplace
* @param imageReplacementText
* @param img
*/
public ImageReplacement(int position,int lengthToReplace,String imageReplacementText, Image img) {
super(position, lengthToReplace,imageReplacementText);
this.img = img;
}
@Override
public void apply(StyledText st,List<StyleRange> toAdd,List<ObjectPoint<Image>> imagePoints,List<ObjectPoint<Control>> controlPoints ,int positionInText,Message message) {
ObjectPoint<Image> ip = ObjectPoint.create(positionInText, 1,replacedText, img, toAdd);
imagePoints.add(ip);
}
}
public static abstract class ControlReplacement extends ObjectReplacement {
public ControlReplacement(int position, String textToReplace) {
super(position, textToReplace.length(),textToReplace);
}
@Override
public void apply(StyledText st,List<StyleRange> toAdd,List<ObjectPoint<Image>> imagePoints,List<ObjectPoint<Control>> controlPoints ,int positionInText,Message message) {
float[] ascent = new float[] {2f/3f};
Control c = createControl(st,ascent);
ObjectPoint<Control> op = ObjectPoint.create(positionInText,replacedText, ascent[0], c, toAdd);
controlPoints.add(op);
}
/**
*
* @param createOn - the text widget to create the control on
* @param ascentPercent - length one array -> used as pointer to percentage of ascent the control should have
* @return control created on
*/
public abstract Control createControl(StyledText createOn,float[] ascentPercent);
}
public static class MyStyledText extends StyledText {
private StyledTextViewer viewer;
public MyStyledText(Composite parent, int style) {
super(parent, style);
}
public void setViewer(StyledTextViewer viewer) {
this.viewer = viewer;
}
@Override
public void copy() {
if (viewer != null) {
viewer.copyToClipboard();
} else {
super.copy();
}
}
@Override
public void copy(int clipboardType) {
if (viewer != null) {
viewer.copyToClipboard();
} else {
super.copy(clipboardType);
}
}
@Override
protected void checkSubclass() {}
}
}