1
2
3
4
5
6 package io.github.olyutorskii.quetexj;
7
8 import java.awt.EventQueue;
9 import java.awt.Point;
10 import java.awt.Rectangle;
11 import java.awt.event.ComponentAdapter;
12 import java.awt.event.ComponentEvent;
13 import java.util.Objects;
14 import javax.swing.BoundedRangeModel;
15 import javax.swing.JTextArea;
16 import javax.swing.SwingConstants;
17 import javax.swing.text.BadLocationException;
18 import javax.swing.text.Document;
19 import javax.swing.text.JTextComponent;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 public class HeightKeeper {
36
37
38 public static final int DEF_HEIGHTLIMIT = 3000;
39
40 public static final int DEF_NEWHEIGHT = 2500;
41
42 private static final Rectangle DMY_RECT = new Rectangle();
43
44
45 private final JTextArea textComp;
46
47 private final BoundedRangeModel rangeModel;
48
49 private int heightLimit;
50 private int newHeight;
51 private final Object condLock = new Object();
52
53 private final SizeWatcher watcher = new SizeWatcher();
54
55
56
57
58
59
60
61
62
63
64 public HeightKeeper(JTextArea textComp, BoundedRangeModel rangeModel) {
65 this(textComp, rangeModel, DEF_HEIGHTLIMIT, DEF_NEWHEIGHT);
66 return;
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public HeightKeeper(JTextArea textComp, BoundedRangeModel rangeModel,
84 int heightLimit, int newHeight) {
85 super();
86
87 Objects.requireNonNull(rangeModel);
88
89 if (newHeight <= 0) throw new IllegalArgumentException();
90 if (heightLimit <= newHeight) throw new IllegalArgumentException();
91
92 this.textComp = textComp;
93 this.textComp.addComponentListener(this.watcher);
94
95 this.heightLimit = heightLimit;
96 this.newHeight = newHeight;
97
98 this.rangeModel = rangeModel;
99
100 return;
101 }
102
103
104
105
106
107
108 public JTextComponent getTextComponent() {
109 return this.textComp;
110 }
111
112
113
114
115
116
117 public BoundedRangeModel getBoundedRangeModel() {
118 return this.rangeModel;
119 }
120
121
122
123
124
125
126 public int getHeightLimit() {
127 return this.heightLimit;
128 }
129
130
131
132
133
134
135 public int getNewHeight() {
136 return this.newHeight;
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151 public void setConditions(int heightLimitArg, int newHeightArg)
152 throws IllegalArgumentException {
153 if (newHeightArg <= 0) {
154 throw new IllegalArgumentException();
155 }
156 if (heightLimitArg <= newHeightArg) {
157 throw new IllegalArgumentException();
158 }
159
160 synchronized (this.condLock) {
161 this.heightLimit = heightLimitArg;
162 this.newHeight = newHeightArg;
163 }
164
165 if (EventQueue.isDispatchThread()) {
166 eventResized();
167 } else {
168 EventQueue.invokeLater(() -> {
169 eventResized();
170 });
171 }
172
173 return;
174 }
175
176
177
178
179
180
181 void eventResized() {
182 int condHeightLimit;
183 int condNewHeight;
184 synchronized (this.condLock) {
185 condHeightLimit = this.heightLimit;
186 condNewHeight = this.newHeight;
187 }
188
189 int compHeight = this.textComp.getHeight();
190 if (compHeight < condHeightLimit) return;
191
192 int chopHeight = compHeight - condNewHeight;
193 int oldRangeVal = this.rangeModel.getValue();
194
195 chopHeadHeightRowBounds(chopHeight);
196
197 adjustBoundedRangeModel(chopHeight, oldRangeVal);
198
199 return;
200 }
201
202
203
204
205
206
207
208
209
210
211 private void chopHeadHeightRowBounds(int chopRegionHeight) {
212 int docLastPos = chopHeightToLinedOffset(chopRegionHeight);
213 chopHeadHeightByDocPos(docLastPos);
214 return;
215 }
216
217
218
219
220
221
222
223
224 private int chopHeightToLinedOffset(int chopHeight) {
225 int chopWidth = this.textComp.getWidth();
226
227
228 Point edgePoint = new Point(chopWidth - 1, chopHeight - 1);
229
230 int docOffset = this.textComp.viewToModel(edgePoint);
231
232 return docOffset;
233 }
234
235
236
237
238
239
240
241
242
243
244 private void chopHeadHeightByDocPos(int docLastPos) {
245 if (docLastPos < 0) return;
246
247 Document document = this.textComp.getDocument();
248 int docLength = document.getLength();
249 if (docLength <= 0) return;
250
251 int regionLength = Integer.min(docLastPos + 1, docLength);
252
253 try {
254 document.remove(0, regionLength);
255 } catch (BadLocationException e) {
256 assert false;
257 }
258
259 return;
260 }
261
262
263
264
265
266
267
268
269 private int adjustBoundedRangeModel(int chopHeight, int oldRangeVal) {
270 int realChopHeight = getRealChopHeight(chopHeight);
271 int newRangeVal = oldRangeVal - realChopHeight;
272 this.rangeModel.setValue(newRangeVal);
273 return newRangeVal;
274 }
275
276
277
278
279
280
281
282
283
284 private int getRealChopHeight(int chopHeight) {
285 int insetsTop = this.textComp.getInsets().top;
286 int bodyHeight = chopHeight - insetsTop;
287 int rowHeight = getRowHeight();
288 int chopRows = bodyHeight / rowHeight + 1;
289
290 int realChopHeight = rowHeight * chopRows + insetsTop;
291
292 return realChopHeight;
293 }
294
295
296
297
298
299
300
301 private int getRowHeight() {
302 int result =
303 this.textComp.getScrollableUnitIncrement(
304 DMY_RECT, SwingConstants.VERTICAL, 0);
305 return result;
306 }
307
308
309
310
311
312 private class SizeWatcher extends ComponentAdapter {
313
314
315
316
317 SizeWatcher() {
318 super();
319 return;
320 }
321
322
323
324
325
326
327 @Override
328 public void componentResized(ComponentEvent ev) {
329 eventResized();
330 return;
331 }
332
333 }
334
335 }