xref: /haiku/src/apps/diskprobe/FindWindow.cpp (revision db10640de90f7f9519ba2da9577b7c1af3c64f6b)
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 			{
178 				int32 start, end;
179 				GetSelection(&start, &end);
180 
181 				if (bytes[0] == B_BACKSPACE) {
182 					if (--start < 0 && end == 0)
183 						return;
184 
185 					start = 0;
186 				}
187 
188 				if (Text()[start] == ' ')
189 					BTextView::KeyDown(bytes, numBytes);
190 
191 				BTextView::KeyDown(bytes, numBytes);
192 
193 				GetSelection(&start, &end);
194 				HexReformat(start, start);
195 				Select(start, start);
196 				return;
197 			}
198 
199 			case B_DELETE:
200 			{
201 				int32 start, end;
202 				GetSelection(&start, &end);
203 
204 				if (Text()[start] == ' ')
205 					BTextView::KeyDown(bytes, numBytes);
206 
207 				BTextView::KeyDown(bytes, numBytes);
208 
209 				HexReformat(start, start);
210 				Select(start, start);
211 				return;
212 			}
213 
214 			default:
215 			{
216 				if (!strchr("0123456789abcdefABCDEF", bytes[0]))
217 					return;
218 
219 				// the original KeyDown() has severe cursor setting
220 				// problems with our InsertText().
221 
222 				int32 start, end;
223 				GetSelection(&start, &end);
224 				InsertText(bytes, 1, start, NULL);
225 				return;
226 			}
227 		}
228 	}
229 	BTextView::KeyDown(bytes, numBytes);
230 }
231 
232 
233 status_t
234 FindTextView::GetHexFromData(const uint8 *in, size_t inSize, char **_hex, size_t *_hexSize)
235 {
236 	char *hex = (char *)malloc(inSize * 3 + 1);
237 	if (hex == NULL)
238 		return B_NO_MEMORY;
239 
240 	char *out = hex;
241 	for (uint32 i = 0; i < inSize; i++) {
242 		out += sprintf(out, "%02x", *(unsigned char *)(in + i));
243 	}
244 	out[0] = '\0';
245 
246 	*_hex = hex;
247 	*_hexSize = out + 1 - hex;
248 	return B_OK;
249 }
250 
251 
252 status_t
253 FindTextView::GetDataFromHex(const char *text, size_t textLength, uint8 **_data, size_t *_dataSize)
254 {
255 	uint8 *data = (uint8 *)malloc(textLength);
256 	if (data == NULL)
257 		return B_NO_MEMORY;
258 
259 	size_t dataSize = 0;
260 	uint8 hiByte = 0;
261 	bool odd = false;
262 	for (uint32 i = 0; i < textLength; i++) {
263 		char c = text[i];
264 		int32 number;
265 		if (c >= 'A' && c <= 'F')
266 			number = c + 10 - 'A';
267 		else if (c >= 'a' && c <= 'f')
268 			number = c + 10 - 'a';
269 		else if (c >= '0' && c <= '9')
270 			number = c - '0';
271 		else
272 			continue;
273 
274 		if (!odd)
275 			hiByte = (number << 4) & 0xf0;
276 		else
277 			data[dataSize++] = hiByte | (number & 0x0f);
278 
279 		odd = !odd;
280 	}
281 	if (odd)
282 		data[dataSize++] = hiByte;
283 
284 	*_data = data;
285 	*_dataSize = dataSize;
286 	return B_OK;
287 }
288 
289 
290 status_t
291 FindTextView::SetMode(find_mode mode)
292 {
293 	if (fMode == mode)
294 		return B_OK;
295 
296 	if (mode == kHexMode) {
297 		// convert text to hex mode
298 
299 		char *hex;
300 		size_t hexSize;
301 		if (GetHexFromData((const uint8 *)Text(), TextLength(), &hex, &hexSize) < B_OK)
302 			return B_NO_MEMORY;
303 
304 		fMode = mode;
305 
306 		SetText(hex, hexSize);
307 		free(hex);
308 	} else {
309 		// convert hex to ascii
310 
311 		uint8 *data;
312 		size_t dataSize;
313 		if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) < B_OK)
314 			return B_NO_MEMORY;
315 
316 		fMode = mode;
317 
318 		SetText((const char *)data, dataSize);
319 		free(data);
320 	}
321 
322 	return B_OK;
323 }
324 
325 
326 void
327 FindTextView::SetData(BMessage &message)
328 {
329 	const uint8 *data;
330 	ssize_t dataSize;
331 	if (message.FindData("data", B_RAW_TYPE, (const void **)&data, &dataSize) != B_OK)
332 		return;
333 
334 	if (fMode == kHexMode) {
335 		char *hex;
336 		size_t hexSize;
337 		if (GetHexFromData(data, dataSize, &hex, &hexSize) < B_OK)
338 			return;
339 
340 		SetText(hex, hexSize);
341 		free(hex);
342 	} else
343 		SetText((char *)data, dataSize);
344 }
345 
346 
347 void
348 FindTextView::GetData(BMessage &message)
349 {
350 	if (fMode == kHexMode) {
351 		// convert hex-text to real data
352 		uint8 *data;
353 		size_t dataSize;
354 		if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) != B_OK)
355 			return;
356 
357 		message.AddData("data", B_RAW_TYPE, data, dataSize);
358 		free(data);
359 	} else
360 		message.AddData("data", B_RAW_TYPE, Text(), TextLength());
361 }
362 
363 
364 //	#pragma mark -
365 
366 
367 FindWindow::FindWindow(BRect rect, BMessage &previous, BMessenger &target,
368 	const BMessage *settings)
369 	: BWindow(rect, "Find", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS),
370 	fTarget(target)
371 {
372 	BView *view = new BView(Bounds(), "main", B_FOLLOW_ALL, 0);
373 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
374 	AddChild(view);
375 
376 	int8 mode = kAsciiMode;
377 	if (previous.FindInt8("find_mode", &mode) != B_OK && settings != NULL)
378 		settings->FindInt8("find_mode", &mode);
379 
380 	// add the top widgets
381 
382 	fMenu = new BPopUpMenu("mode");
383 	BMessage *message;
384 	BMenuItem *item;
385 	fMenu->AddItem(item = new BMenuItem("Text", message = new BMessage(kMsgFindMode)));
386 	message->AddInt8("mode", kAsciiMode);
387 	if (mode == kAsciiMode)
388 		item->SetMarked(true);
389 	fMenu->AddItem(item = new BMenuItem("Hexadecimal", message = new BMessage(kMsgFindMode)));
390 	message->AddInt8("mode", kHexMode);
391 	if (mode == kHexMode)
392 		item->SetMarked(true);
393 
394 	BRect rect = Bounds().InsetByCopy(5, 5);
395 	BMenuField *menuField = new BMenuField(rect, B_EMPTY_STRING,
396 						"Mode:", fMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP);
397 	menuField->SetDivider(menuField->StringWidth(menuField->Label()) + 8);
398 	menuField->ResizeToPreferred();
399 	view->AddChild(menuField);
400 
401 	// add the bottom widgets
402 
403 	BButton *button = new BButton(rect, B_EMPTY_STRING, "Find", new BMessage(kMsgStartFind),
404 								B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
405 	button->MakeDefault(true);
406 	button->ResizeToPreferred();
407 	button->MoveTo(rect.right - button->Bounds().Width(),
408 				rect.bottom - button->Bounds().Height());
409 	view->AddChild(button);
410 
411 	fCaseCheckBox = new BCheckBox(rect, B_EMPTY_STRING, "Case sensitive",
412 							NULL, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
413 	fCaseCheckBox->ResizeToPreferred();
414 	fCaseCheckBox->MoveTo(5, button->Frame().top);
415 	bool caseSensitive;
416 	if (previous.FindBool("case_sensitive", &caseSensitive) != B_OK) {
417 		if (settings == NULL || settings->FindBool("case_sensitive", &caseSensitive) != B_OK)
418 			caseSensitive = true;
419 	}
420 	fCaseCheckBox->SetValue(caseSensitive);
421 	view->AddChild(fCaseCheckBox);
422 
423 	// and now those inbetween
424 
425 	rect.top = menuField->Frame().bottom + 5;
426 	rect.bottom = fCaseCheckBox->Frame().top - 8;
427 	rect.InsetBy(2, 2);
428 	fTextView = new FindTextView(rect, B_EMPTY_STRING,
429 						rect.OffsetToCopy(B_ORIGIN).InsetByCopy(3, 3),
430 						B_FOLLOW_ALL);
431 	fTextView->SetWordWrap(true);
432 	fTextView->SetMode((find_mode)mode);
433 	fTextView->SetData(previous);
434 
435 	BScrollView *scrollView = new BScrollView("scroller", fTextView, B_FOLLOW_ALL, B_WILL_DRAW, false, false);
436 	view->AddChild(scrollView);
437 
438 	ResizeTo(290, button->Frame().Height() * 3 + 30);
439 
440 	SetSizeLimits(fCaseCheckBox->Bounds().Width() + button->Bounds().Width() + 20,
441 		32768, button->Frame().Height() * 3 + 10, 32768);
442 }
443 
444 
445 FindWindow::~FindWindow()
446 {
447 }
448 
449 
450 void
451 FindWindow::WindowActivated(bool active)
452 {
453 	fTextView->MakeFocus(active);
454 }
455 
456 
457 void
458 FindWindow::MessageReceived(BMessage *message)
459 {
460 	switch (message->what) {
461 		case kMsgFindMode:
462 		{
463 			int8 mode;
464 			if (message->FindInt8("mode", &mode) != B_OK)
465 				break;
466 
467 			if (fTextView->SetMode((find_mode)mode) != B_OK) {
468 				// activate other item
469 				fMenu->ItemAt(mode == kAsciiMode ? 1 : 0)->SetMarked(true);
470 				beep();
471 			}
472 			fTextView->MakeFocus(true);
473 			break;
474 		}
475 
476 		case kMsgStartFind:
477 		{
478 			BMessage find(kMsgFind);
479 			fTextView->GetData(find);
480 			find.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
481 			find.AddInt8("find_mode", fTextView->Mode());
482 			fTarget.SendMessage(&find);
483 
484 			PostMessage(B_QUIT_REQUESTED);
485 			break;
486 		}
487 
488 		default:
489 			BWindow::MessageReceived(message);
490 	}
491 }
492 
493 
494 bool
495 FindWindow::QuitRequested()
496 {
497 	// update the application's settings
498 	BMessage update(kMsgSettingsChanged);
499 	update.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
500 	update.AddInt8("find_mode", fTextView->Mode());
501 	be_app_messenger.SendMessage(&update);
502 
503 	be_app_messenger.SendMessage(kMsgFindWindowClosed);
504 	return true;
505 }
506 
507 
508 void
509 FindWindow::SetTarget(BMessenger &target)
510 {
511 	fTarget = target;
512 }
513 
514