xref: /haiku/src/apps/diskprobe/FindWindow.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2 ** Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 ** Distributed under the terms of the Haiku License.
4 */
5 
6 
7 #include "FindWindow.h"
8 #include "DiskProbe.h"
9 
10 #include <Application.h>
11 #include <TextView.h>
12 #include <MenuField.h>
13 #include <PopUpMenu.h>
14 #include <MenuItem.h>
15 #include <Button.h>
16 #include <ScrollView.h>
17 #include <CheckBox.h>
18 #include <Beep.h>
19 
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 
24 
25 static const uint32 kMsgFindMode = 'FMde';
26 static const uint32 kMsgStartFind = 'SFnd';
27 
28 
29 class FindTextView : public BTextView {
30 	public:
31 		FindTextView(BRect frame, const char *name, BRect textRect, uint32 resizeMask);
32 
33 		virtual void MakeFocus(bool state);
34 		virtual void TargetedByScrollView(BScrollView *view);
35 
36 		find_mode Mode() const { return fMode; }
37 		status_t SetMode(find_mode mode);
38 
39 		void SetData(BMessage &message);
40 		void GetData(BMessage &message);
41 
42 		virtual void KeyDown(const char *bytes, int32 numBytes);
43 
44 	protected:
45 		virtual	void InsertText(const char *text, int32 length,
46 						int32 offset, const text_run_array *runs);
47 
48 	private:
49 		void HexReformat(int32 oldCursor, int32 &newCursor);
50 		status_t GetHexFromData(const uint8 *in, size_t inSize, char **_hex, size_t *_hexSize);
51 		status_t GetDataFromHex(const char *text, size_t textLength, uint8 **_data, size_t *_dataSize);
52 
53 		BScrollView	*fScrollView;
54 		find_mode	fMode;
55 };
56 
57 
58 //---------------
59 
60 
61 FindTextView::FindTextView(BRect frame, const char *name, BRect textRect,
62 	uint32 resizeMask)
63 	: BTextView(frame, name, textRect, resizeMask),
64 	fScrollView(NULL),
65 	fMode(kAsciiMode)
66 {
67 }
68 
69 
70 void
71 FindTextView::MakeFocus(bool state)
72 {
73 	BTextView::MakeFocus(state);
74 
75 	if (fScrollView != NULL)
76 		fScrollView->SetBorderHighlighted(state);
77 }
78 
79 
80 void
81 FindTextView::TargetedByScrollView(BScrollView *view)
82 {
83 	BTextView::TargetedByScrollView(view);
84 	fScrollView = view;
85 }
86 
87 
88 void
89 FindTextView::HexReformat(int32 oldCursor, int32 &newCursor)
90 {
91 	const char *text = Text();
92 	int32 textLength = TextLength();
93 	char *insert = (char *)malloc(textLength * 2);
94 	if (insert == NULL)
95 		return;
96 
97 	newCursor = TextLength();
98 	int32 out = 0;
99 	for (int32 i = 0; i < textLength; i++) {
100 		if (i == oldCursor) {
101 			// this is the end of the inserted text
102 			newCursor = out;
103 		}
104 
105 		char c = text[i];
106 		if (c >= 'A' && c <= 'F')
107 			c += 'a' - 'A';
108 		if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))
109 			insert[out++] = c;
110 
111 		if ((out % 48) == 47)
112 			insert[out++] = '\n';
113 		else if ((out % 3) == 2)
114 			insert[out++] = ' ';
115 	}
116 	insert[out] = '\0';
117 
118 	DeleteText(0, textLength);
119 
120 	// InsertText() does not work here, as we need the text
121 	// to be reformatted as well (newlines, breaks, whatever).
122 	// IOW the BTextView class is not very nicely done.
123 	//	BTextView::InsertText(insert, out, 0, NULL);
124 	fMode = kAsciiMode;
125 	Insert(0, insert, out);
126 	fMode = kHexMode;
127 
128 	free(insert);
129 }
130 
131 
132 void
133 FindTextView::InsertText(const char *text, int32 length, int32 offset,
134 	const text_run_array *runs)
135 {
136 	if (fMode == kHexMode) {
137 		if (offset > TextLength())
138 			offset = TextLength();
139 
140 		BTextView::InsertText(text, length, offset, runs);
141 			// lets add anything, and then start to filter out
142 			// (since we have to reformat the whole text)
143 
144 		int32 start, end;
145 		GetSelection(&start, &end);
146 
147 		int32 cursor;
148 		HexReformat(offset, cursor);
149 
150 		if (length == 1 && start == offset)
151 			Select(cursor + 1, cursor + 1);
152 	} else
153 		BTextView::InsertText(text, length, offset, runs);
154 }
155 
156 
157 void
158 FindTextView::KeyDown(const char *bytes, int32 numBytes)
159 {
160 	if (fMode == kHexMode) {
161 		// filter out invalid (for hex mode) characters
162 		if (numBytes > 1)
163 			return;
164 
165 		switch (bytes[0]) {
166 			case B_RIGHT_ARROW:
167 			case B_LEFT_ARROW:
168 			case B_UP_ARROW:
169 			case B_DOWN_ARROW:
170 			case B_HOME:
171 			case B_END:
172 			case B_PAGE_UP:
173 			case B_PAGE_DOWN:
174 				break;
175 
176 			case B_BACKSPACE:
177 			case B_DELETE:
178 			{
179 				int32 start, end;
180 				GetSelection(&start, &end);
181 
182 				if (bytes[0] == B_BACKSPACE && --start < 0) {
183 					if (end == 0)
184 						return;
185 					start = 0;
186 				}
187 
188 				if (ByteAt(start) == ' ')
189 					BTextView::KeyDown(bytes, numBytes);
190 
191 				BTextView::KeyDown(bytes, numBytes);
192 
193 				if (bytes[0] == B_BACKSPACE)
194 					GetSelection(&start, &end);
195 
196 				HexReformat(start, start);
197 				Select(start, start);
198 				return;
199 			}
200 
201 			default:
202 			{
203 				if (!strchr("0123456789abcdefABCDEF", bytes[0]))
204 					return;
205 
206 				// the original KeyDown() has severe cursor setting
207 				// problems with our InsertText().
208 
209 				int32 start, end;
210 				GetSelection(&start, &end);
211 				InsertText(bytes, 1, start, NULL);
212 				return;
213 			}
214 		}
215 	}
216 	BTextView::KeyDown(bytes, numBytes);
217 }
218 
219 
220 status_t
221 FindTextView::GetHexFromData(const uint8 *in, size_t inSize, char **_hex, size_t *_hexSize)
222 {
223 	char *hex = (char *)malloc(inSize * 3 + 1);
224 	if (hex == NULL)
225 		return B_NO_MEMORY;
226 
227 	char *out = hex;
228 	for (uint32 i = 0; i < inSize; i++) {
229 		out += sprintf(out, "%02x", *(unsigned char *)(in + i));
230 	}
231 	out[0] = '\0';
232 
233 	*_hex = hex;
234 	*_hexSize = out + 1 - hex;
235 	return B_OK;
236 }
237 
238 
239 status_t
240 FindTextView::GetDataFromHex(const char *text, size_t textLength, uint8 **_data, size_t *_dataSize)
241 {
242 	uint8 *data = (uint8 *)malloc(textLength);
243 	if (data == NULL)
244 		return B_NO_MEMORY;
245 
246 	size_t dataSize = 0;
247 	uint8 hiByte = 0;
248 	bool odd = false;
249 	for (uint32 i = 0; i < textLength; i++) {
250 		char c = text[i];
251 		int32 number;
252 		if (c >= 'A' && c <= 'F')
253 			number = c + 10 - 'A';
254 		else if (c >= 'a' && c <= 'f')
255 			number = c + 10 - 'a';
256 		else if (c >= '0' && c <= '9')
257 			number = c - '0';
258 		else
259 			continue;
260 
261 		if (!odd)
262 			hiByte = (number << 4) & 0xf0;
263 		else
264 			data[dataSize++] = hiByte | (number & 0x0f);
265 
266 		odd = !odd;
267 	}
268 	if (odd)
269 		data[dataSize++] = hiByte;
270 
271 	*_data = data;
272 	*_dataSize = dataSize;
273 	return B_OK;
274 }
275 
276 
277 status_t
278 FindTextView::SetMode(find_mode mode)
279 {
280 	if (fMode == mode)
281 		return B_OK;
282 
283 	if (mode == kHexMode) {
284 		// convert text to hex mode
285 
286 		char *hex;
287 		size_t hexSize;
288 		if (GetHexFromData((const uint8 *)Text(), TextLength(), &hex, &hexSize) < B_OK)
289 			return B_NO_MEMORY;
290 
291 		fMode = mode;
292 
293 		SetText(hex, hexSize);
294 		free(hex);
295 	} else {
296 		// convert hex to ascii
297 
298 		uint8 *data;
299 		size_t dataSize;
300 		if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) < B_OK)
301 			return B_NO_MEMORY;
302 
303 		fMode = mode;
304 
305 		SetText((const char *)data, dataSize);
306 		free(data);
307 	}
308 
309 	return B_OK;
310 }
311 
312 
313 void
314 FindTextView::SetData(BMessage &message)
315 {
316 	const uint8 *data;
317 	ssize_t dataSize;
318 	if (message.FindData("data", B_RAW_TYPE, (const void **)&data, &dataSize) != B_OK)
319 		return;
320 
321 	if (fMode == kHexMode) {
322 		char *hex;
323 		size_t hexSize;
324 		if (GetHexFromData(data, dataSize, &hex, &hexSize) < B_OK)
325 			return;
326 
327 		SetText(hex, hexSize);
328 		free(hex);
329 	} else
330 		SetText((char *)data, dataSize);
331 }
332 
333 
334 void
335 FindTextView::GetData(BMessage &message)
336 {
337 	if (fMode == kHexMode) {
338 		// convert hex-text to real data
339 		uint8 *data;
340 		size_t dataSize;
341 		if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) != B_OK)
342 			return;
343 
344 		message.AddData("data", B_RAW_TYPE, data, dataSize);
345 		free(data);
346 	} else
347 		message.AddData("data", B_RAW_TYPE, Text(), TextLength());
348 }
349 
350 
351 //	#pragma mark -
352 
353 
354 FindWindow::FindWindow(BRect _rect, BMessage &previous, BMessenger &target,
355 	const BMessage *settings)
356 	: BWindow(_rect, "Find", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS),
357 	fTarget(target)
358 {
359 	BView *view = new BView(Bounds(), "main", B_FOLLOW_ALL, 0);
360 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
361 	AddChild(view);
362 
363 	int8 mode = kAsciiMode;
364 	if (previous.FindInt8("find_mode", &mode) != B_OK && settings != NULL)
365 		settings->FindInt8("find_mode", &mode);
366 
367 	// add the top widgets
368 
369 	fMenu = new BPopUpMenu("mode");
370 	BMessage *message;
371 	BMenuItem *item;
372 	fMenu->AddItem(item = new BMenuItem("Text", message = new BMessage(kMsgFindMode)));
373 	message->AddInt8("mode", kAsciiMode);
374 	if (mode == kAsciiMode)
375 		item->SetMarked(true);
376 	fMenu->AddItem(item = new BMenuItem("Hexadecimal", message = new BMessage(kMsgFindMode)));
377 	message->AddInt8("mode", kHexMode);
378 	if (mode == kHexMode)
379 		item->SetMarked(true);
380 
381 	BRect rect = Bounds().InsetByCopy(5, 5);
382 	BMenuField *menuField = new BMenuField(rect, B_EMPTY_STRING,
383 						"Mode:", fMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP);
384 	menuField->SetDivider(menuField->StringWidth(menuField->Label()) + 8);
385 	menuField->ResizeToPreferred();
386 	view->AddChild(menuField);
387 
388 	// add the bottom widgets
389 
390 	BButton *button = new BButton(rect, B_EMPTY_STRING, "Find", new BMessage(kMsgStartFind),
391 								B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
392 	button->MakeDefault(true);
393 	button->ResizeToPreferred();
394 	button->MoveTo(rect.right - button->Bounds().Width(),
395 				rect.bottom - button->Bounds().Height());
396 	view->AddChild(button);
397 
398 	fCaseCheckBox = new BCheckBox(rect, B_EMPTY_STRING, "Case sensitive",
399 							NULL, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
400 	fCaseCheckBox->ResizeToPreferred();
401 	fCaseCheckBox->MoveTo(5, button->Frame().top);
402 	bool caseSensitive;
403 	if (previous.FindBool("case_sensitive", &caseSensitive) != B_OK) {
404 		if (settings == NULL || settings->FindBool("case_sensitive", &caseSensitive) != B_OK)
405 			caseSensitive = true;
406 	}
407 	fCaseCheckBox->SetValue(caseSensitive);
408 	view->AddChild(fCaseCheckBox);
409 
410 	// and now those inbetween
411 
412 	rect.top = menuField->Frame().bottom + 5;
413 	rect.bottom = fCaseCheckBox->Frame().top - 8;
414 	rect.InsetBy(2, 2);
415 	fTextView = new FindTextView(rect, B_EMPTY_STRING,
416 						rect.OffsetToCopy(B_ORIGIN).InsetByCopy(3, 3),
417 						B_FOLLOW_ALL);
418 	fTextView->SetWordWrap(true);
419 	fTextView->SetMode((find_mode)mode);
420 	fTextView->SetData(previous);
421 
422 	BScrollView *scrollView = new BScrollView("scroller", fTextView, B_FOLLOW_ALL, B_WILL_DRAW, false, false);
423 	view->AddChild(scrollView);
424 
425 	ResizeTo(290, button->Frame().Height() * 3 + 30);
426 
427 	SetSizeLimits(fCaseCheckBox->Bounds().Width() + button->Bounds().Width() + 20,
428 		32768, button->Frame().Height() * 3 + 10, 32768);
429 }
430 
431 
432 FindWindow::~FindWindow()
433 {
434 }
435 
436 
437 void
438 FindWindow::WindowActivated(bool active)
439 {
440 	fTextView->MakeFocus(active);
441 }
442 
443 
444 void
445 FindWindow::MessageReceived(BMessage *message)
446 {
447 	switch (message->what) {
448 		case kMsgFindMode:
449 		{
450 			int8 mode;
451 			if (message->FindInt8("mode", &mode) != B_OK)
452 				break;
453 
454 			if (fTextView->SetMode((find_mode)mode) != B_OK) {
455 				// activate other item
456 				fMenu->ItemAt(mode == kAsciiMode ? 1 : 0)->SetMarked(true);
457 				beep();
458 			}
459 			fTextView->MakeFocus(true);
460 			break;
461 		}
462 
463 		case kMsgStartFind:
464 		{
465 			BMessage find(kMsgFind);
466 			fTextView->GetData(find);
467 			find.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
468 			find.AddInt8("find_mode", fTextView->Mode());
469 			fTarget.SendMessage(&find);
470 
471 			PostMessage(B_QUIT_REQUESTED);
472 			break;
473 		}
474 
475 		default:
476 			BWindow::MessageReceived(message);
477 	}
478 }
479 
480 
481 bool
482 FindWindow::QuitRequested()
483 {
484 	// update the application's settings
485 	BMessage update(kMsgSettingsChanged);
486 	update.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
487 	update.AddInt8("find_mode", fTextView->Mode());
488 	be_app_messenger.SendMessage(&update);
489 
490 	be_app_messenger.SendMessage(kMsgFindWindowClosed);
491 	return true;
492 }
493 
494 
495 void
496 FindWindow::SetTarget(BMessenger &target)
497 {
498 	fTarget = target;
499 }
500 
501