xref: /haiku/src/kits/interface/textview_support/TextGapBuffer.cpp (revision aa3083e086e5a929c061c72983e09d916c548a38)
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 (stefano.ceccherini@gmail.com)
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 bool
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 false;
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 false;
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 	return true;
111 }
112 
113 
114 void
115 TextGapBuffer::RemoveRange(int32 start, int32 end)
116 {
117 	int32 inAtIndex = start;
118 	int32 inNumItems = end - start;
119 
120 	if (inNumItems < 1)
121 		return;
122 
123 	inAtIndex = (inAtIndex > fItemCount - 1) ? (fItemCount - 1) : inAtIndex;
124 	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
125 
126 	_MoveGapTo(inAtIndex);
127 
128 	fGapCount += inNumItems;
129 	fItemCount -= inNumItems;
130 
131 	if (fGapCount > kTextGapBufferBlockSize)
132 		_ShrinkGapTo(kTextGapBufferBlockSize / 2);
133 }
134 
135 
136 const char*
137 TextGapBuffer::GetString(int32 fromOffset, int32* _numBytes)
138 {
139 	const char* result = "";
140 	if (_numBytes == NULL)
141 		return result;
142 
143 	int32 numBytes = *_numBytes;
144 	if (numBytes < 1)
145 		return result;
146 
147 	bool isStartBeforeGap = fromOffset < fGapIndex;
148 	bool isEndBeforeGap = (fromOffset + numBytes - 1) < fGapIndex;
149 
150 	if (isStartBeforeGap == isEndBeforeGap) {
151 		result = fBuffer + fromOffset;
152 		if (!isStartBeforeGap)
153 			result += fGapCount;
154 	} else {
155 		if (fScratchSize < numBytes) {
156 			fScratchBuffer = (char*)realloc(fScratchBuffer, numBytes);
157 			fScratchSize = numBytes;
158 		}
159 
160 		for (int32 i = 0; i < numBytes; i++)
161 			fScratchBuffer[i] = RealCharAt(fromOffset + i);
162 
163 		result = fScratchBuffer;
164 	}
165 
166 	// TODO: this could be improved. We are overwriting what we did some lines
167 	// ago, we could just avoid to do that.
168 	if (fPasswordMode) {
169 		uint32 numChars = UTF8CountChars(result, numBytes);
170 		uint32 charLen = UTF8CountBytes(B_UTF8_BULLET, 1);
171 		uint32 newSize = numChars * charLen;
172 
173 		if ((uint32)fScratchSize < newSize) {
174 			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
175 			fScratchSize = newSize;
176 		}
177 		result = fScratchBuffer;
178 
179 		char* scratchPtr = fScratchBuffer;
180 		for (uint32 i = 0; i < numChars; i++) {
181 			memcpy(scratchPtr, B_UTF8_BULLET, charLen);
182 			scratchPtr += charLen;
183 		}
184 
185 		*_numBytes = newSize;
186 	}
187 
188 	return result;
189 }
190 
191 
192 bool
193 TextGapBuffer::FindChar(char inChar, int32 fromIndex, int32* ioDelta)
194 {
195 	int32 numChars = *ioDelta;
196 	for (int32 i = 0; i < numChars; i++) {
197 		char realChar = RealCharAt(fromIndex + i);
198 		if ((realChar & 0xc0) == 0x80)
199 			continue;
200 		if (realChar == inChar) {
201 			*ioDelta = i;
202 			return true;
203 		}
204 	}
205 
206 	return false;
207 }
208 
209 
210 const char*
211 TextGapBuffer::Text()
212 {
213 	const char* realText = RealText();
214 
215 	if (fPasswordMode) {
216 		const uint32 numChars = UTF8CountChars(realText, Length());
217 		const uint32 bulletCharLen = UTF8CountBytes(B_UTF8_BULLET, 1);
218 		uint32 newSize = numChars * bulletCharLen + 1;
219 
220 		if ((uint32)fScratchSize < newSize) {
221 			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
222 			fScratchSize = newSize;
223 		}
224 
225 		char* scratchPtr = fScratchBuffer;
226 		for (uint32 i = 0; i < numChars; i++) {
227 			memcpy(scratchPtr, B_UTF8_BULLET, bulletCharLen);
228 			scratchPtr += bulletCharLen;
229 		}
230 		*scratchPtr = '\0';
231 
232 		return fScratchBuffer;
233 	}
234 
235 	return realText;
236 }
237 
238 
239 const char*
240 TextGapBuffer::RealText()
241 {
242 	_MoveGapTo(fItemCount);
243 
244 	if (fGapCount == 0)
245 		_EnlargeGapTo(kTextGapBufferBlockSize);
246 
247 	fBuffer[fItemCount] = '\0';
248 	return fBuffer;
249 }
250 
251 
252 void
253 TextGapBuffer::GetString(int32 offset, int32 length, char* buffer)
254 {
255 	if (buffer == NULL)
256 		return;
257 
258 	int32 textLen = Length();
259 
260 	if (offset < 0 || offset > (textLen - 1) || length < 1) {
261 		buffer[0] = '\0';
262 		return;
263 	}
264 
265 	length = ((offset + length) > textLen) ? textLen - offset : length;
266 
267 	bool isStartBeforeGap = (offset < fGapIndex);
268 	bool isEndBeforeGap = ((offset + length - 1) < fGapIndex);
269 
270 	if (isStartBeforeGap == isEndBeforeGap) {
271 		char* source = fBuffer + offset;
272 		if (!isStartBeforeGap)
273 			source += fGapCount;
274 
275 		memcpy(buffer, source, length);
276 
277 	} else {
278 		// if we are here, it can only be that start is before gap,
279 		// and the end is after gap.
280 
281 		int32 beforeLen = fGapIndex - offset;
282 		int32 afterLen = length - beforeLen;
283 
284 		memcpy(buffer, fBuffer + offset, beforeLen);
285 		memcpy(buffer + beforeLen, fBuffer + fGapIndex + fGapCount, afterLen);
286 
287 	}
288 
289 	buffer[length] = '\0';
290 }
291 
292 
293 bool
294 TextGapBuffer::PasswordMode() const
295 {
296 	return fPasswordMode;
297 }
298 
299 
300 void
301 TextGapBuffer::SetPasswordMode(bool state)
302 {
303 	fPasswordMode = state;
304 }
305 
306 
307 void
308 TextGapBuffer::_MoveGapTo(int32 toIndex)
309 {
310 	if (toIndex == fGapIndex)
311 		return;
312 	if (toIndex > fItemCount) {
313 		debugger("MoveGapTo: invalid toIndex supplied");
314 		return;
315 	}
316 
317 	int32 srcIndex = 0;
318 	int32 dstIndex = 0;
319 	int32 count = 0;
320 	if (toIndex > fGapIndex) {
321 		srcIndex = fGapIndex + fGapCount;
322 		dstIndex = fGapIndex;
323 		count = toIndex - fGapIndex;
324 	} else {
325 		srcIndex = toIndex;
326 		dstIndex = toIndex + fGapCount;
327 		count = fGapIndex- toIndex;
328 	}
329 
330 	if (count > 0)
331 		memmove(fBuffer + dstIndex, fBuffer + srcIndex, count);
332 
333 	fGapIndex = toIndex;
334 }
335 
336 
337 void
338 TextGapBuffer::_EnlargeGapTo(int32 inCount)
339 {
340 	if (inCount == fGapCount)
341 		return;
342 
343 	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
344 	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
345 		fBufferCount - (fGapIndex + fGapCount));
346 
347 	fGapCount = inCount;
348 	fBufferCount = fItemCount + fGapCount;
349 }
350 
351 
352 void
353 TextGapBuffer::_ShrinkGapTo(int32 inCount)
354 {
355 	if (inCount == fGapCount)
356 		return;
357 
358 	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
359 		fBufferCount - (fGapIndex + fGapCount));
360 	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
361 
362 	fGapCount = inCount;
363 	fBufferCount = fItemCount + fGapCount;
364 }
365 
366 
367 } // namespace BPrivate
368