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