/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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 org.jkiss.dbeaver.ui;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.jkiss.utils.CommonUtils;
import java.util.Locale;
import java.util.StringTokenizer;
/**
* Text utils
*/
public class TextUtils {
public static final char PARAGRAPH_CHAR = (char) 182;
public static String compactWhiteSpaces(String str) {
return str.replaceAll("\\s+", " ");
}
public static boolean isEmptyLine(IDocument document, int line)
throws BadLocationException {
IRegion region = document.getLineInformation(line);
if (region == null || region.getLength() == 0) {
return true;
}
String str = document.get(region.getOffset(), region.getLength());
return str.trim().length() == 0;
}
public static int getOffsetOf(IDocument document, int line, String pattern)
throws BadLocationException {
IRegion region = document.getLineInformation(line);
if (region == null || region.getLength() == 0) {
return -1;
}
String str = document.get(region.getOffset(), region.getLength());
return str.indexOf(pattern);
}
/**
* Shortens a supplied string so that it fits within the area specified by
* the width argument. Strings that have been shorted have an "..." attached
* to the end of the string. The width is computed using the
* {@link org.eclipse.swt.graphics.GC#textExtent(String)}.
*
* @param gc GC used to perform calculation.
* @param t text to modify.
* @param width Pixels to display.
* @return shortened string that fits in area specified.
*/
public static String getShortText(GC gc, String t, int width) {
if (CommonUtils.isEmpty(t)) {
return t;
}
if (width >= gc.textExtent(t).x) {
return t;
}
int w = gc.textExtent("...").x;
String text = t;
int l = text.length();
int pivot = l / 2;
int s = pivot;
int e = pivot + 1;
while (s >= 0 && e < l) {
String s1 = text.substring(0, s);
String s2 = text.substring(e, l);
int l1 = gc.textExtent(s1).x;
int l2 = gc.textExtent(s2).x;
if (l1 + w + l2 < width) {
text = s1 + " ... " + s2;
break;
}
s--;
e++;
}
if (s == 0 || e == l) {
text = text.substring(0, 1) + "..." + text.substring(l - 1, l);
}
return text;
}
/**
* Shortens a supplied string so that it fits within the area specified by
* the width argument. Strings that have been shorted have an "..." attached
* to the end of the string. The width is computed using the
* {@link org.eclipse.swt.graphics.GC#stringExtent(String)}.
* <p/>
* Text shorten removed due to awful algorithm (it works really slow on long strings).
* TODO: make something better
*
* @param fontMetrics fontMetrics used to perform calculation.
* @param t text to modify.
* @param width Pixels to display.
* @return shortened string that fits in area specified.
*/
public static String getShortString(FontMetrics fontMetrics, String t, int width) {
// return t;
if (CommonUtils.isEmpty(t)) {
return t;
}
if (width <= 0) {
return ""; //$NON-NLS-1$
}
int avgCharWidth = fontMetrics.getAverageCharWidth();
float length = t.length();
if (width < length * avgCharWidth) {
length = (float) width / avgCharWidth;
length *= 1.5;
if (length < t.length()) {
t = t.substring(0, (int) length);
//return getShortText(gc, t, width);
}
}
return t;
}
public static String formatSentence(String sent)
{
if (sent == null) {
return "";
}
StringBuilder result = new StringBuilder();
StringTokenizer st = new StringTokenizer(sent, " \t\n\r-,.\\/", true);
while (st.hasMoreTokens()) {
String word = st.nextToken();
if (word.length() > 0) {
result.append(formatWord(word));
}
}
return result.toString();
}
public static String formatWord(String word)
{
if (word == null) {
return "";
}
StringBuilder sb = new StringBuilder(word.length());
sb.append(Character.toUpperCase(word.charAt(0)));
for (int i = 1; i < word.length(); i++) {
char c = word.charAt(i);
if ((c == 'i' || c == 'I') && sb.charAt(i - 1) == 'I') {
sb.append('I');
} else {
sb.append(Character.toLowerCase(c));
}
}
return sb.toString();
}
/**
* Gets text size.
* x: maximum line length
* y: number of lines
* @param text source text
* @return size
*/
public static Point getTextSize(String text) {
int length = text.length();
int maxLength = 0;
int lineCount = 1;
int lineLength = 0;
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
switch (c) {
case '\n':
maxLength = Math.max(maxLength, lineLength);
lineCount++;
lineLength = 0;
break;
case '\r':
break;
case '\t':
lineLength += 4;
break;
default:
lineLength++;
break;
}
}
maxLength = Math.max(maxLength, lineLength);
return new Point(maxLength, lineCount);
}
public static boolean isPointInRectangle(int x, int y, int rectX, int rectY, int rectWidth, int rectHeight)
{
return (x >= rectX) && (y >= rectY) && x < (rectX + rectWidth) && y < (rectY + rectHeight);
}
public static String getSingleLineString(String displayString) {
return displayString.replace('\n', PARAGRAPH_CHAR).replace("\r", "").replace((char)0, ' ');
}
/**
* Copied from Apache FuzzyScore implementation.
* One point is given for every matched character. Subsequent matches yield two bonus points. A higher score
* indicates a higher similarity.
*/
public static int fuzzyScore(CharSequence term, CharSequence query) {
return fuzzyScore(term, query, Locale.getDefault());
}
public static int fuzzyScore(CharSequence term, CharSequence query, Locale locale) {
if (term == null || query == null) {
throw new IllegalArgumentException("Strings must not be null");
}
// fuzzy logic is case insensitive. We normalize the Strings to lower
// case right from the start. Turning characters to lower case
// via Character.toLowerCase(char) is unfortunately insufficient
// as it does not accept a locale.
final String termLowerCase = term.toString().toLowerCase(locale);
final String queryLowerCase = query.toString().toLowerCase(locale);
// the resulting score
int score = 0;
// the position in the term which will be scanned next for potential
// query character matches
int termIndex = 0;
// index of the previously matched character in the term
int previousMatchingCharacterIndex = Integer.MIN_VALUE;
for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) {
final char queryChar = queryLowerCase.charAt(queryIndex);
boolean termCharacterMatchFound = false;
for (; termIndex < termLowerCase.length(); termIndex++) {
final char termChar = termLowerCase.charAt(termIndex);
if (queryChar == termChar) {
// simple character matches result in one point
score++;
if (termIndex == 0) {
// First character
score += 4;
} else if (!Character.isLetter(termLowerCase.charAt(termIndex - 1))) {
// Previous character was a divider
score += 2;
}
// subsequent character matches further improve
// the score.
if (previousMatchingCharacterIndex + 1 == termIndex) {
score += 4;
}
previousMatchingCharacterIndex = termIndex;
termIndex++;
// we can leave the nested loop. Every character in the
// query can match at most one character in the term.
termCharacterMatchFound = true;
break;
}
}
if (!termCharacterMatchFound) {
return 0;
}
}
return score;
}
public static String cutExtraLines(String message, int maxLines) {
if (message == null || message.indexOf('\n') == -1) {
return message;
}
int lfCount = 0;
StringBuilder buf = new StringBuilder();
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
if (c == '\n') {
lfCount++;
}
buf.append(c);
if (lfCount == maxLines) {
buf.append("...");
break;
}
}
return buf.toString();
}
}