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