All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pushingpixels.substance.extras.internal.tabbed.TabPreviewThread Maven / Gradle / Ivy

There is a newer version: 4.5.0
Show newest version
/*
 * Copyright (c) 2005-2018 Substance Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Substance Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.substance.extras.internal.tabbed;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import org.pushingpixels.substance.extras.api.tabbed.TabPreviewPainter;
import org.pushingpixels.substance.extras.internal.tabbed.DeltaQueue.DeltaMatcher;
import org.pushingpixels.substance.extras.internal.tabbed.DeltaQueue.Deltable;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.TrackableThread;
import org.pushingpixels.substance.internal.utils.WidgetUtilities;

/**
 * Thread for running the tab preview requests.
 * 
 * @author Kirill Grouchnikov
 */
public class TabPreviewThread extends TrackableThread {
	/**
	 * Indication whether a stop request has been issued on this
	 * thread.
	 */
	private boolean stopRequested;

	/**
	 * Queue of preview requests. Contains {@link TabPreviewInfo}s.
	 */
	protected DeltaQueue previewQueue;

	/**
	 * Information for previewing a tabbed pane.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static class TabPreviewInfo extends DeltaQueue.Deltable {
		/**
		 * Tabbed pane.
		 */
		public JTabbedPane tabPane;

		/**
		 * Callback for passing the preview thumbnail once it is computed.
		 */
		public TabPreviewThread.TabPreviewCallback previewCallback;

		/**
		 * Width of the preview thumbnail.
		 */
		private int previewWidth;

		/**
		 * Height of the preview thumbnail.
		 */
		private int previewHeight;

		/**
		 * Indicates whether all tabs in the {@link #tabPane} should be
		 * previewed.
		 */
		public boolean toPreviewAllTabs;

		/**
		 * If {@link #toPreviewAllTabs} is false, contains the
		 * index of the tab to be previewed.
		 */
		public int tabIndexToPreview;

		/**
		 * Points to the preview initiator.
		 */
		public Object initiator;

		public void setPreviewWidth(int previewWidth) {
			this.previewWidth = previewWidth;
		}

		public int getPreviewWidth() {
			return this.previewWidth;
		}

		public void setPreviewHeight(int previewHeight) {
			this.previewHeight = previewHeight;
		}

