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