xref: /haiku/src/apps/diskprobe/DiskProbe.cpp (revision a5bf12376daeded4049521eb17a6cc41192250d9)
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 <Alert.h>
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <Catalog.h>
19 #include <Directory.h>
20 #include <Entry.h>
21 #include <FilePanel.h>
22 #include <FindDirectory.h>
23 #include <Locale.h>
24 #include <Path.h>
25 #include <Screen.h>
26 #include <TextView.h>
27 
28 #include <stdio.h>
29 #include <string.h>
30 
31 #undef B_TRANSLATE_CONTEXT
32 #define B_TRANSLATE_CONTEXT "DiskProbe"
33 
34 const char *kSignature = "application/x-vnd.Haiku-DiskProbe";
35 
36 static const uint32 kMsgDiskProbeSettings = 'DPst';
37 static const uint32 kCascadeOffset = 20;
38 
39 
40 struct disk_probe_settings {
41 	BRect	window_frame;
42 	int32	base_type;
43 	int32	font_size;
44 	int32	flags;
45 };
46 
47 enum disk_probe_flags {
48 	kCaseSensitive	= 0x01,	// this flag alone is R5 DiskProbe settings compatible
49 	kHexFindMode	= 0x02,
50 };
51 
52 class Settings {
53 	public:
54 		Settings();
55 		~Settings();
56 
57 		const BMessage &Message() const { return fMessage; }
58 		void UpdateFrom(BMessage *message);
59 
60 	private:
61 		status_t Open(BFile *file, int32 mode);
62 
63 		BMessage	fMessage;
64 		bool		fUpdated;
65 };
66 
67 
68 class DiskProbe : public BApplication {
69 	public:
70 		DiskProbe();
71 		virtual ~DiskProbe();
72 
73 		virtual void ReadyToRun();
74 
75 		virtual void RefsReceived(BMessage *message);
76 		virtual void ArgvReceived(int32 argc, char **argv);
77 		virtual void MessageReceived(BMessage *message);
78 
79 		virtual void AboutRequested();
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 		BCatalog	fAppCatalog;
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 	fAppCatalog(NULL)
241 {
242 	be_locale->GetAppCatalog(&fAppCatalog);
243 	fFilePanel = new BFilePanel();
244 	fWindowFrame = fSettings.Message().FindRect("window_frame");
245 }
246 
247 
248 DiskProbe::~DiskProbe()
249 {
250 	delete fFilePanel;
251 }
252 
253 
254 void
255 DiskProbe::ReadyToRun()
256 {
257 	// are there already windows open?
258 	if (CountWindows() != 1)
259 		return;
260 
261 	// if not, ask the user to open a file
262 	PostMessage(kMsgOpenOpenWindow);
263 }
264 
265 
266 /** Opens a window containing the file pointed to by the entry_ref.
267  *	This function will fail if that file doesn't exist or could not
268  *	be opened.
269  *	It will check if there already is a window that probes the
270  *	file in question and will activate it in that case.
271  *	This function must be called with the application looper locked.
272  */
273 
274 status_t
275 DiskProbe::Probe(BEntry &entry, const char *attribute)
276 {
277 	entry_ref ref;
278 	status_t status = entry.GetRef(&ref);
279 	if (status < B_OK)
280 		return status;
281 
282 	ProbeWindow *lastWindow(NULL);
283 
284 	// Do we already have that window open?
285 	for (int32 i = CountWindows(); i-- > 0; ) {
286 		ProbeWindow *window = dynamic_cast<ProbeWindow *>(WindowAt(i));
287 		if (window == NULL)
288 			continue;
289 
290 		if (window->Contains(ref, attribute)) {
291 			window->Activate(true);
292 			return B_OK;
293 		}
294 		if (lastWindow == NULL)
295 			lastWindow = window;
296 	}
297 
298 	// Does the file really exist?
299 	if (!entry.Exists())
300 		return B_ENTRY_NOT_FOUND;
301 
302 	entry.GetRef(&ref);
303 
304 	// cascade window
305 	BRect rect;
306 	if (lastWindow != NULL)
307 		rect = lastWindow->Frame();
308 	else
309 		rect = fWindowFrame;
310 
311 	rect.OffsetBy(kCascadeOffset, kCascadeOffset);
312 
313 	BWindow *window;
314 	if (attribute != NULL)
315 		window = new AttributeWindow(rect, &ref, attribute, &fSettings.Message());
316 	else
317 		window = new FileWindow(rect, &ref, &fSettings.Message());
318 
319 	window->Show();
320 
321 	/* adjust the cascading... we can only do this after the window was created
322 	 * to adjust to the real size */
323 	rect.right = window->Frame().right;
324 	rect.bottom = window->Frame().bottom;
325 
326 	BScreen screen;
327 	BRect screenBorder = screen.Frame();
328 
329 	float left = rect.left;
330 	if (left + rect.Width() > screenBorder.right)
331 		left = 7;
332 
333 	float top = rect.top;
334 	if (top + rect.Height() > screenBorder.bottom)
335 		top = 26;
336 
337 	rect.OffsetTo(BPoint(left, top));
338 	window->MoveTo(BPoint(left, top));
339 
340 	fWindowCount++;
341 
342 	return B_OK;
343 }
344 
345 
346 void
347 DiskProbe::RefsReceived(BMessage *message)
348 {
349 	bool traverseLinks = (modifiers() & B_SHIFT_KEY) == 0;
350 
351 	int32 index = 0;
352 	entry_ref ref;
353 	while (message->FindRef("refs", index++, &ref) == B_OK) {
354 		const char *attribute = NULL;
355 		if (message->FindString("attributes", index - 1, &attribute) == B_OK)
356 			traverseLinks = false;
357 
358 		BEntry entry;
359 		status_t status = entry.SetTo(&ref, traverseLinks);
360 
361 		if (status == B_OK)
362 			status = Probe(entry, attribute);
363 
364 		if (status != B_OK) {
365 			char buffer[1024];
366 			snprintf(buffer, sizeof(buffer),
367 				B_TRANSLATE("Could not open \"%s\":\n"
368 				"%s"),
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 	BAlert *alert = new BAlert("about", B_TRANSLATE("DiskProbe\n"
490 		"\twritten by Axel Dörfler\n"
491 		"\tCopyright 2004-2007, Haiku.\n\n"
492 		"Original Be version by Robert Polic\n"), B_TRANSLATE("OK"));
493 	BTextView *view = alert->TextView();
494 	BFont font;
495 
496 	view->SetStylable(true);
497 
498 	view->GetFont(&font);
499 	font.SetSize(18);
500 	font.SetFace(B_BOLD_FACE);
501 	view->SetFontAndColor(0, 9, &font);
502 
503 	alert->Go();
504 }
505 
506 
507 bool
508 DiskProbe::QuitRequested()
509 {
510 	return true;
511 }
512 
513 
514 //	#pragma mark -
515 
516 
517 int
518 main(int argc, char **argv)
519 {
520 	DiskProbe probe;
521 
522 	probe.Run();
523 	return 0;
524 }
525