blob: 674b15e4f2dcd1203119db7f9770beae393a49b8 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 com.motorola.studio.android.videos.ui.utils;
import java.util.ArrayList;
import java.util.List;
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.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import com.motorola.studio.android.common.log.StudioLogger;
/**
* General UI Utilities used by the MOTODEV Studio Videos View
*/
public class UiUtilities
{
/**
* Highlight the search keyword in the styled text provided
*
* @param styledText the styled text to be decorated
* @param keyword the keyword to be highlighted in the styled text
* @param highlightTextColor the background color to be used to highlight the keyword
*/
public static void highlightKeywords(StyledText styledText, String keyword,
Color highlightTextColor)
{
// split into multiple keywords
String[] keywordPieces = keyword.split(" ");
/*
* Define which characters must be highlighted by creating a boolean array with the text
* size plus 1 (the last position will remain with the default value - false, and that's
* important to define the last segment to be highlighted)
*/
List<Integer> startOffsets = null;
boolean[] highlightedChars = null;
highlightedChars = new boolean[styledText.getText().length() + 1];
// for each keyword, define which characters must be highlighted in the string / StyledText
for (String keywordPiece : keywordPieces)
{
// find all occurrences of that keyword
startOffsets =
findAllIndexOf(styledText.getText().toUpperCase(), keywordPiece.toUpperCase());
if (startOffsets.size() > 0)
{
// for each occurrence, mark the characters that must be highlighted
for (int startOffset : startOffsets)
{
for (int i = startOffset; i < startOffset + keywordPiece.length(); i++)
{
highlightedChars[i] = true;
}
}
}
}
// finally, create and set the style ranges based on the boolean array with the
// information about what characters that must be highlighted
styledText.setStyleRanges(createStyleRanges(highlightedChars, highlightTextColor));
}
/**
* Create and return an array of StyleRanges based on a boolean array defining which characters
* must be highlighted in the StyledText
*
* @param highlightedChars a boolean array defining which characters must be highlighted in the StyledText
* @param highlightTextColor the background color to be used to highlight the keyword
* @return an array of the StyleRanges that must be applied to the StyledText
*/
private static StyleRange[] createStyleRanges(boolean[] highlightedChars,
Color highlightTextColor)
{
List<StyleRange> styleRanges = new ArrayList<StyleRange>();
// the start variable marks the start of a new segment
Integer start = null;
for (int j = 0; j < highlightedChars.length; j++)
{
if (highlightedChars[j] == true)
{
// this is the beginning of a new segment. If start is not
// null, then this is just an adjacent character in the current segment
if (start == null)
{
start = j;
}
}
else
{
// end of the current segment, register it and continue searching for the next segment
if (start != null)
{
styleRanges.add(getHighlightStyle(start, j - start, highlightTextColor));
start = null;
}
}
}
return styleRanges.toArray(new StyleRange[styleRanges.size()]);
}
/**
* Find all "indexOf" of a keyword in a string, and not only the
* first one, as the method available in the String type implementation
*
* @param text the base string
* @param keyword the keyword to be find in the base string
* @return a list with all indexes
*/
private static List<Integer> findAllIndexOf(String text, String keyword)
{
List<Integer> allIndexes = new ArrayList<Integer>();
if (!keyword.equals(""))
{
int index = text.indexOf(keyword);
while (index >= 0)
{
allIndexes.add(index);
index = text.indexOf(keyword, index + keyword.length());
}
}
return allIndexes;
}
/**
* Create the StyleRange that will be used to highlight the keyword in a given location
*
* @param startOffset where the keyword starts in a string
* @param length the size of the keyword
* @param highlightTextColor the background color to be used to highlight the keyword
* @return the corresponding StyleRange
*/
private static StyleRange getHighlightStyle(int startOffset, int length,
Color highlightTextColor)
{
StyleRange styleRange = new StyleRange();
styleRange.start = startOffset;
styleRange.length = length;
styleRange.background = highlightTextColor;
return styleRange;
}
/**
* Display an animated GIF in a Label
*
* @param device the device object to be used to create the UI components
* @param path the GIF path
* @param container the Label where the GIF will be displayed
*/
public static void displayAnimatedGIF(final Device device, final String path,
final Label container)
{
// create the image loader
final ImageLoader imageLoader = new ImageLoader();
imageLoader.load(path);
// create GC and set the first image
final Image firstImage = new Image(device, imageLoader.data[0]);
final GC gc = new GC(firstImage);
container.setImage(firstImage);
// the paint listener is important because just calling redraw on the
// container doesn't update the content (actually, that works on Windows,
// but on Linux and MacOS it doesn't)
container.addPaintListener(new PaintListener()
{
public void paintControl(PaintEvent e)
{
e.gc.drawImage(firstImage, 0, 0);
}
});
/*
* Create a thread that flip among the other images in the GIF
*/
final Thread thread = new Thread()
{
@Override
public void run()
{
try
{
final Integer[] imageNumber = new Integer[1];
imageNumber[0] = 0;
/*
* Do it until the thread is interrupted
*/
while (true)
{
// wait the appropriate time
int delayTime = imageLoader.data[imageNumber[0]].delayTime;
Thread.sleep(delayTime * 10);
// draw the next image
// it's a sync call so that the executions are not delayed and grouped in the
// feature. Ex: after some time, 10 calls are executed one right after another,
// what will flip among 10 images in the GIF almost at the same time, which
// has no value to the user. Using syncExec we prevent that.
Display.getDefault().syncExec(new Runnable()
{
public void run()
{
// cyclic counter
imageNumber[0] =
imageNumber[0].intValue() == imageLoader.data.length - 1
? 0 : imageNumber[0] + 1;
ImageData nextImageData = imageLoader.data[imageNumber[0]];
Image nextImage = new Image(device, nextImageData);
gc.drawImage(nextImage, nextImageData.x, nextImageData.y);
nextImage.dispose();
// redraw
if (!container.isDisposed())
{
container.redraw();
}
}
});
}
}
catch (InterruptedException e)
{
StudioLogger.info(this.getClass(),
"Thread displaying animated GIF was interrupted: " + path);
}
}
};
/*
* When the container is disposed, the thread that flips
* among the images in the GIF is also stopped
*/
container.addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
thread.interrupt();
}
});
// start the thread
thread.start();
}
}