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
37static const uint32 kMsgFindMode = 'FMde';
38static const uint32 kMsgStartFind = 'SFnd';
39
40
41class FindTextView : public BTextView {
42public:
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
62protected:
63	virtual	void			InsertText(const char* text, int32 length,
64								int32 offset, const text_run_array* runs);
65
66private:
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
78FindTextView::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
87void
88FindTextView::MakeFocus(bool state)
89{
90	BTextView::MakeFocus(state);
91
92	if (fScrollView != NULL)
93		fScrollView->SetBorderHighlighted(state);
94}
95
96
97void
98FindTextView::TargetedByScrollView(BScrollView* view)
99{
100	BTextView::TargetedByScrollView(view);
101	fScrollView = view;
102}
103
104
105void
106FindTextView::_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
149void
150FindTextView::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
174void
175FindTextView::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
237bool
238FindTextView::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
257void
258FindTextView::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
293void
294FindTextView::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
317void
318FindTextView::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
350status_t
351FindTextView::_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
370status_t
371FindTextView::_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
409status_t
410FindTextView::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
446void
447FindTextView::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
468void
469FindTextView::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
488FindWindow::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->SetViewColor(ui_color(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
571FindWindow::~FindWindow()
572{
573}
574
575
576void
577FindWindow::WindowActivated(bool active)
578{
579	fTextView->MakeFocus(active);
580}
581
582
583void
584FindWindow::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
620bool
621FindWindow::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
634void
635FindWindow::SetTarget(BMessenger& target)
636{
637	fTarget = target;
638}
639
640