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