		public int getPreviewHeight() {
			return this.previewHeight;
		}
	}

	/**
	 * Interface for offering the tab preview image once it has been computed.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static interface TabPreviewCallback {
		/**
		 * Starts the current cycle of
		 * {@link #offer(JTabbedPane, int, BufferedImage)} calls. This can be
		 * used by the implementing class to revalidate itself in case the tab
		 * count in the specified tabbed pane has changed since the previous
		 * cycle of {@link #offer(JTabbedPane, int, BufferedImage)} call.
		 * 
		 * @param tabPane
		 *            Tabbed pane.
		 * @param tabCount
		 *            Tab count in the tabbed pane.
		 * @param tabPreviewInfo
		 *            Tab preview info. Can be changed in the implementation
		 *            code.
		 */
		public void start(JTabbedPane tabPane, int tabCount,
				TabPreviewInfo tabPreviewInfo);

		/**
		 * Offers the preview image (thumbnail) of a tab in the specified tabbed
		 * pane.
		 * 
		 * @param tabPane
		 *            Tabbed pane.
		 * @param tabIndex
		 *            Tab index.
		 * @param componentSnap
		 *            Tab preview image.
		 */
		public void offer(JTabbedPane tabPane, int tabIndex,
				BufferedImage componentSnap);
	}

	/**
	 * Simple constructor. Defined private for singleton.
	 * 
	 * @see #getInstance()
	 */
	private TabPreviewThread() {
		super();
		this.setName("Laf-Widget tab preview");
		this.stopRequested = false;
		this.previewQueue = new DeltaQueue();
	}

	@Override
	public void run() {
		while (!this.stopRequested) {
			try {
				// System.out.println(System.currentTimeMillis() + " Polling");
				int delay = 500;
				List expired = this.dequeueTabPreviewRequest(delay);
				for (Deltable dExpired : expired) {
					final TabPreviewInfo nextPreviewInfo = (TabPreviewInfo) dExpired;
					final JTabbedPane jtp = nextPreviewInfo.tabPane;
					if (jtp == null)
						continue;
					final TabPreviewPainter previewPainter = TabPreviewUtilities
							.getTabPreviewPainter(jtp);
					final int tabCount = jtp.getTabCount();

					if (nextPreviewInfo.toPreviewAllTabs) {
						// The call to start() is only relevant for the
						// preview of all tabs.
						SwingUtilities.invokeLater(() ->
								nextPreviewInfo.previewCallback.start(jtp, 
										jtp.getTabCount(), nextPreviewInfo));

						for (int i = 0; i < tabCount; i++) {
							final int index = i;
							SwingUtilities.invokeLater(() -> 
									getSingleTabPreviewImage(jtp, previewPainter, 
											nextPreviewInfo, index));
						}
					} else {
						SwingUtilities.invokeLater(() -> 
							getSingleTabPreviewImage(jtp, previewPainter,
									nextPreviewInfo, nextPreviewInfo.tabIndexToPreview));
					}

					if (previewPainter.toUpdatePeriodically(jtp)) {
						TabPreviewInfo cyclePreviewInfo = new TabPreviewInfo();
						// copy all the fields from the currently processed
						// request
						cyclePreviewInfo.tabPane = nextPreviewInfo.tabPane;
						cyclePreviewInfo.tabIndexToPreview = nextPreviewInfo.tabIndexToPreview;
						cyclePreviewInfo.toPreviewAllTabs = nextPreviewInfo.toPreviewAllTabs;
						cyclePreviewInfo.previewCallback = nextPreviewInfo.previewCallback;
						cyclePreviewInfo.setPreviewWidth(nextPreviewInfo.getPreviewWidth());
						cyclePreviewInfo.setPreviewHeight(nextPreviewInfo.getPreviewHeight());
						cyclePreviewInfo.initiator = nextPreviewInfo.initiator;

						// schedule it to app-specific delay
						cyclePreviewInfo.setDelta(previewPainter.getUpdateCycle(cyclePreviewInfo.tabPane));

						// queue the new request
						this.queueTabPreviewRequest(cyclePreviewInfo);
					}
				}
				Thread.sleep(delay);
			} catch (InterruptedException ie) {
				ie.printStackTrace();
			}
		}
	}

	/**
	 * Computes and offers the preview thumbnail for a single tab.
	 * 
	 * @param tabPane
	 *            Tabbed pane.
	 * @param previewPainter
	 *            Tab preview painter.
	 * @param previewInfo
	 *            Preview info.
	 * @param tabIndex
	 *            Index of the tab to preview.
	 */
	protected void getSingleTabPreviewImage(final JTabbedPane tabPane,
			final TabPreviewPainter previewPainter,
			final TabPreviewInfo previewInfo, final int tabIndex) {
		int pWidth = previewInfo.getPreviewWidth();
		int pHeight = previewInfo.getPreviewHeight();
		final BufferedImage previewImage = SubstanceCoreUtilities.getBlankImage(pWidth, pHeight);
		Component comp = tabPane.getComponentAt(tabIndex);

		if (previewPainter.hasPreview(tabPane, tabIndex)) {
			Map dbSnapshot = new HashMap();
			WidgetUtilities.makePreviewable(comp, dbSnapshot);
			previewPainter.previewTab(tabPane, tabIndex, previewImage, 0, 0, pWidth, pHeight);
			WidgetUtilities.restorePreviewable(comp, dbSnapshot);
		} else {
			Graphics2D gr = previewImage.createGraphics();
			gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			gr.setColor(Color.red);
			gr.setStroke(new BasicStroke(Math.max(5.0f, Math.min(pWidth, pHeight) / 10.0f)));
			gr.drawLine(0, 0, pWidth, pHeight);
			gr.drawLine(0, pHeight, pWidth, 0);
			gr.dispose();
		}

		if (previewInfo.previewCallback != null) {
			SwingUtilities.invokeLater(() ->
					previewInfo.previewCallback.offer(tabPane, tabIndex, previewImage));
		}
	}

	/**
	 * Queues the request to preview one or all tabs in the specified tabbed
	 * pane. Once the request is queued, the thread will pick it up from the
	 * queue (in at most 500 milliseconds in the current implementation) and
	 * start processing it. For each tab (if all tabs were requested to be
	 * previewed), the preview thumbnail will be offered to the relevant
	 * callback. This allows to maintain the interactivity of the application
	 * while generating the preview thumbnails for the tab overview dialog (see
	 * {@link TabOverviewDialog}).
	 * 
	 * @param previewInfo
	 *            Tab preview info.
	 */
	public void queueTabPreviewRequest(TabPreviewInfo previewInfo) {
		this.previewQueue.queue(previewInfo);
	}

	/**
	 * Cancels all tab preview requests that were initiated by the specified
	 * initiator.
	 * 
	 * @param initiator
	 *            Initiator.
	 */
	public void cancelTabPreviewRequests(final Object initiator) {
		DeltaMatcher matcher = new DeltaMatcher() {
			public boolean matches(Deltable deltable) {
				TabPreviewInfo currInfo = (TabPreviewInfo) deltable;
				return (currInfo.initiator == initiator);
			}
		};
		this.previewQueue.removeMatching(matcher);
	}

	/**
	 * Removes the tab preview requests that have at most specified delay left.
	 * 
	 * @param delay
	 *            Delay.
	 * @return The list of all tab preview requests that have at most specified
	 *         delay left.
	 */
	private List dequeueTabPreviewRequest(int delay) {
		return this.previewQueue.dequeue(delay);
	}

	@Override
	protected void requestStop() {
		this.stopRequested = true;
		TabPreviewThread.tabPreviewThread = null;
	}

	/**
	 * The preview thread.
	 */
	private static TabPreviewThread tabPreviewThread;

	/**
	 * Returns the singleton instance of the tab preview thread.
	 * 
	 * @return The singleton instance of the tab preview thread.
	 */
	public static synchronized TabPreviewThread getInstance() {
		if (TabPreviewThread.tabPreviewThread == null) {
			// System.err.println("Allocating ");
			// Thread.dumpStack();
			TabPreviewThread.tabPreviewThread = new TabPreviewThread();
			TabPreviewThread.tabPreviewThread.start();
		}
		return TabPreviewThread.tabPreviewThread;
	}

	/**
	 * Returns indication whether tab preview thread is running.
	 * 
	 * @return true if the tab preview thread is running,
	 *         false otherwise.
	 */
	public static synchronized boolean instanceRunning() {
		return (TabPreviewThread.tabPreviewThread != null);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy