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