xref: /haiku/src/kits/interface/textview_support/TextGapBuffer.cpp (revision a085e81e62d7a860f809b4fb7c7bf5654c396985)
1 /*
2  * Copyright 2001-2006, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (burton666@libero.it)
8  */
9 
10 
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cstring>
14 
15 #include <utf8_functions.h>
16 
17 #include <File.h>
18 #include <InterfaceDefs.h> // for B_UTF8_BULLET
19 
20 #include "TextGapBuffer.h"
21 
22 
23 namespace BPrivate {
24 
25 
26 static const int32 kTextGapBufferBlockSize = 2048;
27 
28 
29 TextGapBuffer::TextGapBuffer()
30 	:
31 	fItemCount(0),
32 	fBuffer(NULL),
33 	fBufferCount(kTextGapBufferBlockSize + fItemCount),
34 	fGapIndex(fItemCount),
35 	fGapCount(fBufferCount - fGapIndex),
36 	fScratchBuffer(NULL),
37 	fScratchSize(0),
38 	fPasswordMode(false)
39 {
40 	fBuffer = (char*)malloc(kTextGapBufferBlockSize + fItemCount);
41 	fScratchBuffer = NULL;
42 }
43 
44 
45 TextGapBuffer::~TextGapBuffer()
46 {
47 	free(fBuffer);
48 	free(fScratchBuffer);
49 }
50 
51 
52 void
53 TextGapBuffer::InsertText(const char* inText, int32 inNumItems, int32 inAtIndex)
54 {
55 	if (inNumItems < 1)
56 		return;
57 
58 	inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
59 	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
60 
61 	if (inAtIndex != fGapIndex)
62 		_MoveGapTo(inAtIndex);
63 
64 	if (fGapCount < inNumItems)
65 		_EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);
66 
67 	memcpy(fBuffer + fGapIndex, inText, inNumItems);
68 
69 	fGapCount -= inNumItems;
70 	fGapIndex += inNumItems;
71 	fItemCount += inNumItems;
72 }
73 
74 
75 void
76 TextGapBuffer::InsertText(BFile* file, int32 fileOffset, int32 inNumItems,
77 	int32 inAtIndex)
78 {
79 	off_t fileSize;
80 
81 	if (file->GetSize(&fileSize) != B_OK
82 		|| !file->IsReadable())
83 		return;
84 
85 	// Clamp the text length to the file size
86 	fileSize -= fileOffset;
87 
88 	if (fileSize < inNumItems)
89 		inNumItems = fileSize;
90 
91 	if (inNumItems < 1)
92 		return;
93 
94 	inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
95 	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
96 
97 	if (inAtIndex != fGapIndex)
98 		_MoveGapTo(inAtIndex);
99 
100 	if (fGapCount < inNumItems)
101 		_EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);
102 
103 	// Finally, read the data and put it into the buffer
104 	if (file->ReadAt(fileOffset, fBuffer + fGapIndex, inNumItems) > 0) {
105 		fGapCount -= inNumItems;
106 		fGapIndex += inNumItems;
107 		fItemCount += inNumItems;
108 	}
109 }
110 
111 
112 void
113 TextGapBuffer::RemoveRange(int32 start, int32 end)
114 {
115 	long inAtIndex = start;
116 	long inNumItems = end - start;
117 
118 	if (inNumItems < 1)
119 		return;
120 
121 	inAtIndex = (inAtIndex > fItemCount - 1) ? (fItemCount - 1) : inAtIndex;
122 	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
123 
124 	_MoveGapTo(inAtIndex);
125 
126 	fGapCount += inNumItems;
127 	fItemCount -= inNumItems;
128 
129 	if (fGapCount > kTextGapBufferBlockSize)
130 		_ShrinkGapTo(kTextGapBufferBlockSize / 2);
131 }
132 
133 
134 const char*
135 TextGapBuffer::GetString(int32 fromOffset, int32* _numBytes)
136 {
137 	const char* result = "";
138 	if (_numBytes == NULL)
139 		return result;
140 
141 	int32 numBytes = *_numBytes;
142 	if (numBytes < 1)
143 		return result;
144 
145 	bool isStartBeforeGap = (fromOffset < fGapIndex);
146 	bool isEndBeforeGap = ((fromOffset + numBytes - 1) < fGapIndex);
147 
148 	if (isStartBeforeGap == isEndBeforeGap) {
149 		result = fBuffer + fromOffset;
150 		if (!isStartBeforeGap)
151 			result += fGapCount;
152 	} else {
153 		if (fScratchSize < numBytes) {
154 			fScratchBuffer = (char*)realloc(fScratchBuffer, numBytes);
155 			fScratchSize = numBytes;
156 		}
157 
158 		for (long i = 0; i < numBytes; i++)
159 			fScratchBuffer[i] = RealCharAt(fromOffset + i);
160 
161 		result = fScratchBuffer;
162 	}
163 
164 	// TODO: this could be improved. We are overwriting what we did some lines
165 	// ago, we could just avoid to do that.
166 	if (fPasswordMode) {
167 		uint32 numChars = UTF8CountChars(result, numBytes);
168 		uint32 charLen = UTF8CountBytes(B_UTF8_BULLET, 1);
169 		uint32 newSize = numChars * charLen;
170 
171 		if ((uint32)fScratchSize < newSize) {
172 			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
173 			fScratchSize = newSize;
174 		}
175 		result = fScratchBuffer;
176 
177 		char* scratchPtr = fScratchBuffer;
178 		for (uint32 i = 0; i < numChars; i++) {
179 			memcpy(scratchPtr, B_UTF8_BULLET, charLen);
180 			scratchPtr += charLen;
181 		}
182 
183 		*_numBytes = newSize;
184 	}
185 
186 	return result;
187 }
188 
189 
190 bool
191 TextGapBuffer::FindChar(char inChar, int32 fromIndex, int32* ioDelta)
192 {
193 	long numChars = *ioDelta;
194 	for (long i = 0; i < numChars; i++) {
195 		char realChar = RealCharAt(fromIndex + i);
196 		if ((realChar & 0xc0) == 0x80)
197 			continue;
198 		if (realChar == inChar) {
199 			*ioDelta = i;
200 			return true;
201 		}
202 	}
203 
204 	return false;
205 }
206 
207 
208 const char*
209 TextGapBuffer::Text()
210 {
211 	const char* realText = RealText();
212 
213 	if (fPasswordMode) {
214 		const uint32 numChars = UTF8CountChars(realText, Length());
215 		const uint32 bulletCharLen = UTF8CountBytes(B_UTF8_BULLET, 1);
216 		uint32 newSize = numChars * bulletCharLen + 1;
217 
218 		if ((uint32)fScratchSize < newSize) {
219 			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
220 			fScratchSize = newSize;
221 		}
222 
223 		char* scratchPtr = fScratchBuffer;
224 		for (uint32 i = 0; i < numChars; i++) {
225 			memcpy(scratchPtr, B_UTF8_BULLET, bulletCharLen);
226 			scratchPtr += bulletCharLen;
227 		}
228 		scratchPtr = '\0';
229 
230 		return fScratchBuffer;
231 	}
232 
233 	return realText;
234 }
235 
236 
237 const char*
238 TextGapBuffer::RealText()
239 {
240 	_MoveGapTo(fItemCount);
241 
242 	if (fGapCount == 0)
243 		_EnlargeGapTo(kTextGapBufferBlockSize);
244 
245 	fBuffer[fItemCount] = '\0';
246 	return fBuffer;
247 }
248 
249 
250 void
251 TextGapBuffer::GetString(int32 offset, int32 length, char* buffer)
252 {
253 	if (buffer == NULL)
254 		return;
255 
256 	int32 textLen = Length();
257 
258 	if (offset < 0 || offset > (textLen - 1) || length < 1) {
259 		buffer[0] = '\0';
260 		return;
261 	}
262 
263 	length = ((offset + length) > textLen) ? textLen - offset : length;
264 
265 	bool isStartBeforeGap = (offset < fGapIndex);
266 	bool isEndBeforeGap = ((offset + length - 1) < fGapIndex);
267 
268 	if (isStartBeforeGap == isEndBeforeGap) {
269 		char* source = fBuffer + offset;
270 		if (!isStartBeforeGap)
271 			source += fGapCount;
272 
273 		memcpy(buffer, source, length);
274 
275 	} else {
276 		// if we are here, it can only be that start is before gap,
277 		// and the end is after gap.
278 
279 		int32 beforeLen = fGapIndex - offset;
280 		int32 afterLen = length - beforeLen;
281 
282 		memcpy(buffer, fBuffer + offset, beforeLen);
283 		memcpy(buffer + beforeLen, fBuffer + fGapIndex, afterLen);
284 
285 	}
286 
287 	buffer[length] = '\0';
288 }
289 
290 
291 bool
292 TextGapBuffer::PasswordMode() const
293 {
294 	return fPasswordMode;
295 }
296 
297 
298 void
299 TextGapBuffer::SetPasswordMode(bool state)
300 {
301 	fPasswordMode = state;
302 }
303 
304 
305 void
306 TextGapBuffer::_MoveGapTo(int32 toIndex)
307 {
308 	if (toIndex == fGapIndex)
309 		return;
310 	if (toIndex > fItemCount) {
311 		debugger("MoveGapTo: invalid toIndex supplied");
312 		return;
313 	}
314 
315 	int32 srcIndex = 0;
316 	int32 dstIndex = 0;
317 	int32 count = 0;
318 	if (toIndex > fGapIndex) {
319 		srcIndex = fGapIndex + fGapCount;
320 		dstIndex = fGapIndex;
321 		count = toIndex - fGapIndex;
322 	} else {
323 		srcIndex = toIndex;
324 		dstIndex = toIndex + fGapCount;
325 		count = fGapIndex- toIndex;
326 	}
327 
328 	if (count > 0)
329 		memmove(fBuffer + dstIndex, fBuffer + srcIndex, count);
330 
331 	fGapIndex = toIndex;
332 }
333 
334 
335 void
336 TextGapBuffer::_EnlargeGapTo(int32 inCount)
337 {
338 	if (inCount == fGapCount)
339 		return;
340 
341 	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
342 	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
343 		fBufferCount - (fGapIndex + fGapCount));
344 
345 	fGapCount = inCount;
346 	fBufferCount = fItemCount + fGapCount;
347 }
348 
349 
350 void
351 TextGapBuffer::_ShrinkGapTo(int32 inCount)
352 {
353 	if (inCount == fGapCount)
354 		return;
355 
356 	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
357 		fBufferCount - (fGapIndex + fGapCount));
358 	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
359 
360 	fGapCount = inCount;
361 	fBufferCount = fItemCount + fGapCount;
362 }
363 
364 
365 } // namespace BPrivate
366