00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 package com.realtime.crossfire.jxclient.gui.log;
00023
00024 import com.realtime.crossfire.jxclient.util.EventListenerList2;
00025 import java.awt.Color;
00026 import java.awt.font.FontRenderContext;
00027 import java.awt.geom.RectangularShape;
00028 import java.util.ArrayList;
00029 import java.util.Collections;
00030 import java.util.Iterator;
00031 import java.util.List;
00032 import java.util.ListIterator;
00033 import java.util.concurrent.CopyOnWriteArrayList;
00034 import org.jetbrains.annotations.NotNull;
00035 import org.jetbrains.annotations.Nullable;
00036
00042 public class Buffer {
00043
00047 public static final int MAX_LINES = 250;
00048
00052 public static final int MIN_LINE_HEIGHT = 8;
00053
00057 @NotNull
00058 private final EventListenerList2<BufferListener> listeners = new EventListenerList2<BufferListener>(BufferListener.class);
00059
00063 @NotNull
00064 private final Fonts fonts;
00065
00069 @NotNull
00070 private final FontRenderContext context;
00071
00075 private int renderWidth;
00076
00080 @NotNull
00081 private final List<Line> lines = new CopyOnWriteArrayList<Line>();
00082
00086 private int totalHeight = 0;
00087
00092 @NotNull
00093 private final Object sync = new Object();
00094
00098 private int lastCount = 0;
00099
00103 @Nullable
00104 private Color lastColor = null;
00105
00109 @NotNull
00110 private String lastText = "";
00111
00118 public Buffer(@NotNull final Fonts fonts, @NotNull final FontRenderContext context, final int renderWidth) {
00119 this.fonts = fonts;
00120 this.context = context;
00121 this.renderWidth = renderWidth;
00122 }
00123
00128 public void setRenderWidth(final int renderWidth) {
00129 if (this.renderWidth == renderWidth) {
00130 return;
00131 }
00132
00133 this.renderWidth = renderWidth;
00134
00135 synchronized (sync) {
00136 totalHeight = 0;
00137 for (final Line line : lines) {
00138 final int height = calculateHeight(line);
00139 line.setHeight(height);
00140 totalHeight += height;
00141 }
00142 }
00143 }
00144
00148 public void clear() {
00149 final List<Line> removedLines;
00150 synchronized (sync) {
00151 removedLines = new ArrayList<Line>(lines);
00152 totalHeight = 0;
00153 lines.clear();
00154 }
00155 for (final BufferListener listener : listeners.getListeners()) {
00156 listener.linesRemoved(removedLines);
00157 }
00158 lastCount = 0;
00159 lastText = "";
00160 lastColor = null;
00161 }
00162
00167 public void addLine(@NotNull final Line line) {
00168 final int height = calculateHeight(line);
00169 line.setHeight(height);
00170 synchronized (sync) {
00171 totalHeight += height;
00172 lines.add(line);
00173 }
00174
00175 for (final BufferListener listener : listeners.getListeners()) {
00176 listener.lineAdded();
00177 }
00178 }
00179
00184 public void replaceLine(@NotNull final Line line) {
00185 final int height = calculateHeight(line);
00186 line.setHeight(height);
00187 synchronized (sync) {
00188 totalHeight += height;
00189 final int lastIndex = lines.size()-1;
00190 totalHeight -= lines.get(lastIndex).getHeight();
00191 lines.set(lastIndex, line);
00192 }
00193
00194 for (final BufferListener listener : listeners.getListeners()) {
00195 listener.lineReplaced();
00196 }
00197 }
00198
00202 public void prune() {
00203 final List<Line> removedLines;
00204 synchronized (sync) {
00205 if (lines.size() <= MAX_LINES) {
00206 return;
00207 }
00208
00209 removedLines = new ArrayList<Line>(lines.size()-MAX_LINES);
00210 while (lines.size() > MAX_LINES) {
00211 final Line line = lines.remove(0);
00212 removedLines.add(line);
00213 totalHeight -= line.getHeight();
00214 }
00215 }
00216 for (final BufferListener listener : listeners.getListeners()) {
00217 listener.linesRemoved(removedLines);
00218 }
00219 }
00220
00227 @NotNull
00228 public Line getLine(final int line) {
00229 synchronized (sync) {
00230 return lines.get(line);
00231 }
00232 }
00233
00238 public int getTotalHeight() {
00239 synchronized (sync) {
00240 return Math.max(totalHeight, 1);
00241 }
00242 }
00243
00249 @NotNull
00250 public Iterator<Line> iterator() {
00251 assert Thread.holdsLock(sync);
00252 return Collections.unmodifiableList(lines).iterator();
00253 }
00254
00260 @NotNull
00261 public ListIterator<Line> listIterator(final int line) {
00262 assert Thread.holdsLock(sync);
00263 return Collections.unmodifiableList(lines).listIterator(line);
00264 }
00265
00270 public int size() {
00271 synchronized (sync) {
00272 return lines.size();
00273 }
00274 }
00275
00281 private int calculateHeight(@NotNull final Line line) {
00282 int height = 0;
00283 int x = 0;
00284 int minY = 0;
00285 int maxY = 0;
00286 int beginIndex = 0;
00287 int i = 0;
00288 for (final Segment segment : line) {
00289 final RectangularShape rectangle = segment.getSize(fonts, context);
00290 final int width = (int)Math.round(rectangle.getWidth());
00291 if (x != 0 && x+width > renderWidth) {
00292 line.updateAttributes(beginIndex, i, height-minY, fonts, context);
00293
00294 height += maxY-minY;
00295 x = 0;
00296 minY = 0;
00297 maxY = 0;
00298 beginIndex = i;
00299 }
00300
00301 segment.setX(x);
00302 segment.setY(height);
00303 segment.setWidth(width);
00304
00305 x += width;
00306 minY = (int)Math.min(minY, Math.round(rectangle.getY()));
00307 maxY = (int)Math.max(maxY, Math.round(rectangle.getY()+rectangle.getHeight()));
00308
00309 i++;
00310 }
00311
00312 line.updateAttributes(beginIndex, i, height-minY, fonts, context);
00313 height += maxY-minY;
00314
00315 return Math.max(MIN_LINE_HEIGHT, height);
00316 }
00317
00322 public void addBufferListener(@NotNull final BufferListener listener) {
00323 listeners.add(listener);
00324 }
00325
00330 public void removeBufferListener(@NotNull final BufferListener listener) {
00331 listeners.remove(listener);
00332 }
00333
00339 @NotNull
00340 public Object getSyncObject() {
00341 return sync;
00342 }
00343
00350 public boolean mergeLines(@NotNull final String text, @Nullable final Color color) {
00351 if (lastCount > 0 && text.equals(lastText) && (lastColor == null ? color == null : lastColor.equals(color))) {
00352 lastCount++;
00353 return true;
00354 }
00355
00356 lastCount = 1;
00357 lastText = text;
00358 lastColor = color;
00359 return false;
00360
00361 }
00362
00368 public int getLastCount() {
00369 return lastCount;
00370 }
00371
00372 }