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