/******************************************************************************* * Copyright (c) 2011, 2012 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tcf.ui.console; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.StringConverter; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.tcf.te.runtime.preferences.ScopedEclipsePreferences; import org.eclipse.tcf.te.tcf.ui.console.activator.UIPlugin; import org.eclipse.tcf.te.tcf.ui.console.interfaces.IPreferenceKeys; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleListener; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.console.MessageConsoleStream; /** * Abstract console implementation. */ public class AbstractConsole extends MessageConsole implements IConsoleListener, IPreferenceChangeListener, IPropertyChangeListener { /** * Immutable buffered console line. */ protected static final class ConsoleBufferLine { public final String message; public final char messageType; /** * Constructor. * * @param message The message. Must not be <code>null</code>. * @param messageType The message type. */ public ConsoleBufferLine(String message, char messageType) { Assert.isNotNull(message); this.message = message; this.messageType = messageType; } } /** * AbstractConsole buffer implementation. Buffers the lines to output if the console itself is * invisible. Once the console gets visible, the buffered lines are send to the console itself. */ protected static class ConsoleBuffer { // The buffer limit (in characters) is retrieved from the console // preferences. If 0, there is no limit. private int bufferLimit = 0; // The consumed characters private int charactersConsumed = 0; // The buffer of console lines private final List<ConsoleBufferLine> lines = new ArrayList<ConsoleBufferLine>(); /** * Constructor. */ public ConsoleBuffer() { } /** * Set the buffer limit in characters. * * @param limit The buffer limit or 0 for unlimited. */ public void setBufferLimit(int limit) { this.bufferLimit = limit > 0 ? limit : 0; } /** * Adds a new line to the buffer. * * @param line The line. Must not be <code>null</code>. */ public void addLine(ConsoleBufferLine line) { Assert.isNotNull(line); // If the limit has been reached, we have to remove the oldest buffered line first. while (bufferLimit > 0 && charactersConsumed + line.message.length() >= bufferLimit) { ConsoleBufferLine droppedLine = lines.remove(0); charactersConsumed -= droppedLine.message.length(); if (charactersConsumed < 0) charactersConsumed = 0; } // Add the new line at the end of the buffer lines.add(line); charactersConsumed += line.message.length(); } /** * Returns the list of currently buffered lines. The list * of buffered lines is cleared. * * @return The currently buffered lines or an empty list. */ public ConsoleBufferLine[] getLinesAndClear() { ConsoleBufferLine[] result = lines.toArray(new ConsoleBufferLine[lines.size()]); lines.clear(); return result; } } // Map to store the created colors per stream type (dispose on shutdown!) private final Map<String, Color> streamColors = new HashMap<String, Color>(); // Map to store the created message streams per stream type private final Map<String, MessageConsoleStream> streams = new HashMap<String, MessageConsoleStream>(); // The console buffer private final ConsoleBuffer buffer = new ConsoleBuffer(); // Flag to show the console on every output private boolean showOnOutput; // Flag to show the console on first output only private boolean showOnFirstOutput; // Flag to mark if the console is visible in the console view. private boolean visible = false; // Flag to mark if the console is fully initialized (means all colors and streams created). private boolean initialized = false; /** * Constructor. * <p> * Initializes preferences and colors but doesn't create the console page yet. * * @param name The console name. * @param imageDescriptor. The console image descriptor or <code>null</code>. */ public AbstractConsole(String name, ImageDescriptor imageDescriptor) { super(name, imageDescriptor); // Initialize the flags from the preferences showOnOutput = UIPlugin.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_CONSOLE_SHOW_ON_OUTPUT); showOnFirstOutput = !showOnOutput; // Listen to preference change events UIPlugin.getScopedPreferences().addPreferenceChangeListener(this); } /* (non-Javadoc) * @see org.eclipse.ui.console.MessageConsole#dispose() */ @Override protected void dispose() { // Do not disconnect the partitioner yet. Dispose is called // if the console is removed from the console view. This does // not mean the user does not want to show the console again later // with the full content preserved. The console will be destroyed // if shutdown() is called. synchronized (buffer) { visible = false; JFaceResources.getFontRegistry().removeListener(this); } } /** * Shutdown and cleanup the console. */ public void shutdown() { // Detach the preferences listener UIPlugin.getScopedPreferences().removePreferenceChangeListener(this); // Partitioner should be disconnected before disposing the colors super.dispose(); // Dispose the colors for (Color color : streamColors.values()) color.dispose(); streamColors.clear(); // Clean the streams streams.clear(); } /* (non-Javadoc) * @see org.eclipse.ui.console.AbstractConsole#init() */ @Override protected void init() { // Called when console is added to the console view super.init(); initConsoleOutputLimitSettings(); initConsoleWidthSetting(); // Ensure that initialization occurs in the UI thread PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { JFaceResources.getFontRegistry().addListener(AbstractConsole.this); initializeStreams(); flushBuffer(); } }); } /** * Appends the cached lines from the buffer to the console. */ protected final void flushBuffer() { synchronized(buffer) { visible = true; for (ConsoleBufferLine line : buffer.getLinesAndClear()) { appendMessage(line.messageType, line.message); } } } /** * Helper method to initialize the console width settings */ private void initConsoleWidthSetting() { // Get the preferences store ScopedEclipsePreferences store = UIPlugin.getScopedPreferences(); // If the console width shall be limited if(store.getBoolean(IPreferenceKeys.PREF_CONSOLE_FIXED_WIDTH)) { // Set the width limit setConsoleWidth(store.getInt(IPreferenceKeys.PREF_CONSOLE_WIDTH)); } else { // Reset to unlimited width setConsoleWidth(-1); } } /** * Helper method to initialize the console output limit settings. */ private void initConsoleOutputLimitSettings() { // Get the preferences store ScopedEclipsePreferences store = UIPlugin.getScopedPreferences(); // If the console output limit shall be set if(store.getBoolean(IPreferenceKeys.PREF_CONSOLE_LIMIT_OUTPUT)) { // Apply the limits setWaterMarks(1000, store.getInt(IPreferenceKeys.PREF_CONSOLE_BUFFER_SIZE)); } else { // Reset to no limits setWaterMarks(-1, 0); } // Update the buffer too synchronized (buffer) { buffer.setBufferLimit(getHighWaterMark()); } } // Internal list of available stream types private static final String[] STREAM_TYPES = new String[] { IPreferenceKeys.PREF_CONSOLE_COLOR_TEXT, IPreferenceKeys.PREF_CONSOLE_COLOR_COMMAND, IPreferenceKeys.PREF_CONSOLE_COLOR_COMMAND_RESPONSE, IPreferenceKeys.PREF_CONSOLE_COLOR_EVENT, IPreferenceKeys.PREF_CONSOLE_COLOR_PROGRESS, IPreferenceKeys.PREF_CONSOLE_COLOR_ERROR }; /* * Initialize the streams of the console. * <p> * Must be called from the UI thread. */ void initializeStreams() { // Synchronize on the buffer to avoid something is dumped // if the console streams have not been fully initialized synchronized (buffer) { if (!initialized) { // Black is the default color. Get the corresponding RGB from the platform RGB defaultColorBlack = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_BLACK).getRGB(); // create the message streams and associate stream colors for (String streamType : STREAM_TYPES) { MessageConsoleStream stream = newMessageStream(); Color color = new Color(PlatformUI.getWorkbench().getDisplay(), StringConverter.asRGB(UIPlugin.getScopedPreferences().getString(streamType), defaultColorBlack)); stream.setColor(color); streams.put(streamType, stream); streamColors.put(streamType, color); } // Apply font setFont(PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry().get(IPreferenceKeys.PREF_CONSOLE_FONT)); // The console is now initialized initialized = true; } } } /** * Returns if or if not this console is visible in the console view. * * @return <code>True</code> if the console is visible, <code>false</code> otherwise. */ public final boolean isVisible() { return visible; } /* (non-Javadoc) * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent) */ @Override public void preferenceChange(PreferenceChangeEvent event) { Assert.isNotNull(event); String property = event.getKey(); if (property == null) return; // Apply the common properties if changed if(IPreferenceKeys.PREF_CONSOLE_FIXED_WIDTH.equals(property)) { initConsoleWidthSetting(); } else if(IPreferenceKeys.PREF_CONSOLE_LIMIT_OUTPUT.equals(property)) { initConsoleOutputLimitSettings(); } else if (IPreferenceKeys.PREF_CONSOLE_SHOW_ON_OUTPUT.equals(property)) { showOnOutput = UIPlugin.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_CONSOLE_SHOW_ON_OUTPUT); } // Color changes are applied only if the console is visible if (isVisible()) { // Black is the default color. Get the corresponding RGB from the platform RGB defaultColorBlack = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_BLACK).getRGB(); for (String streamType : STREAM_TYPES) { if (property.equals(streamType)) { // Get the new RGB values from the preferences RGB newRGB = StringConverter.asRGB(UIPlugin.getScopedPreferences().getString(streamType), defaultColorBlack); // Get the old color Color oldColor = streamColors.get(streamType); // Update the stream color will work only if the color is really different from the old one if ((oldColor == null && newRGB != null) || (oldColor != null && !oldColor.getRGB().equals(newRGB))) { // Create the new color object Color newColor = new Color(PlatformUI.getWorkbench().getDisplay(), newRGB); // Update the stream MessageConsoleStream stream = streams.get(streamType); stream.setColor(newColor); // Dispose the old color if (oldColor != null) oldColor.dispose(); // and update the stream color map streamColors.put(streamType, newColor); } } } } } /* (non-Javadoc) * @see org.eclipse.ui.console.IConsoleListener#consolesAdded(org.eclipse.ui.console.IConsole[]) */ @Override public void consolesAdded(IConsole[] consoles) { for (int i = 0; i < consoles.length; i++) { IConsole console = consoles[i]; if (console == AbstractConsole.this) { ConsolePlugin.getDefault().getConsoleManager().addConsoleListener(this); init(); } } } /* (non-Javadoc) * @see org.eclipse.ui.console.IConsoleListener#consolesRemoved(org.eclipse.ui.console.IConsole[]) */ @Override public void consolesRemoved(IConsole[] consoles) { for (int i = 0; i < consoles.length; i++) { IConsole console = consoles[i]; if (console == AbstractConsole.this) { ConsolePlugin.getDefault().getConsoleManager().removeConsoleListener(this); dispose(); } } } /* (non-Javadoc) * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) */ @Override public void propertyChange(PropertyChangeEvent event) { String property = event != null ? event.getProperty() : null; // Font changes are applied only if the console is visible if (property != null && isVisible()) { if (property.equals(IPreferenceKeys.PREF_CONSOLE_FONT)) { setFont(((FontRegistry) event.getSource()).get(IPreferenceKeys.PREF_CONSOLE_FONT)); } } } /** * Append message to the console. * * @param type The message type. * @param message The message. */ public void appendMessage(char type, String message) { // If no message is passed in, nothing to do if (message == null) return; // Open the console if necessary showConsole(false); // Append the message to the console synchronized (buffer) { if (isVisible()) { switch (type) { case 'C': MessageConsoleStream stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_COMMAND); if (stream != null) stream.println(message); break; case 'R': stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_COMMAND_RESPONSE); if (stream != null) stream.println(message); break; case 'E': stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_EVENT); if (stream != null) stream.println(message); break; case 'P': stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_PROGRESS); if (stream != null) stream.println(message); break; case 'N': case 'F': stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_TEXT); if (stream != null) stream.println(message); break; default: stream = streams.get(IPreferenceKeys.PREF_CONSOLE_COLOR_ERROR); if (stream != null) stream.println(message); } } else { buffer.addLine(new ConsoleBufferLine(message, type)); } } } /** * Ensure that the console is shown if needed. * * @param showNoMatterWhat If <code>True</code>, the console will be forced to be shown. */ private void showConsole(boolean showNoMatterWhat) { if(showNoMatterWhat || showOnOutput || showOnFirstOutput) { showOnFirstOutput = false; ConsolePlugin.getDefault().getConsoleManager().showConsoleView(this); } } }