1/*
2 * Copyright 2004-2018, Axel D��rfler, axeld@pinc-software.de.
3 * All rights reserved. Distributed under the terms of the MIT license.
4 */
5
6
7#include "DiskProbe.h"
8
9#include <stdio.h>
10#include <string.h>
11
12#include <AboutWindow.h>
13#include <Alert.h>
14#include <Application.h>
15#include <Autolock.h>
16#include <Catalog.h>
17#include <Directory.h>
18#include <Entry.h>
19#include <FilePanel.h>
20#include <FindDirectory.h>
21#include <LayoutUtils.h>
22#include <Locale.h>
23#include <Path.h>
24#include <Screen.h>
25#include <TextView.h>
26
27#include "DataEditor.h"
28#include "DataView.h"
29#include "FileWindow.h"
30#include "AttributeWindow.h"
31#include "OpenWindow.h"
32#include "FindWindow.h"
33
34
35#undef B_TRANSLATION_CONTEXT
36#define B_TRANSLATION_CONTEXT "DiskProbe"
37
38
39const char* kSignature = "application/x-vnd.Haiku-DiskProbe";
40
41static const uint32 kMsgDiskProbeSettings = 'DPst';
42static const uint32 kCascadeOffset = 20;
43
44
45struct disk_probe_settings {
46	BRect	window_frame;
47	int32	base_type;
48	int32	font_size;
49	int32	flags;
50};
51
52
53enum disk_probe_flags {
54	kCaseSensitive	= 0x01,
55		// this flag alone is R5 DiskProbe settings compatible
56	kHexFindMode	= 0x02,
57};
58
59
60class Settings {
61public:
62								Settings();
63								~Settings();
64
65			const BMessage&		Message() const { return fMessage; }
66			void				UpdateFrom(BMessage* message);
67
68private:
69			status_t			Open(BFile* file, int32 mode);
70
71private:
72			BMessage			fMessage;
73			bool				fUpdated;
74};
75
76
77class DiskProbe : public BApplication {
78public:
79								DiskProbe();
80	virtual						~DiskProbe();
81
82	virtual	void				ReadyToRun();
83
84	virtual	void				RefsReceived(BMessage* message);
85	virtual	void				ArgvReceived(int32 argc, char** argv);
86	virtual	void				MessageReceived(BMessage* message);
87
88	virtual bool				QuitRequested();
89
90private:
91			status_t			Probe(BEntry& entry,
92									const char* attribute = NULL);
93
94private:
95			Settings			fSettings;
96			BFilePanel*			fFilePanel;
97			BWindow*			fOpenWindow;
98			FindWindow*			fFindWindow;
99			uint32				fWindowCount;
100			BRect				fWindowFrame;
101			BMessenger			fFindTarget;
102};
103
104
105//-----------------
106
107
108Settings::Settings()
109	:
110	fMessage(kMsgDiskProbeSettings),
111	fUpdated(false)
112{
113	float fontSize = be_plain_font->Size();
114	int32 windowWidth = DataView::WidthForFontSize(fontSize) + 20;
115		// TODO: make scrollbar width variable
116
117	BScreen screen;
118	fMessage.AddRect("window_frame", BLayoutUtils::AlignInFrame(screen.Frame(),
119		BSize(windowWidth, windowWidth),
120		BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER)));
121	fMessage.AddInt32("base_type", kHexBase);
122	fMessage.AddFloat("font_size", fontSize);
123	fMessage.AddBool("case_sensitive", true);
124	fMessage.AddInt8("find_mode", kAsciiMode);
125
126	BFile file;
127	if (Open(&file, B_READ_ONLY) != B_OK)
128		return;
129
130	// TODO: load/save settings as flattened BMessage - but not yet,
131	//		since that will break compatibility with R5's DiskProbe
132
133	disk_probe_settings settings;
134	if (file.Read(&settings, sizeof(settings)) == sizeof(settings)) {
135#if B_HOST_IS_BENDIAN
136		// settings are saved in little endian
137		settings.window_frame.left = B_LENDIAN_TO_HOST_FLOAT(
138			settings.window_frame.left);
139		settings.window_frame.top = B_LENDIAN_TO_HOST_FLOAT(
140			settings.window_frame.top);
141		settings.window_frame.right = B_LENDIAN_TO_HOST_FLOAT(
142			settings.window_frame.right);
143		settings.window_frame.bottom = B_LENDIAN_TO_HOST_FLOAT(
144			settings.window_frame.bottom);
145#endif
146		// check if the window frame is on screen at all
147		BScreen screen;
148		if (screen.Frame().Contains(settings.window_frame.LeftTop())
149			&& settings.window_frame.Width() < screen.Frame().Width()
150			&& settings.window_frame.Height() < screen.Frame().Height())
151			fMessage.ReplaceRect("window_frame", settings.window_frame);
152
153		if (settings.base_type == kHexBase
154			|| settings.base_type == kDecimalBase)
155			fMessage.ReplaceInt32("base_type",
156				B_LENDIAN_TO_HOST_INT32(settings.base_type));
157		if (settings.font_size >= 0 && settings.font_size <= 72)
158			fMessage.ReplaceFloat("font_size",
159				float(B_LENDIAN_TO_HOST_INT32(settings.font_size)));
160
161		fMessage.ReplaceBool("case_sensitive",
162			settings.flags & kCaseSensitive);
163		fMessage.ReplaceInt8("find_mode",
164			settings.flags & kHexFindMode ? kHexMode : kAsciiMode);
165	}
166}
167
168
169Settings::~Settings()
170{
171	// only save the settings if something has changed
172	if (!fUpdated)
173		return;
174
175	BFile file;
176	if (Open(&file, B_CREATE_FILE | B_WRITE_ONLY) != B_OK)
177		return;
178
179	disk_probe_settings settings;
180
181	settings.window_frame = fMessage.FindRect("window_frame");
182#if B_HOST_IS_BENDIAN
183	// settings are saved in little endian
184	settings.window_frame.left = B_HOST_TO_LENDIAN_FLOAT(
185		settings.window_frame.left);
186	settings.window_frame.top = B_HOST_TO_LENDIAN_FLOAT(
187		settings.window_frame.top);
188	settings.window_frame.right = B_HOST_TO_LENDIAN_FLOAT(
189		settings.window_frame.right);
190	settings.window_frame.bottom = B_HOST_TO_LENDIAN_FLOAT(
191		settings.window_frame.bottom);
192#endif
193
194	settings.base_type = B_HOST_TO_LENDIAN_INT32(
195		fMessage.FindInt32("base_type"));
196	settings.font_size = B_HOST_TO_LENDIAN_INT32(
197		int32(fMessage.FindFloat("font_size") + 0.5f));
198	settings.flags = B_HOST_TO_LENDIAN_INT32(
199		(fMessage.FindBool("case_sensitive") ? kCaseSensitive : 0)
200		| (fMessage.FindInt8("find_mode") == kHexMode ? kHexFindMode : 0));
201
202	file.Write(&settings, sizeof(settings));
203}
204
205
206status_t
207Settings::Open(BFile* file, int32 mode)
208{
209	BPath path;
210	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
211		return B_ERROR;
212
213	path.Append("DiskProbe_data");
214
215	return file->SetTo(path.Path(), mode);
216}
217
218
219void
220Settings::UpdateFrom(BMessage* message)
221{
222	BRect frame;
223	if (message->FindRect("window_frame", &frame) == B_OK)
224		fMessage.ReplaceRect("window_frame", frame);
225
226	int32 baseType;
227	if (message->FindInt32("base_type", &baseType) == B_OK)
228		fMessage.ReplaceInt32("base_type", baseType);
229
230	float fontSize;
231	if (message->FindFloat("font_size", &fontSize) == B_OK)
232		fMessage.ReplaceFloat("font_size", fontSize);
233
234	bool caseSensitive;
235	if (message->FindBool("case_sensitive", &caseSensitive) == B_OK)
236		fMessage.ReplaceBool("case_sensitive", caseSensitive);
237
238	int8 findMode;
239	if (message->FindInt8("find_mode", &findMode) == B_OK)
240		fMessage.ReplaceInt8("find_mode", findMode);
241
242	fUpdated = true;
243}
244
245
246//	#pragma mark -
247
248
249DiskProbe::DiskProbe()
250	: BApplication(kSignature),
251	fOpenWindow(NULL),
252	fFindWindow(NULL),
253	fWindowCount(0)
254{
255	fFilePanel = new BFilePanel();
256	fWindowFrame = fSettings.Message().FindRect("window_frame");
257}
258
259
260DiskProbe::~DiskProbe()
261{
262	delete fFilePanel;
263}
264
265
266void
267DiskProbe::ReadyToRun()
268{
269	// are there already windows open?
270	if (CountWindows() != 1)
271		return;
272
273	// if not, ask the user to open a file
274	PostMessage(kMsgOpenOpenWindow);
275}
276
277
278/*!	Opens a window containing the file pointed to by the entry_ref.
279	This function will fail if that file doesn't exist or could not
280	be opened.
281	It will check if there already is a window that probes the
282	file in question and will activate it in that case.
283	This function must be called with the application looper locked.
284*/
285status_t
286DiskProbe::Probe(BEntry& entry, const char* attribute)
287{
288	entry_ref ref;
289	status_t status = entry.GetRef(&ref);
290	if (status < B_OK)
291		return status;
292
293	ProbeWindow* lastWindow(NULL);
294
295	// Do we already have that window open?
296	for (int32 i = CountWindows(); i-- > 0; ) {
297		ProbeWindow* window = dynamic_cast<ProbeWindow*>(WindowAt(i));
298		if (window == NULL)
299			continue;
300
301		if (window->Contains(ref, attribute)) {
302			window->Activate(true);
303			return B_OK;
304		}
305		if (lastWindow == NULL)
306			lastWindow = window;
307	}
308
309	// Does the file really exist?
310	if (!entry.Exists())
311		return B_ENTRY_NOT_FOUND;
312
313	entry.GetRef(&ref);
314
315	// cascade window
316	BRect rect;
317	if (lastWindow != NULL)
318		rect = lastWindow->Frame();
319	else
320		rect = fWindowFrame;
321
322	rect.OffsetBy(kCascadeOffset, kCascadeOffset);
323
324	BWindow* window;
325	if (attribute != NULL) {
326		window = new AttributeWindow(rect, &ref, attribute,
327			&fSettings.Message());
328	} else
329		window = new FileWindow(rect, &ref, &fSettings.Message());
330
331	window->Show();
332
333	// Adjust the cascading... we can only do this after the window was created
334	// to adjust to the real size.
335	rect.right = window->Frame().right;
336	rect.bottom = window->Frame().bottom;
337
338	BScreen screen;
339	BRect screenBorder = screen.Frame();
340
341	float left = rect.left;
342	if (left + rect.Width() > screenBorder.right)
343		left = 7;
344
345	float top = rect.top;
346	if (top + rect.Height() > screenBorder.bottom)
347		top = 26;
348
349	rect.OffsetTo(BPoint(left, top));
350	window->MoveTo(BPoint(left, top));
351
352	fWindowCount++;
353
354	return B_OK;
355}
356
357
358void
359DiskProbe::RefsReceived(BMessage* message)
360{
361	bool traverseLinks = (modifiers() & B_SHIFT_KEY) == 0;
362
363	int32 index = 0;
364	entry_ref ref;
365	while (message->FindRef("refs", index++, &ref) == B_OK) {
366		const char* attribute = NULL;
367		if (message->FindString("attributes", index - 1, &attribute) == B_OK)
368			traverseLinks = false;
369
370		BEntry entry;
371		status_t status = entry.SetTo(&ref, traverseLinks);
372
373		if (status == B_OK)
374			status = Probe(entry, attribute);
375
376		if (status != B_OK) {
377			char buffer[1024];
378			snprintf(buffer, sizeof(buffer),
379				B_TRANSLATE_COMMENT("Could not open \"%s\":\n"
380				"%s", "Opening of entry reference buffer for a DiskProbe "
381				"request Alert message. The name of entry reference and "
382				"error message is shown."),
383				ref.name, strerror(status));
384
385			BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
386				buffer, B_TRANSLATE("OK"), NULL, NULL,
387				B_WIDTH_AS_USUAL, B_STOP_ALERT);
388			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
389			alert->Go();
390		}
391	}
392}
393
394
395void
396DiskProbe::ArgvReceived(int32 argc, char** argv)
397{
398	BMessage* message = CurrentMessage();
399
400	BDirectory currentDirectory;
401	if (message)
402		currentDirectory.SetTo(message->FindString("cwd"));
403
404	BMessage refs;
405
406	for (int i = 1 ; i < argc ; i++) {
407		BPath path;
408		if (argv[i][0] == '/')
409			path.SetTo(argv[i]);
410		else
411			path.SetTo(&currentDirectory, argv[i]);
412
413		status_t status;
414		entry_ref ref;
415		BEntry entry;
416
417		if ((status = entry.SetTo(path.Path(), false)) != B_OK
418			|| (status = entry.GetRef(&ref)) != B_OK) {
419			fprintf(stderr, B_TRANSLATE("Could not open file \"%s\": %s\n"),
420				path.Path(), strerror(status));
421			continue;
422		}
423
424		refs.AddRef("refs", &ref);
425	}
426
427	RefsReceived(&refs);
428}
429
430
431void
432DiskProbe::MessageReceived(BMessage* message)
433{
434	switch (message->what) {
435		case kMsgOpenOpenWindow:
436			if (fOpenWindow == NULL) {
437				fOpenWindow = new OpenWindow();
438				fOpenWindow->Show();
439				fWindowCount++;
440			} else
441				fOpenWindow->Activate(true);
442			break;
443
444		case kMsgOpenWindowClosed:
445			fOpenWindow = NULL;
446			// supposed to fall through
447		case kMsgWindowClosed:
448			if (--fWindowCount == 0 && !fFilePanel->IsShowing())
449				PostMessage(B_QUIT_REQUESTED);
450			break;
451
452		case kMsgSettingsChanged:
453			fSettings.UpdateFrom(message);
454			break;
455
456		case kMsgFindWindowClosed:
457			fFindWindow = NULL;
458			break;
459		case kMsgFindTarget:
460		{
461			BMessenger target;
462			if (message->FindMessenger("target", &target) != B_OK)
463				break;
464
465			if (fFindWindow != NULL && fFindWindow->Lock()) {
466				fFindWindow->SetTarget(target);
467				fFindWindow->Unlock();
468			}
469			break;
470		}
471		case kMsgOpenFindWindow:
472		{
473			BMessenger target;
474			if (message->FindMessenger("target", &target) != B_OK)
475				break;
476
477			if (fFindWindow == NULL) {
478				// open it!
479				fFindWindow = new FindWindow(fWindowFrame.OffsetByCopy(80, 80),
480					*message, target, &fSettings.Message());
481				fFindWindow->Show();
482			} else
483				fFindWindow->Activate();
484			break;
485		}
486
487		case kMsgOpenFilePanel:
488			fFilePanel->Show();
489			break;
490		case B_CANCEL:
491			if (fWindowCount == 0)
492				PostMessage(B_QUIT_REQUESTED);
493			break;
494
495		default:
496			BApplication::MessageReceived(message);
497			break;
498	}
499}
500
501
502bool
503DiskProbe::QuitRequested()
504{
505	return true;
506}
507
508
509//	#pragma mark -
510
511
512int
513main(int argc, char** argv)
514{
515	DiskProbe probe;
516
517	probe.Run();
518	return 0;
519}
520