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