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