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