X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scl.ui%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fui%2Fconsole%2FAbstractCommandConsole.java;fp=bundles%2Forg.simantics.scl.ui%2Fsrc%2Forg%2Fsimantics%2Fscl%2Fui%2Fconsole%2FAbstractCommandConsole.java;h=b4260b47b4a07ea01fc2ee2d94a363ce3f22bdea;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java new file mode 100755 index 000000000..b4260b47b --- /dev/null +++ b/bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/console/AbstractCommandConsole.java @@ -0,0 +1,532 @@ +package org.simantics.scl.ui.console; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Deque; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jface.preference.IPersistentPreferenceStore; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.window.DefaultToolTip; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.custom.VerifyKeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Sash; +import org.eclipse.ui.preferences.ScopedPreferenceStore; + +/** + * A console with input and output area that can be embedded + * into any editor or view. + * @author Hannu Niemistö + */ +public abstract class AbstractCommandConsole extends Composite { + + public static final String PLUGIN_ID = "org.simantics.scl.ui"; + + public static final int COMMAND_HISTORY_SIZE = 50; + + public static final int SASH_HEIGHT = 3; + + LocalResourceManager resourceManager; + + StyledText output; + Sash sash; + protected StyledText input; + + int userInputHeight=0; + int minInputHeight=0; + + protected Color greenColor; + protected Color redColor; + + ArrayList commandHistory = new ArrayList(); + int commandHistoryPos = 0; + + boolean outputModiLock = false; + + /* + Shell tip = null; + Label label = null; + */ + + public AbstractCommandConsole(Composite parent, int style) { + super(parent, style); + createControl(); + } + + @Override + public boolean setFocus() { + return input.setFocus(); + } + + protected boolean canExecuteCommand() { + return true; + } + + private void createControl() { + resourceManager = new LocalResourceManager(JFaceResources.getResources(), this); + greenColor = resourceManager.createColor(new RGB(0, 128, 0)); + redColor = resourceManager.createColor(new RGB(172, 0, 0)); + + setLayout(new FormLayout()); + + Font textFont = new Font(getDisplay(),"Courier New",12,SWT.NONE); + + // Sash + sash = new Sash(this, /*SWT.BORDER |*/ SWT.HORIZONTAL); + sash.addListener(SWT.Selection, new Listener () { + public void handleEvent(Event e) { + Rectangle bounds = AbstractCommandConsole.this.getBounds(); + int max = bounds.y + bounds.height; + + userInputHeight = max-e.y; + + int actualInputHeight = Math.max(userInputHeight, minInputHeight); + sash.setBounds(e.x, max-actualInputHeight, e.width, e.height); + setInputHeight(actualInputHeight); + } + }); + + // Upper + output = new StyledText(this, SWT.MULTI /*| SWT.READ_ONLY*/ | SWT.V_SCROLL | SWT.H_SCROLL); + output.setFont(textFont); + { + FormData formData = new FormData(); + formData.top = new FormAttachment(0); + formData.bottom = new FormAttachment(sash); + formData.left = new FormAttachment(0); + formData.right = new FormAttachment(100); + output.setLayoutData(formData); + } + output.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent e) { + if(outputModiLock) + return; + input.append(e.text); + input.setFocus(); + input.setCaretOffset(input.getText().length()); + e.doit = false; + } + }); + + // Deco + StyledText deco = new StyledText(this, SWT.MULTI | SWT.READ_ONLY); + deco.setFont(textFont); + deco.setEnabled(false); + GC gc = new GC(deco); + int inputLeftPos = gc.getFontMetrics().getAverageCharWidth()*2; + deco.setText(">"); + { + FormData formData = new FormData(); + formData.top = new FormAttachment(sash); + formData.bottom = new FormAttachment(100); + formData.left = new FormAttachment(0); + formData.right = new FormAttachment(0, inputLeftPos); + deco.setLayoutData(formData); + } + + // Input area + input = new StyledText(this, SWT.MULTI); + input.setFont(textFont); + { + FormData formData = new FormData(); + formData.top = new FormAttachment(sash); + formData.bottom = new FormAttachment(100); + formData.left = new FormAttachment(0, inputLeftPos); + formData.right = new FormAttachment(100); + input.setLayoutData(formData); + } + adjustInputSize(""); + input.addVerifyKeyListener(new VerifyKeyListener() { + + @Override + public void verifyKey(VerifyEvent event) { + switch(event.keyCode) { + case SWT.KEYPAD_CR: + case SWT.CR: + if((event.stateMask & SWT.CTRL) == 0) { + if(canExecuteCommand()) + execute(); + event.doit = false; + } + break; + case SWT.ARROW_UP: + case SWT.ARROW_DOWN: + if((event.stateMask & SWT.CTRL) != 0) { + int targetHistoryPos = commandHistoryPos; + if(event.keyCode == SWT.ARROW_UP) { + if(commandHistoryPos <= 0) + return; + --targetHistoryPos; + } + else { + if(commandHistoryPos >= commandHistory.size()-1) + return; + ++targetHistoryPos; + } + setInputText(commandHistory.get(targetHistoryPos)); + commandHistoryPos = targetHistoryPos; + event.doit = false; + } + break; +// case SWT.ESC: +// setInputText(""); +// commandHistoryPos = commandHistory.size(); +// break; + } + } + + }); + input.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent e) { + if(e.text.contains("\n")) { + int lineId = input.getLineAtOffset(e.start); + int lineOffset = input.getOffsetAtLine(lineId); + int indentAmount; + for(indentAmount=0; + lineOffset+indentAmount < input.getCharCount() && + input.getTextRange(lineOffset+indentAmount, 1).equals(" "); + ++indentAmount); + StringBuilder indent = new StringBuilder(); + indent.append('\n'); + for(int i=0;i offset) { + min = Math.max(min, annotation.start); + max = Math.max(min, annotation.end); + if(first) + first = false; + else + description.append('\n'); + description.append(annotation.description); + } + } + + if(min != Integer.MIN_VALUE) { + Rectangle bounds = input.getTextBounds(min, max-1); + toolTip.setText(description.toString()); + toolTip.show(new Point(bounds.x, bounds.y+bounds.height)); + toolTipVisible = true; + } + return; + } + case SWT.MouseMove: + if(toolTipVisible) { + int offset = getOffsetInInput(e.x, e.y); + if(offset < min || offset >= max) { + toolTip.hide(); + toolTipVisible = false; + return; + } + } + return; + case SWT.MouseExit: + if(toolTipVisible) { + toolTip.hide(); + toolTipVisible = false; + } + return; + } + } + }; + input.addListener(SWT.MouseHover, hoverListener); + input.addListener(SWT.MouseMove, hoverListener); + input.addListener(SWT.MouseExit, hoverListener); + + readPreferences(); + + addListener(SWT.Dispose, new Listener() { + + @Override + public void handleEvent(Event event) { + try { + writePreferences(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + }); + + } + + private int getOffsetInInput(int x, int y) { + int offset; + try { + offset = input.getOffsetAtLocation(new Point(x, y)); + } catch(IllegalArgumentException e) { + return -1; + } + if(offset == input.getText().length()) + --offset; + else if(offset > 0) { + Rectangle rect = input.getTextBounds(offset, offset); + if(!rect.contains(x, y)) + --offset; + } + return offset; + } + + public void setInputText(String text) { + input.setText(text); + input.setCaretOffset(text.length()); + adjustInputSize(text); + } + + String validatedText; + + Job validationJob = new Job("SCL input validation") { + + @Override + protected IStatus run(IProgressMonitor monitor) { + String text = validatedText; + asyncSetErrorAnnotations(text, validate(text)); + return Status.OK_STATUS; + } + + }; + + Job preValidationJob = new Job("SCL input validation") { + @Override + protected IStatus run(IProgressMonitor monitor) { + if(!input.isDisposed()) { + input.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if(!input.isDisposed()) { + validatedText = input.getText(); + validationJob.setPriority(Job.BUILD); + validationJob.schedule(); + } + } + }); + } + + return Status.OK_STATUS; + } + }; + + private void asyncValidate() { + if(!input.getText().equals(errorAnnotationsForCommand)) { + preValidationJob.cancel(); + preValidationJob.setPriority(Job.BUILD); + preValidationJob.schedule(500); + } + } + + private static int rowCount(String text) { + int rowCount = 1; + for(int i=0;i COMMAND_HISTORY_SIZE*2) + commandHistory = new ArrayList( + commandHistory.subList(COMMAND_HISTORY_SIZE, COMMAND_HISTORY_SIZE*2)); + commandHistoryPos = commandHistory.size(); + + // Print it into output area + //appendOutput("> " + command.replace("\n", "\n ") + "\n", greenColor, null); + input.setText(""); + + // Execute + execute(command); + } + + public static final ErrorAnnotation[] EMPTY_ANNOTATION_ARRAY = new ErrorAnnotation[0]; + + String errorAnnotationsForCommand; + ErrorAnnotation[] errorAnnotations = EMPTY_ANNOTATION_ARRAY; + + private void syncSetErrorAnnotations(String forCommand, ErrorAnnotation[] annotations) { + errorAnnotationsForCommand = forCommand; + errorAnnotations = annotations; + + { + StyleRange clearRange = new StyleRange(0, forCommand.length(), null, null); + input.setStyleRange(clearRange); + } + + for(int i=0;i recentImportPaths = Preferences.decodePaths(commandHistoryPref); + + commandHistory = new ArrayList(recentImportPaths); + commandHistoryPos = commandHistory.size(); + + return true; + } + + private void writePreferences() throws IOException { + + IPersistentPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); + + store.putValue(Preferences.COMMAND_HISTORY, Preferences.encodePaths(commandHistory)); + + if (store.needsSaving()) + store.save(); + + } + + public abstract void execute(String command); + public abstract ErrorAnnotation[] validate(String command); + + public void clear() { + outputModiLock = true; + output.setText(""); + outputModiLock = false; + } + + public StyledText getOutputWidget() { + return output; + } + +}