1/*
2 * Copyright 2004-2018, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "ProbeView.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <strings.h>
12
13#include <Alert.h>
14#include <Application.h>
15#include <Autolock.h>
16#include <Beep.h>
17#include <Bitmap.h>
18#include <Box.h>
19#include <Button.h>
20#include <Catalog.h>
21#include <ControlLook.h>
22#include <Clipboard.h>
23#include <Directory.h>
24#include <Entry.h>
25#include <ExpressionParser.h>
26#include <fs_attr.h>
27#include <GridView.h>
28#include <GroupLayout.h>
29#include <GroupLayoutBuilder.h>
30#include <GroupView.h>
31#include <LayoutBuilder.h>
32#include <Locale.h>
33#include <MenuBar.h>
34#include <MenuItem.h>
35#include <MessageQueue.h>
36#include <NodeInfo.h>
37#include <Node.h>
38#include <NodeMonitor.h>
39#include <Path.h>
40#include <PrintJob.h>
41#include <ScrollView.h>
42#include <StringView.h>
43#include <Slider.h>
44#include <String.h>
45#include <TextControl.h>
46#include <Volume.h>
47#include <Window.h>
48
49#include "DataView.h"
50#include "DiskProbe.h"
51#include "TypeEditors.h"
52
53
54#undef B_TRANSLATION_CONTEXT
55#define B_TRANSLATION_CONTEXT "ProbeView"
56
57static const uint32 kMsgSliderUpdate = 'slup';
58static const uint32 kMsgPositionUpdate = 'poup';
59static const uint32 kMsgLastPosition = 'lpos';
60static const uint32 kMsgFontSize = 'fnts';
61static const uint32 kMsgBlockSize = 'blks';
62static const uint32 kMsgAddBookmark = 'bmrk';
63static const uint32 kMsgPrint = 'prnt';
64static const uint32 kMsgPageSetup = 'pgsp';
65static const uint32 kMsgViewAs = 'vwas';
66
67static const uint32 kMsgStopFind = 'sfnd';
68
69
70class IconView : public BView {
71public:
72								IconView(const entry_ref* ref, bool isDevice);
73	virtual						~IconView();
74
75	virtual	void				AttachedToWindow();
76	virtual	void				Draw(BRect updateRect);
77
78			void				UpdateIcon();
79
80private:
81			entry_ref			fRef;
82			bool				fIsDevice;
83			BBitmap*			fBitmap;
84};
85
86
87class PositionSlider : public BSlider {
88public:
89								PositionSlider(const char* name,
90									BMessage* message, off_t size,
91									uint32 blockSize);
92	virtual						~PositionSlider();
93
94			off_t				Position() const;
95			off_t				Size() const { return fSize; }
96			uint32				BlockSize() const { return fBlockSize; }
97
98	virtual	void				SetPosition(float position);
99			void				SetPosition(off_t position);
100			void				SetSize(off_t size);
101			void				SetBlockSize(uint32 blockSize);
102
103private:
104			void				Reset();
105
106private:
107	static	const int32			kMaxSliderLimit = 0x7fffff80;
108		// this is the maximum value that BSlider seem to work with fine
109
110			off_t				fSize;
111			uint32				fBlockSize;
112};
113
114
115class HeaderView : public BGridView, public BInvoker {
116public:
117								HeaderView(const entry_ref* ref,
118									DataEditor& editor);
119	virtual						~HeaderView();
120
121	virtual	void				AttachedToWindow();
122	virtual	void				DetachedFromWindow();
123	virtual	void				MessageReceived(BMessage* message);
124
125			base_type			Base() const { return fBase; }
126			void				SetBase(base_type);
127
128			off_t				CursorOffset() const
129									{ return fPosition % fBlockSize; }
130			off_t				Position() const { return fPosition; }
131			uint32				BlockSize() const { return fBlockSize; }
132			void				SetTo(off_t position, uint32 blockSize);
133
134			void				UpdateIcon();
135
136private:
137			void				FormatValue(char* buffer, size_t bufferSize,
138									off_t value);
139			void				UpdatePositionViews(bool all = true);
140			void				UpdateOffsetViews(bool all = true);
141			void				UpdateFileSizeView();
142			void				NotifyTarget();
143
144private:
145			const char*			fAttribute;
146			off_t				fFileSize;
147			uint32				fBlockSize;
148			base_type			fBase;
149			off_t				fPosition;
150			off_t				fLastPosition;
151
152			BTextControl*		fTypeControl;
153			BTextControl*		fPositionControl;
154			BStringView*		fPathView;
155			BStringView*		fSizeView;
156			BTextControl*		fOffsetControl;
157			BTextControl*		fFileOffsetControl;
158			PositionSlider*		fPositionSlider;
159			IconView*			fIconView;
160			BButton*			fStopButton;
161};
162
163
164class TypeMenuItem : public BMenuItem {
165public:
166								TypeMenuItem(const char* name, const char* type,
167									BMessage* message);
168
169	virtual	void				GetContentSize(float* _width, float* _height);
170	virtual	void				DrawContent();
171
172private:
173			BString				fType;
174};
175
176
177class EditorLooper : public BLooper {
178public:
179								EditorLooper(const char* name,
180									DataEditor& editor, BMessenger messenger);
181	virtual						~EditorLooper();
182
183	virtual	void				MessageReceived(BMessage* message);
184
185			bool				FindIsRunning() const { return !fQuitFind; }
186			void				Find(off_t startAt, const uint8* data,
187									size_t dataSize, bool caseInsensitive,
188									BMessenger progressMonitor);
189			void				QuitFind();
190
191private:
192			DataEditor&			fEditor;
193			BMessenger			fMessenger;
194	volatile bool				fQuitFind;
195};
196
197
198class TypeView : public BView {
199public:
200								TypeView(BRect rect, const char* name,
201									int32 index, DataEditor& editor,
202									int32 resizingMode);
203	virtual						~TypeView();
204
205	virtual	void				FrameResized(float width, float height);
206
207private:
208			BView*				fTypeEditorView;
209};
210
211
212//	#pragma mark - utility functions
213
214
215static void
216get_type_string(char* buffer, size_t bufferSize, type_code type)
217{
218	for (int32 i = 0; i < 4; i++) {
219		buffer[i] = type >> (24 - 8 * i);
220		if (buffer[i] < ' ' || buffer[i] == 0x7f) {
221			snprintf(buffer, bufferSize, "0x%04" B_PRIx32, type);
222			break;
223		} else if (i == 3)
224			buffer[4] = '\0';
225	}
226}
227
228
229//	#pragma mark - IconView
230
231
232IconView::IconView(const entry_ref* ref, bool isDevice)
233	: BView(NULL, B_WILL_DRAW),
234	fRef(*ref),
235	fIsDevice(isDevice),
236	fBitmap(NULL)
237{
238	UpdateIcon();
239
240	if (fBitmap != NULL)
241		SetExplicitSize(fBitmap->Bounds().Size());
242}
243
244
245IconView::~IconView()
246{
247	delete fBitmap;
248}
249
250
251void
252IconView::AttachedToWindow()
253{
254	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
255}
256
257
258void
259IconView::Draw(BRect updateRect)
260{
261	if (fBitmap == NULL)
262		return;
263
264	SetDrawingMode(B_OP_ALPHA);
265	DrawBitmap(fBitmap, updateRect, updateRect);
266	SetDrawingMode(B_OP_COPY);
267}
268
269
270void
271IconView::UpdateIcon()
272{
273	if (fBitmap == NULL) {
274		fBitmap = new BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_LARGE_ICON)),
275			B_RGBA32);
276	}
277
278	if (fBitmap != NULL) {
279		status_t status = B_ERROR;
280
281		if (fIsDevice) {
282			BPath path(&fRef);
283			status = get_device_icon(path.Path(), fBitmap, B_LARGE_ICON);
284		} else {
285			status = BNodeInfo::GetTrackerIcon(&fRef, fBitmap,
286				(icon_size)(fBitmap->Bounds().IntegerWidth() + 1));
287		}
288
289		if (status != B_OK) {
290			// Try to get generic icon
291			BMimeType type(B_FILE_MIME_TYPE);
292			status = type.GetIcon(fBitmap, B_LARGE_ICON);
293		}
294
295		if (status != B_OK) {
296			delete fBitmap;
297			fBitmap = NULL;
298		}
299
300		Invalidate();
301	}
302}
303
304
305//	#pragma mark - PositionSlider
306
307
308PositionSlider::PositionSlider(const char* name, BMessage* message,
309	off_t size, uint32 blockSize)
310	:
311	BSlider(name, NULL, message, 0, kMaxSliderLimit, B_HORIZONTAL,
312		B_TRIANGLE_THUMB),
313	fSize(size),
314	fBlockSize(blockSize)
315{
316	Reset();
317
318	rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
319	UseFillColor(true, &color);
320}
321
322
323PositionSlider::~PositionSlider()
324{
325}
326
327
328void
329PositionSlider::Reset()
330{
331	SetKeyIncrementValue(int32(1.0 * kMaxSliderLimit / ((fSize - 1) / fBlockSize) + 0.5));
332	SetEnabled(fSize > fBlockSize);
333}
334
335
336off_t
337PositionSlider::Position() const
338{
339	// ToDo:
340	// Note: this code is far from being perfect: depending on the file size, it has
341	//	a maxium granularity that might be less than the actual block size demands...
342	//	The only way to work around this that I can think of, is to replace the slider
343	//	class completely with one that understands off_t values.
344	//	For example, with a block size of 512 bytes, it should be good enough for about
345	//	1024 GB - and that's not really that far away these days.
346
347	return (off_t(1.0 * (fSize - 1) * Value() / kMaxSliderLimit + 0.5) / fBlockSize) * fBlockSize;
348}
349
350
351void
352PositionSlider::SetPosition(float position)
353{
354	BSlider::SetPosition(position);
355}
356
357
358void
359PositionSlider::SetPosition(off_t position)
360{
361	position /= fBlockSize;
362	SetValue(int32(1.0 * kMaxSliderLimit * position / ((fSize - 1) / fBlockSize) + 0.5));
363}
364
365
366void
367PositionSlider::SetSize(off_t size)
368{
369	if (size == fSize)
370		return;
371
372	off_t position = Position();
373	if (position >= size)
374		position = size - 1;
375
376	fSize = size;
377	Reset();
378	SetPosition(position);
379}
380
381
382void
383PositionSlider::SetBlockSize(uint32 blockSize)
384{
385	if (blockSize == fBlockSize)
386		return;
387
388	off_t position = Position();
389	fBlockSize = blockSize;
390	Reset();
391	SetPosition(position);
392}
393
394
395//	#pragma mark - HeaderView
396
397
398HeaderView::HeaderView(const entry_ref* ref, DataEditor& editor)
399	: BGridView("probeHeader", B_USE_SMALL_SPACING, B_USE_SMALL_SPACING),
400	fAttribute(editor.Attribute()),
401	fFileSize(editor.FileSize()),
402	fBlockSize(editor.BlockSize()),
403	fBase(kHexBase),
404	fPosition(0),
405	fLastPosition(0)
406{
407	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
408	GridLayout()->SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
409		B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING);
410
411	fIconView = new IconView(ref, editor.IsDevice());
412	GridLayout()->AddView(fIconView, 0, 0, 1, 2);
413
414	BGroupView* line = new BGroupView(B_HORIZONTAL);
415	GridLayout()->AddView(line, 1, 0);
416
417	BFont boldFont = *be_bold_font;
418	BFont plainFont = *be_plain_font;
419	boldFont.SetSize(ceilf(plainFont.Size() * 0.83));
420	plainFont.SetSize(ceilf(plainFont.Size() * 0.83));
421
422	BStringView* stringView = new BStringView(
423		B_EMPTY_STRING, editor.IsAttribute()
424			? B_TRANSLATE("Attribute: ") : editor.IsDevice()
425			? B_TRANSLATE("Device: ") : B_TRANSLATE("File: "));
426	stringView->SetFont(&boldFont);
427	line->AddChild(stringView);
428
429	BPath path(ref);
430	BString string = path.Path();
431	if (fAttribute != NULL) {
432		string.Prepend(" (");
433		string.Prepend(fAttribute);
434		string.Append(")");
435	}
436	fPathView = new BStringView(B_EMPTY_STRING, string.String());
437	fPathView->SetFont(&plainFont);
438	line->AddChild(fPathView);
439
440	if (editor.IsAttribute()) {
441		stringView = new BStringView(B_EMPTY_STRING,
442			B_TRANSLATE("Attribute type: "));
443		stringView->SetFont(&boldFont);
444		line->AddChild(stringView);
445
446		char buffer[16];
447		get_type_string(buffer, sizeof(buffer), editor.Type());
448		fTypeControl = new BTextControl(B_EMPTY_STRING, NULL, buffer,
449			new BMessage(kMsgPositionUpdate));
450		fTypeControl->SetFont(&plainFont);
451		fTypeControl->TextView()->SetFontAndColor(&plainFont);
452		fTypeControl->SetEnabled(false);
453			// ToDo: for now
454		line->AddChild(fTypeControl);
455
456	} else
457		fTypeControl = NULL;
458
459	fStopButton = new BButton(B_EMPTY_STRING,
460		B_TRANSLATE("Stop"), new BMessage(kMsgStopFind));
461	fStopButton->SetFont(&plainFont);
462	fStopButton->Hide();
463	line->AddChild(fStopButton);
464
465	BGroupLayoutBuilder(line).AddGlue();
466
467	line = new BGroupView(B_HORIZONTAL, B_USE_SMALL_SPACING);
468	GridLayout()->AddView(line, 1, 1);
469
470	stringView = new BStringView(B_EMPTY_STRING, B_TRANSLATE("Block: "));
471	stringView->SetFont(&boldFont);
472	line->AddChild(stringView);
473
474	BMessage* msg = new BMessage(kMsgPositionUpdate);
475	msg->AddBool("fPositionControl", true);
476		// BTextControl oddities
477	fPositionControl = new BTextControl(B_EMPTY_STRING, NULL, "0x0", msg);
478	fPositionControl->SetDivider(0.0);
479	fPositionControl->SetFont(&plainFont);
480	fPositionControl->TextView()->SetFontAndColor(&plainFont);
481	fPositionControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);
482	line->AddChild(fPositionControl);
483
484	fSizeView = new BStringView(B_EMPTY_STRING, B_TRANSLATE_COMMENT("of "
485		"0x0", "This is a part of 'Block 0xXXXX of 0x0026' message. In "
486		"languages without 'of' structure it can be replaced simply "
487		"with '/'."));
488	fSizeView->SetFont(&plainFont);
489	line->AddChild(fSizeView);
490	UpdateFileSizeView();
491
492	stringView = new BStringView(B_EMPTY_STRING, B_TRANSLATE("Offset: "));
493	stringView->SetFont(&boldFont);
494	line->AddChild(stringView);
495
496	msg = new BMessage(kMsgPositionUpdate);
497	msg->AddBool("fOffsetControl", false);
498	fOffsetControl = new BTextControl(B_EMPTY_STRING, NULL, "0x0", msg);
499	fOffsetControl->SetDivider(0.0);
500	fOffsetControl->SetFont(&plainFont);
501	fOffsetControl->TextView()->SetFontAndColor(&plainFont);
502	fOffsetControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);
503	line->AddChild(fOffsetControl);
504	UpdateOffsetViews(false);
505
506	stringView = new BStringView(B_EMPTY_STRING, editor.IsAttribute()
507		? B_TRANSLATE("Attribute offset: ") : editor.IsDevice()
508			? B_TRANSLATE("Device offset: ") : B_TRANSLATE("File offset: "));
509	stringView->SetFont(&boldFont);
510	line->AddChild(stringView);
511
512	msg = new BMessage(kMsgPositionUpdate);
513	msg->AddBool("fFileOffsetControl", false);
514	fFileOffsetControl = new BTextControl(B_EMPTY_STRING, NULL, "0x0", msg);
515	fFileOffsetControl->SetDivider(0.0);
516	fFileOffsetControl->SetFont(&plainFont);
517	fFileOffsetControl->TextView()->SetFontAndColor(&plainFont);
518	fFileOffsetControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);
519	line->AddChild(fFileOffsetControl);
520
521	BGroupLayoutBuilder(line).AddGlue();
522
523	fPositionSlider = new PositionSlider("slider",
524		new BMessage(kMsgSliderUpdate), editor.FileSize(), editor.BlockSize());
525	fPositionSlider->SetModificationMessage(new BMessage(kMsgSliderUpdate));
526	fPositionSlider->SetBarThickness(8);
527	GridLayout()->AddView(fPositionSlider, 0, 2, 2, 1);
528}
529
530
531HeaderView::~HeaderView()
532{
533}
534
535
536void
537HeaderView::AttachedToWindow()
538{
539	SetTarget(Window());
540
541	fStopButton->SetTarget(Parent());
542	fPositionControl->SetTarget(this);
543	fOffsetControl->SetTarget(this);
544	fFileOffsetControl->SetTarget(this);
545	fPositionSlider->SetTarget(this);
546
547	BMessage* message;
548	Window()->AddShortcut(B_HOME, B_COMMAND_KEY,
549		message = new BMessage(kMsgPositionUpdate), this);
550	message->AddInt64("block", 0);
551	Window()->AddShortcut(B_END, B_COMMAND_KEY,
552		message = new BMessage(kMsgPositionUpdate), this);
553	message->AddInt64("block", -1);
554	Window()->AddShortcut(B_PAGE_UP, B_COMMAND_KEY,
555		message = new BMessage(kMsgPositionUpdate), this);
556	message->AddInt32("delta", -1);
557	Window()->AddShortcut(B_PAGE_DOWN, B_COMMAND_KEY,
558		message = new BMessage(kMsgPositionUpdate), this);
559	message->AddInt32("delta", 1);
560}
561
562
563void
564HeaderView::DetachedFromWindow()
565{
566	Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY);
567	Window()->RemoveShortcut(B_END, B_COMMAND_KEY);
568	Window()->RemoveShortcut(B_PAGE_UP, B_COMMAND_KEY);
569	Window()->RemoveShortcut(B_PAGE_DOWN, B_COMMAND_KEY);
570}
571
572
573void
574HeaderView::UpdateIcon()
575{
576	fIconView->UpdateIcon();
577}
578
579
580void
581HeaderView::FormatValue(char* buffer, size_t bufferSize, off_t value)
582{
583	snprintf(buffer, bufferSize, fBase == kHexBase ? "0x%" B_PRIxOFF : "%"
584		B_PRIdOFF, value);
585}
586
587
588void
589HeaderView::UpdatePositionViews(bool all)
590{
591	char buffer[64];
592	FormatValue(buffer, sizeof(buffer), fPosition / fBlockSize);
593	fPositionControl->SetText(buffer);
594
595	if (all) {
596		FormatValue(buffer, sizeof(buffer), fPosition);
597		fFileOffsetControl->SetText(buffer);
598	}
599}
600
601
602void
603HeaderView::UpdateOffsetViews(bool all)
604{
605	char buffer[64];
606	FormatValue(buffer, sizeof(buffer), fPosition % fBlockSize);
607	fOffsetControl->SetText(buffer);
608
609	if (all) {
610		FormatValue(buffer, sizeof(buffer), fPosition);
611		fFileOffsetControl->SetText(buffer);
612	}
613}
614
615
616void
617HeaderView::UpdateFileSizeView()
618{
619	BString string(B_TRANSLATE("of "));
620	char buffer[64];
621	FormatValue(buffer, sizeof(buffer),
622		(fFileSize + fBlockSize - 1) / fBlockSize);
623	string << buffer;
624
625	fSizeView->SetText(string.String());
626}
627
628
629void
630HeaderView::SetBase(base_type type)
631{
632	if (fBase == type)
633		return;
634
635	fBase = type;
636
637	UpdatePositionViews();
638	UpdateOffsetViews(false);
639	UpdateFileSizeView();
640}
641
642
643void
644HeaderView::SetTo(off_t position, uint32 blockSize)
645{
646	fPosition = position;
647	fLastPosition = (fLastPosition / fBlockSize) * blockSize;
648	fBlockSize = blockSize;
649
650	fPositionSlider->SetBlockSize(blockSize);
651	UpdatePositionViews();
652	UpdateOffsetViews(false);
653	UpdateFileSizeView();
654}
655
656
657void
658HeaderView::NotifyTarget()
659{
660	BMessage update(kMsgPositionUpdate);
661	update.AddInt64("position", fPosition);
662	Messenger().SendMessage(&update);
663}
664
665
666void
667HeaderView::MessageReceived(BMessage* message)
668{
669	switch (message->what) {
670		case B_OBSERVER_NOTICE_CHANGE: {
671			int32 what;
672			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
673				break;
674
675			switch (what) {
676				case kDataViewCursorPosition:
677					off_t offset;
678					if (message->FindInt64("position", &offset) == B_OK) {
679						fPosition = (fPosition / fBlockSize) * fBlockSize
680							+ offset;
681						UpdateOffsetViews();
682					}
683					break;
684			}
685			break;
686		}
687
688		case kMsgSliderUpdate:
689		{
690			// First, make sure we're only considering the most
691			// up-to-date message in the queue (which might not
692			// be this one).
693			// If there is another message of this type in the
694			// queue, we're just ignoring the current message.
695
696			if (Looper()->MessageQueue()->FindMessage(kMsgSliderUpdate, 0)
697					!= NULL)
698				break;
699
700			// if nothing has changed, we can ignore this message as well
701			if (fPosition == fPositionSlider->Position())
702				break;
703
704			fLastPosition = fPosition;
705			fPosition = fPositionSlider->Position();
706
707			// update position text control
708			UpdatePositionViews();
709
710			// notify our target
711			NotifyTarget();
712			break;
713		}
714
715		case kMsgDataEditorFindProgress:
716		{
717			bool state;
718			if (message->FindBool("running", &state) == B_OK
719				&& fFileSize > fBlockSize) {
720				fPositionSlider->SetEnabled(!state);
721				if (state) {
722					fStopButton->Show();
723				} else {
724					fStopButton->Hide();
725				}
726			}
727
728			off_t position;
729			if (message->FindInt64("position", &position) != B_OK)
730				break;
731
732			fPosition = (position / fBlockSize) * fBlockSize;
733				// round to block size
734
735			// update views
736			UpdatePositionViews(false);
737			fPositionSlider->SetPosition(fPosition);
738			break;
739		}
740
741		case kMsgPositionUpdate:
742		{
743			off_t lastPosition = fPosition;
744
745			off_t position;
746			int32 delta;
747			bool round = true;
748			if (message->FindInt64("position", &position) == B_OK)
749				fPosition = position;
750			else if (message->FindInt64("block", &position) == B_OK) {
751				if (position < 0)
752					position += (fFileSize - 1) / fBlockSize + 1;
753				fPosition = position * fBlockSize;
754			} else if (message->FindInt32("delta", &delta) == B_OK) {
755				fPosition += delta * off_t(fBlockSize);
756			} else {
757				try {
758					ExpressionParser parser;
759					parser.SetSupportHexInput(true);
760					if (message->FindBool("fPositionControl", &round)
761						== B_OK) {
762						fPosition = parser.EvaluateToInt64(
763							fPositionControl->Text()) * fBlockSize;
764					} else if (message->FindBool("fOffsetControl", &round)
765					== B_OK) {
766						fPosition = (fPosition / fBlockSize) * fBlockSize +
767							parser.EvaluateToInt64(fOffsetControl->Text());
768					} else if (message->FindBool("fFileOffsetControl", &round)
769						== B_OK) {
770						fPosition = parser.EvaluateToInt64(
771							fFileOffsetControl->Text());
772					}
773				} catch (...) {
774					beep();
775					break;
776				}
777			}
778
779			fLastPosition = lastPosition;
780
781			if (round)
782				fPosition = (fPosition / fBlockSize) * fBlockSize;
783				// round to block size
784
785			if (fPosition < 0)
786				fPosition = 0;
787			else if (fPosition > ((fFileSize - 1) / fBlockSize) * fBlockSize)
788				fPosition = ((fFileSize - 1) / fBlockSize) * fBlockSize;
789
790			// update views
791			UpdatePositionViews();
792			fPositionSlider->SetPosition(fPosition);
793
794			// notify our target
795			NotifyTarget();
796			break;
797		}
798
799		case kMsgLastPosition:
800		{
801			fPosition = fLastPosition;
802			fLastPosition = fPositionSlider->Position();
803
804			// update views
805			UpdatePositionViews();
806			fPositionSlider->SetPosition(fPosition);
807
808			// notify our target
809			NotifyTarget();
810			break;
811		}
812
813		case kMsgBaseType:
814		{
815			int32 type;
816			if (message->FindInt32("base", &type) != B_OK)
817				break;
818
819			SetBase((base_type)type);
820			break;
821		}
822
823		default:
824			BView::MessageReceived(message);
825	}
826}
827
828
829//	#pragma mark - TypeMenuItem
830
831
832/*!	The TypeMenuItem is a BMenuItem that displays a type string at its
833	right border.
834	It is used to display the attribute and type in the attributes menu.
835	It does not mix nicely with short cuts.
836*/
837TypeMenuItem::TypeMenuItem(const char* name, const char* type,
838		BMessage* message)
839	:
840	BMenuItem(name, message),
841	fType(type)
842{
843}
844
845
846void
847TypeMenuItem::GetContentSize(float* _width, float* _height)
848{
849	BMenuItem::GetContentSize(_width, _height);
850
851	if (_width)
852		*_width += Menu()->StringWidth(fType.String());
853}
854
855
856void
857TypeMenuItem::DrawContent()
858{
859	// draw the label
860	BMenuItem::DrawContent();
861
862	font_height fontHeight;
863	Menu()->GetFontHeight(&fontHeight);
864
865	// draw the type
866	BPoint point = ContentLocation();
867	point.x = Frame().right - 4 - Menu()->StringWidth(fType.String());
868	point.y += fontHeight.ascent;
869
870	Menu()->DrawString(fType.String(), point);
871}
872
873
874//	#pragma mark - EditorLooper
875
876
877/*!	The purpose of this looper is to off-load the editor data loading from
878	the main window looper.
879
880	It will listen to the offset changes of the editor, let him update its
881	data, and will then synchronously notify the target.
882	That way, simple offset changes will not stop the main looper from
883	operating. Therefore, all offset updates for the editor will go through
884	this looper.
885	Also, it will run the find action in the editor.
886*/
887EditorLooper::EditorLooper(const char* name, DataEditor& editor,
888		BMessenger target)
889	: BLooper(name),
890	fEditor(editor),
891	fMessenger(target),
892	fQuitFind(true)
893{
894	fEditor.StartWatching(this);
895}
896
897
898EditorLooper::~EditorLooper()
899{
900	fEditor.StopWatching(this);
901}
902
903
904void
905EditorLooper::MessageReceived(BMessage* message)
906{
907	switch (message->what) {
908		case kMsgPositionUpdate:
909		{
910			// First, make sure we're only considering the most
911			// up-to-date message in the queue (which might not
912			// be this one).
913			// If there is another message of this type in the
914			// queue, we're just ignoring the current message.
915
916			if (Looper()->MessageQueue()->FindMessage(kMsgPositionUpdate, 0) != NULL)
917				break;
918
919			off_t position;
920			if (message->FindInt64("position", &position) == B_OK) {
921				BAutolock locker(fEditor);
922				fEditor.SetViewOffset(position);
923
924				BMessage message(kMsgSetSelection);
925				message.AddInt64("start", position - fEditor.ViewOffset());
926				message.AddInt64("end", position - fEditor.ViewOffset());
927				fMessenger.SendMessage(&message);
928			}
929			break;
930		}
931
932		case kMsgDataEditorParameterChange:
933		{
934			bool updated = false;
935
936			if (fEditor.Lock()) {
937				fEditor.UpdateIfNeeded(&updated);
938				fEditor.Unlock();
939			}
940
941			if (updated) {
942				BMessage reply;
943				fMessenger.SendMessage(kMsgUpdateData, &reply);
944					// We are doing a synchronously transfer, to prevent
945					// that we're already locking the editor again when
946					// our target wants to get the editor data.
947			}
948			break;
949		}
950
951		case kMsgFind:
952		{
953			BMessenger progressMonitor;
954			message->FindMessenger("progress_monitor", &progressMonitor);
955
956			off_t startAt = 0;
957			message->FindInt64("start", &startAt);
958
959			bool caseInsensitive = !message->FindBool("case_sensitive");
960
961			ssize_t dataSize;
962			const uint8* data;
963			if (message->FindData("data", B_RAW_TYPE, (const void**)&data,
964					&dataSize) == B_OK)
965				Find(startAt, data, dataSize, caseInsensitive, progressMonitor);
966		}
967
968		default:
969			BLooper::MessageReceived(message);
970			break;
971	}
972}
973
974
975void
976EditorLooper::Find(off_t startAt, const uint8* data, size_t dataSize,
977	bool caseInsensitive, BMessenger progressMonitor)
978{
979	fQuitFind = false;
980
981	BAutolock locker(fEditor);
982
983	bigtime_t startTime = system_time();
984
985	off_t foundAt = fEditor.Find(startAt, data, dataSize, caseInsensitive,
986		true, progressMonitor, &fQuitFind);
987	if (foundAt >= B_OK) {
988		fEditor.SetViewOffset(foundAt);
989
990		// select the part in our target
991		BMessage message(kMsgSetSelection);
992		message.AddInt64("start", foundAt - fEditor.ViewOffset());
993		message.AddInt64("end", foundAt + dataSize - 1 - fEditor.ViewOffset());
994		fMessenger.SendMessage(&message);
995	} else if (foundAt == B_ENTRY_NOT_FOUND) {
996		if (system_time() > startTime + 8000000LL) {
997			// If the user had to wait more than 8 seconds for the result,
998			// we are trying to please him with a requester...
999			BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
1000				B_TRANSLATE("Could not find search string."),
1001				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
1002				B_WARNING_ALERT);
1003			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1004			alert->Go(NULL);
1005		} else
1006			beep();
1007	}
1008}
1009
1010
1011void
1012EditorLooper::QuitFind()
1013{
1014	fQuitFind = true;
1015		// this will cleanly stop the find process
1016}
1017
1018
1019//	#pragma mark - TypeView
1020
1021
1022TypeView::TypeView(BRect rect, const char* name, int32 index,
1023		DataEditor& editor, int32 resizingMode)
1024	: BView(rect, name, resizingMode, B_FRAME_EVENTS)
1025{
1026	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1027
1028	fTypeEditorView = GetTypeEditorAt(index, Frame(), editor);
1029	if (fTypeEditorView == NULL) {
1030		AddChild(new BStringView(Bounds(), B_TRANSLATE("Type editor"),
1031			B_TRANSLATE("Type editor not supported"), B_FOLLOW_NONE));
1032	} else
1033		AddChild(fTypeEditorView);
1034
1035	if ((fTypeEditorView->ResizingMode() & (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM))
1036			!= 0) {
1037		BRect rect = Bounds();
1038
1039		BRect frame = fTypeEditorView->Frame();
1040		rect.left = frame.left;
1041		rect.top = frame.top;
1042		if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0)
1043			rect.right = frame.right;
1044		if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0)
1045			rect.bottom = frame.bottom;
1046
1047		fTypeEditorView->ResizeTo(rect.Width(), rect.Height());
1048	}
1049}
1050
1051
1052TypeView::~TypeView()
1053{
1054}
1055
1056
1057void
1058TypeView::FrameResized(float width, float height)
1059{
1060	BRect rect = Bounds();
1061
1062	BPoint point = fTypeEditorView->Frame().LeftTop();
1063	if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0)
1064		point.x = (rect.Width() - fTypeEditorView->Bounds().Width()) / 2;
1065	if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0)
1066		point.y = (rect.Height() - fTypeEditorView->Bounds().Height()) / 2;
1067
1068	fTypeEditorView->MoveTo(point);
1069}
1070
1071
1072//	#pragma mark - ProbeView
1073
1074
1075ProbeView::ProbeView(entry_ref* ref, const char* attribute,
1076		const BMessage* settings)
1077	: BView("probeView", B_WILL_DRAW),
1078	fPrintSettings(NULL),
1079	fTypeView(NULL),
1080	fLastSearch(NULL)
1081{
1082	fEditor.SetTo(*ref, attribute);
1083
1084	int32 baseType = kHexBase;
1085	float fontSize = be_plain_font->Size();
1086	if (settings != NULL) {
1087		settings->FindInt32("base_type", &baseType);
1088		settings->FindFloat("font_size", &fontSize);
1089	}
1090
1091	fHeaderView = new HeaderView(&fEditor.Ref(), fEditor);
1092	fHeaderView->SetBase((base_type)baseType);
1093
1094	fDataView = new DataView(fEditor);
1095	fDataView->SetBase((base_type)baseType);
1096	fDataView->SetFontSize(fontSize);
1097
1098	fScrollView = new BScrollView("scroller", fDataView, B_WILL_DRAW, true,
1099		true);
1100
1101	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
1102		.SetInsets(-1, -1, -1, -1)
1103		.Add(fHeaderView)
1104		.AddGroup(B_VERTICAL, 0)
1105			.SetInsets(-1, 0, -1, -1)
1106			.Add(fScrollView)
1107		.End();
1108
1109	fDataView->UpdateScroller();
1110}
1111
1112
1113ProbeView::~ProbeView()
1114{
1115}
1116
1117
1118void
1119ProbeView::DetachedFromWindow()
1120{
1121	fEditorLooper->QuitFind();
1122
1123	if (fEditorLooper->Lock())
1124		fEditorLooper->Quit();
1125	fEditorLooper = NULL;
1126
1127	fEditor.StopWatching(this);
1128	fDataView->StopWatching(fHeaderView, kDataViewCursorPosition);
1129	fDataView->StopWatching(this, kDataViewSelection);
1130	fDataView->StopWatching(this, kDataViewPreferredSize);
1131	be_clipboard->StopWatching(this);
1132}
1133
1134
1135void
1136ProbeView::_UpdateAttributesMenu(BMenu* menu)
1137{
1138	// remove old contents
1139
1140	for (int32 i = menu->CountItems(); i-- > 0;) {
1141		delete menu->RemoveItem(i);
1142	}
1143
1144	// add new items (sorted)
1145
1146	BNode node(&fEditor.AttributeRef());
1147	if (node.InitCheck() == B_OK) {
1148		char attribute[B_ATTR_NAME_LENGTH];
1149		node.RewindAttrs();
1150
1151		while (node.GetNextAttrName(attribute) == B_OK) {
1152			attr_info info;
1153			if (node.GetAttrInfo(attribute, &info) != B_OK)
1154				continue;
1155
1156			char type[16];
1157			type[0] = '[';
1158			get_type_string(type + 1, sizeof(type) - 2, info.type);
1159			strcat(type, "]");
1160
1161			// find where to insert
1162			int32 i;
1163			for (i = 0; i < menu->CountItems(); i++) {
1164				if (strcasecmp(menu->ItemAt(i)->Label(), attribute) > 0)
1165					break;
1166			}
1167
1168			BMessage* message = new BMessage(B_REFS_RECEIVED);
1169			message->AddRef("refs", &fEditor.AttributeRef());
1170			message->AddString("attributes", attribute);
1171
1172			menu->AddItem(new TypeMenuItem(attribute, type, message), i);
1173		}
1174	}
1175
1176	if (menu->CountItems() == 0) {
1177		// if there are no attributes, add an item to the menu
1178		// that says so
1179		BMenuItem* item = new BMenuItem(B_TRANSLATE_COMMENT("none",
1180			"No attributes"), NULL);
1181		item->SetEnabled(false);
1182		menu->AddItem(item);
1183	}
1184
1185	menu->SetTargetForItems(be_app);
1186}
1187
1188
1189void
1190ProbeView::AddSaveMenuItems(BMenu* menu, int32 index)
1191{
1192	menu->AddItem(fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"),
1193		new BMessage(B_SAVE_REQUESTED), 'S'), index);
1194	fSaveMenuItem->SetTarget(this);
1195	fSaveMenuItem->SetEnabled(false);
1196	//menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, NULL), index);
1197}
1198
1199
1200void
1201ProbeView::AddPrintMenuItems(BMenu* menu, int32 index)
1202{
1203	BMenuItem* item;
1204	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
1205		new BMessage(kMsgPageSetup)), index++);
1206	item->SetTarget(this);
1207	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
1208		new BMessage(kMsgPrint), 'P'), index++);
1209	item->SetTarget(this);
1210}
1211
1212
1213void
1214ProbeView::AddViewAsMenuItems()
1215{
1216#if 0
1217	BMenuBar* bar = Window()->KeyMenuBar();
1218	if (bar == NULL)
1219		return;
1220
1221	BMenuItem* item = bar->FindItem(B_TRANSLATE("View"));
1222	BMenu* menu = NULL;
1223	if (item != NULL)
1224		menu = item->Submenu();
1225	else
1226		menu = bar->SubmenuAt(bar->CountItems() - 1);
1227
1228	if (menu == NULL)
1229		return;
1230
1231	menu->AddSeparatorItem();
1232
1233	BMenu* subMenu = new BMenu(B_TRANSLATE("View As"));
1234	subMenu->SetRadioMode(true);
1235
1236	BMessage* message = new BMessage(kMsgViewAs);
1237	subMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Raw"), message));
1238	item->SetMarked(true);
1239
1240	const char* name;
1241	for (int32 i = 0; GetNthTypeEditor(i, &name) == B_OK; i++) {
1242		message = new BMessage(kMsgViewAs);
1243		message->AddInt32("editor index", i);
1244		subMenu->AddItem(new BMenuItem(name, message));
1245	}
1246
1247	subMenu->SetTargetForItems(this);
1248	menu->AddItem(new BMenuItem(subMenu));
1249#endif
1250}
1251
1252
1253void
1254ProbeView::AttachedToWindow()
1255{
1256	BView::AttachedToWindow();
1257
1258	fEditorLooper = new EditorLooper(fEditor.Ref().name, fEditor,
1259		BMessenger(fDataView));
1260	fEditorLooper->Run();
1261
1262	fEditor.StartWatching(this);
1263	fDataView->StartWatching(fHeaderView, kDataViewCursorPosition);
1264	fDataView->StartWatching(this, kDataViewSelection);
1265	fDataView->StartWatching(this, kDataViewPreferredSize);
1266	be_clipboard->StartWatching(this);
1267
1268	// Add menu to window
1269
1270	BMenuBar* bar = Window()->KeyMenuBar();
1271	if (bar == NULL) {
1272		// there is none? Well, but we really want to have one
1273		bar = new BMenuBar("");
1274		Window()->AddChild(bar);
1275
1276		BMenu* menu = new BMenu(fEditor.IsAttribute()
1277			? B_TRANSLATE("Attribute") : fEditor.IsDevice()
1278			? B_TRANSLATE("Device") : B_TRANSLATE("File"));
1279		AddSaveMenuItems(menu, 0);
1280		menu->AddSeparatorItem();
1281		AddPrintMenuItems(menu, menu->CountItems());
1282		menu->AddSeparatorItem();
1283
1284		menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1285			new BMessage(B_CLOSE_REQUESTED), 'W'));
1286		bar->AddItem(menu);
1287	}
1288
1289	// "Edit" menu
1290
1291	BMenu* menu = new BMenu(B_TRANSLATE("Edit"));
1292	BMenuItem* item;
1293	menu->AddItem(fUndoMenuItem = new BMenuItem(B_TRANSLATE("Undo"),
1294		new BMessage(B_UNDO), 'Z'));
1295	fUndoMenuItem->SetEnabled(fEditor.CanUndo());
1296	fUndoMenuItem->SetTarget(fDataView);
1297	menu->AddItem(fRedoMenuItem = new BMenuItem(B_TRANSLATE("Redo"),
1298		new BMessage(B_REDO), 'Z', B_SHIFT_KEY));
1299	fRedoMenuItem->SetEnabled(fEditor.CanRedo());
1300	fRedoMenuItem->SetTarget(fDataView);
1301	menu->AddSeparatorItem();
1302	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"),
1303		new BMessage(B_COPY), 'C'));
1304	item->SetTarget(NULL, Window());
1305	menu->AddItem(fPasteMenuItem = new BMenuItem(B_TRANSLATE("Paste"),
1306		new BMessage(B_PASTE), 'V'));
1307	fPasteMenuItem->SetTarget(NULL, Window());
1308	_CheckClipboard();
1309	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"),
1310		new BMessage(B_SELECT_ALL), 'A'));
1311	item->SetTarget(NULL, Window());
1312	menu->AddSeparatorItem();
1313	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
1314		new BMessage(kMsgOpenFindWindow), 'F'));
1315	item->SetTarget(this);
1316	menu->AddItem(fFindAgainMenuItem = new BMenuItem(B_TRANSLATE("Find again"),
1317		new BMessage(kMsgFind), 'G'));
1318	fFindAgainMenuItem->SetEnabled(false);
1319	fFindAgainMenuItem->SetTarget(this);
1320	bar->AddItem(menu);
1321
1322	// "Block" menu
1323
1324	menu = new BMenu(B_TRANSLATE("Block"));
1325	BMessage* message = new BMessage(kMsgPositionUpdate);
1326	message->AddInt32("delta", 1);
1327	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Next"), message,
1328		B_RIGHT_ARROW));
1329	item->SetTarget(fHeaderView);
1330	message = new BMessage(kMsgPositionUpdate);
1331	message->AddInt32("delta", -1);
1332	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Previous"), message,
1333		B_LEFT_ARROW));
1334	item->SetTarget(fHeaderView);
1335	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Back"),
1336		new BMessage(kMsgLastPosition), 'J'));
1337	item->SetTarget(fHeaderView);
1338
1339	BMenu* subMenu = new BMenu(B_TRANSLATE("Selection"));
1340	message = new BMessage(kMsgPositionUpdate);
1341	message->AddInt64("block", 0);
1342	subMenu->AddItem(fNativeMenuItem = new BMenuItem("", message, 'K'));
1343	fNativeMenuItem->SetTarget(fHeaderView);
1344	message = new BMessage(*message);
1345	subMenu->AddItem(fSwappedMenuItem = new BMenuItem("", message, 'L'));
1346	fSwappedMenuItem->SetTarget(fHeaderView);
1347	menu->AddItem(new BMenuItem(subMenu));
1348	_UpdateSelectionMenuItems(0, 0);
1349	menu->AddSeparatorItem();
1350
1351	fBookmarkMenu = new BMenu(B_TRANSLATE("Bookmarks"));
1352	fBookmarkMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Add"),
1353		new BMessage(kMsgAddBookmark), 'B'));
1354	item->SetTarget(this);
1355	menu->AddItem(new BMenuItem(fBookmarkMenu));
1356	bar->AddItem(menu);
1357
1358	// "Attributes" menu (it's only visible if the underlying
1359	// file system actually supports attributes)
1360
1361	BDirectory directory;
1362	BVolume volume;
1363	if (directory.SetTo(&fEditor.AttributeRef()) == B_OK
1364		&& directory.IsRootDirectory())
1365		directory.GetVolume(&volume);
1366	else
1367		fEditor.File().GetVolume(&volume);
1368
1369	if (!fEditor.IsAttribute() && volume.InitCheck() == B_OK
1370		&& (volume.KnowsMime() || volume.KnowsAttr())) {
1371		bar->AddItem(menu = new BMenu(B_TRANSLATE("Attributes")));
1372		_UpdateAttributesMenu(menu);
1373	}
1374
1375	// "View" menu
1376
1377	menu = new BMenu(B_TRANSLATE_COMMENT("View",
1378		"This is the last menubar item 'File Edit Block View'"));
1379
1380	// Number Base (hex/decimal)
1381
1382	subMenu = new BMenu(B_TRANSLATE_COMMENT("Base", "A menu item, the number "
1383		"that is basis for a system of calculation. The base 10 system is a "
1384		"decimal system. This is in the same menu window than 'Font size' "
1385		"and 'BlockSize'"));
1386	message = new BMessage(kMsgBaseType);
1387	message->AddInt32("base_type", kDecimalBase);
1388	subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Decimal",
1389		"A menu item, as short as possible, noun is recommended if it is "
1390		"shorter than adjective."), message, 'D'));
1391	item->SetTarget(this);
1392	if (fHeaderView->Base() == kDecimalBase)
1393		item->SetMarked(true);
1394
1395	message = new BMessage(kMsgBaseType);
1396	message->AddInt32("base_type", kHexBase);
1397	subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Hex",
1398		"A menu item, as short as possible, noun is recommended if it is "
1399		"shorter than adjective."), message, 'H'));
1400	item->SetTarget(this);
1401	if (fHeaderView->Base() == kHexBase)
1402		item->SetMarked(true);
1403
1404	subMenu->SetRadioMode(true);
1405	menu->AddItem(new BMenuItem(subMenu));
1406
1407	// Block Size
1408
1409	subMenu = new BMenu(B_TRANSLATE_COMMENT("Block size", "Menu item. "
1410		"This is in the same menu window than 'Base' and 'Font size'"));
1411	subMenu->SetRadioMode(true);
1412	const uint32 blockSizes[] = {512, 1024, 2048, 4096};
1413	for (uint32 i = 0; i < sizeof(blockSizes) / sizeof(blockSizes[0]); i++) {
1414		char buffer[32];
1415		snprintf(buffer, sizeof(buffer), "%" B_PRId32 "%s", blockSizes[i],
1416			fEditor.IsDevice() && fEditor.BlockSize() == blockSizes[i]
1417			? B_TRANSLATE(" (native)") : "");
1418		subMenu->AddItem(item = new BMenuItem(buffer,
1419			message = new BMessage(kMsgBlockSize)));
1420		message->AddInt32("block_size", blockSizes[i]);
1421		if (fEditor.BlockSize() == blockSizes[i])
1422			item->SetMarked(true);
1423	}
1424	if (subMenu->FindMarked() == NULL) {
1425		// if the device has some weird block size, we'll add it here, too
1426		char buffer[32];
1427		snprintf(buffer, sizeof(buffer), B_TRANSLATE("%ld (native)"),
1428			fEditor.BlockSize());
1429		subMenu->AddItem(item = new BMenuItem(buffer,
1430			message = new BMessage(kMsgBlockSize)));
1431		message->AddInt32("block_size", fEditor.BlockSize());
1432		item->SetMarked(true);
1433	}
1434	subMenu->SetTargetForItems(this);
1435	menu->AddItem(new BMenuItem(subMenu));
1436	menu->AddSeparatorItem();
1437
1438	// Font Size
1439
1440	subMenu = new BMenu(B_TRANSLATE("Font size"));
1441	subMenu->SetRadioMode(true);
1442	const int32 fontSizes[] = {9, 10, 11, 12, 13, 14, 18, 24, 36, 48};
1443	int32 fontSize = int32(fDataView->FontSize() + 0.5);
1444	if (fDataView->FontSizeFitsBounds())
1445		fontSize = 0;
1446	for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) {
1447		char buffer[16];
1448		snprintf(buffer, sizeof(buffer), "%" B_PRId32, fontSizes[i]);
1449		subMenu->AddItem(item = new BMenuItem(buffer,
1450			message = new BMessage(kMsgFontSize)));
1451		message->AddFloat("font_size", fontSizes[i]);
1452		if (fontSizes[i] == fontSize)
1453			item->SetMarked(true);
1454	}
1455	subMenu->AddSeparatorItem();
1456	subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Fit",
1457		"Size of fonts, fits to available room"),
1458		message = new BMessage(kMsgFontSize)));
1459	message->AddFloat("font_size", 0.0f);
1460	if (fontSize == 0)
1461		item->SetMarked(true);
1462
1463	subMenu->SetTargetForItems(this);
1464	menu->AddItem(new BMenuItem(subMenu));
1465
1466	bar->AddItem(menu);
1467}
1468
1469
1470void
1471ProbeView::AllAttached()
1472{
1473	fHeaderView->SetTarget(fEditorLooper);
1474}
1475
1476
1477void
1478ProbeView::WindowActivated(bool active)
1479{
1480	if (!active)
1481		return;
1482
1483	fDataView->MakeFocus(true);
1484
1485	// set this view as the current find panel's target
1486	BMessage target(kMsgFindTarget);
1487	target.AddMessenger("target", this);
1488	be_app_messenger.SendMessage(&target);
1489}
1490
1491
1492void
1493ProbeView::_UpdateSelectionMenuItems(int64 start, int64 end)
1494{
1495	int64 position = 0;
1496	const uint8* data = fDataView->DataAt(start);
1497	if (data == NULL) {
1498		fNativeMenuItem->SetEnabled(false);
1499		fSwappedMenuItem->SetEnabled(false);
1500		return;
1501	}
1502
1503	// retrieve native endian position
1504
1505	int size;
1506	if (end < start + 8)
1507		size = end + 1 - start;
1508	else
1509		size = 8;
1510
1511	int64 bigEndianPosition = 0;
1512	memcpy(&bigEndianPosition, data, size);
1513
1514	position = B_BENDIAN_TO_HOST_INT64(bigEndianPosition) >> (8 * (8 - size));
1515
1516	// update menu items
1517
1518	char buffer[128];
1519	if (fDataView->Base() == kHexBase) {
1520		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: 0x%0*Lx"),
1521			size * 2, (long long int)position);
1522	} else {
1523		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: %lld (0x%0*Lx)"),
1524			(long long int)position, size * 2, (long long int)position);
1525	}
1526
1527	fNativeMenuItem->SetLabel(buffer);
1528	fNativeMenuItem->SetEnabled(position >= 0
1529		&& (off_t)(position * fEditor.BlockSize()) < fEditor.FileSize());
1530	fNativeMenuItem->Message()->ReplaceInt64("block", position);
1531
1532	position = B_SWAP_INT64(position) >> (8 * (8 - size));
1533	if (fDataView->Base() == kHexBase) {
1534		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: 0x%0*Lx"),
1535			size * 2, (long long int)position);
1536	} else {
1537		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: %lld (0x%0*Lx)"),
1538			(long long int)position, size * 2, (long long int)position);
1539	}
1540
1541	fSwappedMenuItem->SetLabel(buffer);
1542	fSwappedMenuItem->SetEnabled(position >= 0 && (off_t)(position * fEditor.BlockSize()) < fEditor.FileSize());
1543	fSwappedMenuItem->Message()->ReplaceInt64("block", position);
1544}
1545
1546
1547void
1548ProbeView::_UpdateBookmarkMenuItems()
1549{
1550	for (int32 i = 2; i < fBookmarkMenu->CountItems(); i++) {
1551		BMenuItem* item = fBookmarkMenu->ItemAt(i);
1552		if (item == NULL)
1553			break;
1554
1555		BMessage* message = item->Message();
1556		if (message == NULL)
1557			break;
1558
1559		off_t block = message->FindInt64("block");
1560
1561		char buffer[128];
1562		if (fDataView->Base() == kHexBase)
1563			snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), (long long unsigned)block);
1564		else {
1565			snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %lld (0x%Lx)"),
1566				(long long int)block, (long long unsigned)block);
1567		}
1568
1569		item->SetLabel(buffer);
1570	}
1571}
1572
1573
1574void
1575ProbeView::_AddBookmark(off_t position)
1576{
1577	int32 count = fBookmarkMenu->CountItems();
1578
1579	if (count == 1) {
1580		fBookmarkMenu->AddSeparatorItem();
1581		count++;
1582	}
1583
1584	// insert current position as bookmark
1585
1586	off_t block = position / fEditor.BlockSize();
1587
1588	off_t bookmark = -1;
1589	BMenuItem* item;
1590	int32 i;
1591	for (i = 2; (item = fBookmarkMenu->ItemAt(i)) != NULL; i++) {
1592		BMessage* message = item->Message();
1593		if (message != NULL && message->FindInt64("block", &bookmark) == B_OK) {
1594			if (block <= bookmark)
1595				break;
1596		}
1597	}
1598
1599	// the bookmark already exists
1600	if (block == bookmark)
1601		return;
1602
1603	char buffer[128];
1604	if (fDataView->Base() == kHexBase)
1605		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), (long long unsigned)block);
1606	else {
1607		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %lld (0x%Lx)"),
1608			(long long int)block, (long long unsigned)block);
1609	}
1610
1611	BMessage* message;
1612	item = new BMenuItem(buffer, message = new BMessage(kMsgPositionUpdate));
1613	item->SetTarget(fHeaderView);
1614	if (count < 12)
1615		item->SetShortcut('0' + count - 2, B_COMMAND_KEY);
1616	message->AddInt64("block", block);
1617
1618	fBookmarkMenu->AddItem(item, i);
1619}
1620
1621
1622void
1623ProbeView::_RemoveTypeEditor()
1624{
1625	if (fTypeView == NULL)
1626		return;
1627
1628	if (Parent() != NULL)
1629		Parent()->RemoveChild(fTypeView);
1630	else
1631		Window()->RemoveChild(fTypeView);
1632
1633	delete fTypeView;
1634	fTypeView = NULL;
1635}
1636
1637
1638void
1639ProbeView::_SetTypeEditor(int32 index)
1640{
1641	if (index == -1) {
1642		// remove type editor, show raw editor
1643		if (IsHidden())
1644			Show();
1645
1646		_RemoveTypeEditor();
1647	} else {
1648		// hide raw editor, create and show type editor
1649		if (!IsHidden())
1650			Hide();
1651
1652		_RemoveTypeEditor();
1653
1654		fTypeView = new TypeView(Frame(), "type shell", index, fEditor,
1655			B_FOLLOW_ALL);
1656
1657		if (Parent() != NULL)
1658			Parent()->AddChild(fTypeView);
1659		else
1660			Window()->AddChild(fTypeView);
1661	}
1662}
1663
1664
1665void
1666ProbeView::_CheckClipboard()
1667{
1668	if (!be_clipboard->Lock())
1669		return;
1670
1671	bool hasData = false;
1672	BMessage* clip;
1673	if ((clip = be_clipboard->Data()) != NULL) {
1674		const void* data;
1675		ssize_t size;
1676		if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK
1677			|| clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK)
1678			hasData = true;
1679	}
1680
1681	be_clipboard->Unlock();
1682
1683	fPasteMenuItem->SetEnabled(hasData);
1684}
1685
1686
1687status_t
1688ProbeView::_PageSetup()
1689{
1690	BPrintJob printJob(Window()->Title());
1691	if (fPrintSettings != NULL)
1692		printJob.SetSettings(new BMessage(*fPrintSettings));
1693
1694	status_t status = printJob.ConfigPage();
1695	if (status == B_OK) {
1696		// replace the print settings on success
1697		delete fPrintSettings;
1698		fPrintSettings = printJob.Settings();
1699	}
1700
1701	return status;
1702}
1703
1704
1705void
1706ProbeView::_Print()
1707{
1708	if (fPrintSettings == NULL && _PageSetup() != B_OK)
1709		return;
1710
1711	BPrintJob printJob(Window()->Title());
1712	printJob.SetSettings(new BMessage(*fPrintSettings));
1713
1714	if (printJob.ConfigJob() == B_OK) {
1715		BRect rect = printJob.PrintableRect();
1716
1717		float width, height;
1718		fDataView->GetPreferredSize(&width, &height);
1719
1720		printJob.BeginJob();
1721
1722		fDataView->SetScale(rect.Width() / width);
1723		printJob.DrawView(fDataView, rect, rect.LeftTop());
1724		fDataView->SetScale(1.0);
1725		printJob.SpoolPage();
1726
1727		printJob.CommitJob();
1728	}
1729}
1730
1731
1732status_t
1733ProbeView::_Save()
1734{
1735	status_t status = fEditor.Save();
1736	if (status == B_OK)
1737		return B_OK;
1738
1739	char buffer[1024];
1740	snprintf(buffer, sizeof(buffer),
1741		B_TRANSLATE("Writing to the file failed:\n"
1742		"%s\n\n"
1743		"All changes will be lost when you quit."),
1744		strerror(status));
1745
1746	BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
1747		buffer, B_TRANSLATE("OK"), NULL, NULL,
1748		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1749	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1750	alert->Go(NULL);
1751
1752	return status;
1753}
1754
1755
1756bool
1757ProbeView::QuitRequested()
1758{
1759	fEditorLooper->QuitFind();
1760
1761	if (!fEditor.IsModified())
1762		return true;
1763
1764	BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
1765		B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"),
1766		B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WIDTH_AS_USUAL,
1767		B_OFFSET_SPACING, B_WARNING_ALERT);
1768	alert->SetShortcut(0, B_ESCAPE);
1769	alert->SetShortcut(1, 'd');
1770	alert->SetShortcut(2, 's');
1771	int32 chosen = alert->Go();
1772
1773	if (chosen == 0)
1774		return false;
1775	if (chosen == 1)
1776		return true;
1777
1778	return _Save() == B_OK;
1779}
1780
1781
1782void
1783ProbeView::MessageReceived(BMessage* message)
1784{
1785	switch (message->what) {
1786		case B_SAVE_REQUESTED:
1787			_Save();
1788			break;
1789
1790		case B_OBSERVER_NOTICE_CHANGE: {
1791			int32 what;
1792			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
1793				break;
1794
1795			switch (what) {
1796				case kDataViewSelection:
1797				{
1798					int64 start, end;
1799					if (message->FindInt64("start", &start) == B_OK
1800						&& message->FindInt64("end", &end) == B_OK)
1801						_UpdateSelectionMenuItems(start, end);
1802					break;
1803				}
1804			}
1805			break;
1806		}
1807
1808		case kMsgBaseType:
1809		{
1810			int32 type;
1811			if (message->FindInt32("base_type", &type) != B_OK)
1812				break;
1813
1814			fHeaderView->SetBase((base_type)type);
1815			fDataView->SetBase((base_type)type);
1816
1817			// The selection menu items depend on the base type as well
1818			int32 start, end;
1819			fDataView->GetSelection(start, end);
1820			_UpdateSelectionMenuItems(start, end);
1821
1822			_UpdateBookmarkMenuItems();
1823
1824			// update the application's settings
1825			BMessage update(*message);
1826			update.what = kMsgSettingsChanged;
1827			be_app_messenger.SendMessage(&update);
1828			break;
1829		}
1830
1831		case kMsgFontSize:
1832		{
1833			float size;
1834			if (message->FindFloat("font_size", &size) != B_OK)
1835				break;
1836
1837			fDataView->SetFontSize(size);
1838
1839			// update the application's settings
1840			BMessage update(*message);
1841			update.what = kMsgSettingsChanged;
1842			be_app_messenger.SendMessage(&update);
1843			break;
1844		}
1845
1846		case kMsgBlockSize:
1847		{
1848			int32 blockSize;
1849			if (message->FindInt32("block_size", &blockSize) != B_OK)
1850				break;
1851
1852			BAutolock locker(fEditor);
1853
1854			if (fEditor.SetViewSize(blockSize) == B_OK
1855				&& fEditor.SetBlockSize(blockSize) == B_OK)
1856				fHeaderView->SetTo(fEditor.ViewOffset(), blockSize);
1857			break;
1858		}
1859
1860		case kMsgViewAs:
1861		{
1862			int32 index;
1863			if (message->FindInt32("editor index", &index) != B_OK)
1864				index = -1;
1865
1866			_SetTypeEditor(index);
1867			break;
1868		}
1869
1870		case kMsgAddBookmark:
1871			_AddBookmark(fHeaderView->Position());
1872			break;
1873
1874		case kMsgPrint:
1875			_Print();
1876			break;
1877
1878		case kMsgPageSetup:
1879			_PageSetup();
1880			break;
1881
1882		case kMsgOpenFindWindow:
1883		{
1884			fEditorLooper->QuitFind();
1885
1886			// set this view as the current find panel's target
1887			BMessage find(*fFindAgainMenuItem->Message());
1888			find.what = kMsgOpenFindWindow;
1889			find.AddMessenger("target", this);
1890			be_app_messenger.SendMessage(&find);
1891			break;
1892		}
1893
1894		case kMsgFind:
1895		{
1896			const uint8* data;
1897			ssize_t size;
1898			if (message->FindData("data", B_RAW_TYPE, (const void**)&data,
1899					&size) != B_OK) {
1900				// search again for last pattern
1901				BMessage* itemMessage = fFindAgainMenuItem->Message();
1902				if (itemMessage == NULL || itemMessage->FindData("data",
1903						B_RAW_TYPE, (const void**)&data, &size) != B_OK) {
1904					// this shouldn't ever happen, but well...
1905					beep();
1906					break;
1907				}
1908			} else {
1909				// remember the search pattern
1910				fFindAgainMenuItem->SetMessage(new BMessage(*message));
1911				fFindAgainMenuItem->SetEnabled(true);
1912			}
1913
1914			int32 start, end;
1915			fDataView->GetSelection(start, end);
1916
1917			BMessage find(*message);
1918			find.AddInt64("start", fHeaderView->Position() + start + 1);
1919			find.AddMessenger("progress_monitor", BMessenger(fHeaderView));
1920			fEditorLooper->PostMessage(&find);
1921			break;
1922		}
1923
1924		case kMsgStopFind:
1925			fEditorLooper->QuitFind();
1926			break;
1927
1928		case B_NODE_MONITOR:
1929		{
1930			switch (message->FindInt32("opcode")) {
1931				case B_STAT_CHANGED:
1932					fEditor.ForceUpdate();
1933					break;
1934				case B_ATTR_CHANGED:
1935				{
1936					const char* name;
1937					if (message->FindString("attr", &name) != B_OK)
1938						break;
1939
1940					if (fEditor.IsAttribute()) {
1941						if (!strcmp(name, fEditor.Attribute()))
1942							fEditor.ForceUpdate();
1943					} else {
1944						BMenuBar* bar = Window()->KeyMenuBar();
1945						if (bar != NULL) {
1946							BMenuItem* item = bar->FindItem("Attributes");
1947							if (item != NULL && item->Submenu() != NULL)
1948								_UpdateAttributesMenu(item->Submenu());
1949						}
1950					}
1951
1952					// There might be a new icon
1953					if (!strcmp(name, "BEOS:TYPE")
1954						|| !strcmp(name, "BEOS:M:STD_ICON")
1955						|| !strcmp(name, "BEOS:L:STD_ICON")
1956						|| !strcmp(name, "BEOS:ICON"))
1957						fHeaderView->UpdateIcon();
1958					break;
1959				}
1960			}
1961			break;
1962		}
1963
1964		case B_CLIPBOARD_CHANGED:
1965			_CheckClipboard();
1966			break;
1967
1968		case kMsgDataEditorStateChange:
1969		{
1970			bool enabled;
1971			if (message->FindBool("can_undo", &enabled) == B_OK)
1972				fUndoMenuItem->SetEnabled(enabled);
1973
1974			if (message->FindBool("can_redo", &enabled) == B_OK)
1975				fRedoMenuItem->SetEnabled(enabled);
1976
1977			if (message->FindBool("modified", &enabled) == B_OK)
1978				fSaveMenuItem->SetEnabled(enabled);
1979			break;
1980		}
1981
1982		default:
1983			BView::MessageReceived(message);
1984	}
1985}
1986
1987