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