xref: /haiku/src/kits/tracker/infowindow/InfoWindow.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 
36 #include "InfoWindow.h"
37 
38 #include <string.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 
42 #include <Alert.h>
43 #include <Catalog.h>
44 #include <Debug.h>
45 #include <Directory.h>
46 #include <File.h>
47 #include <Font.h>
48 #include <Locale.h>
49 #include <MenuField.h>
50 #include <Mime.h>
51 #include <NodeInfo.h>
52 #include <NodeMonitor.h>
53 #include <Path.h>
54 #include <PopUpMenu.h>
55 #include <Region.h>
56 #include <Roster.h>
57 #include <Screen.h>
58 #include <ScrollView.h>
59 #include <StringFormat.h>
60 #include <SymLink.h>
61 #include <TabView.h>
62 #include <TextView.h>
63 #include <Volume.h>
64 #include <VolumeRoster.h>
65 
66 #include "Attributes.h"
67 #include "AttributesView.h"
68 #include "AutoLock.h"
69 #include "Commands.h"
70 #include "DialogPane.h"
71 #include "FSUtils.h"
72 #include "GeneralInfoView.h"
73 #include "IconCache.h"
74 #include "Model.h"
75 #include "NavMenu.h"
76 #include "PoseView.h"
77 #include "StringForSize.h"
78 #include "Tracker.h"
79 #include "WidgetAttributeText.h"
80 
81 
82 #undef B_TRANSLATION_CONTEXT
83 #define B_TRANSLATION_CONTEXT "InfoWindow"
84 
85 
86 const uint32 kNewTargetSelected = 'selc';
87 
88 //	#pragma mark - BInfoWindow
89 
90 
91 BInfoWindow::BInfoWindow(Model* model, int32 group_index,
92 	LockingList<BWindow>* list)
93 	:
94 	BWindow(BInfoWindow::InfoWindowRect(),
95 		"InfoWindow", B_TITLED_WINDOW,
96 		B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS,
97 		B_CURRENT_WORKSPACE),
98 	fModel(model),
99 	fStopCalc(false),
100 	fIndex(group_index),
101 	fCalcThreadID(-1),
102 	fWindowList(list),
103 	fPermissionsView(NULL),
104 	fFilePanel(NULL),
105 	fFilePanelOpen(false)
106 {
107 	SetPulseRate(1000000);
108 		// we use pulse to check freebytes on volume
109 
110 	TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this);
111 
112 	// window list is Locked by Tracker around this constructor
113 	if (list != NULL)
114 		list->AddItem(this);
115 
116 	AddShortcut('E', 0, new BMessage(kEditItem));
117 	AddShortcut('O', 0, new BMessage(kOpenSelection));
118 	AddShortcut('U', 0, new BMessage(kUnmountVolume));
119 	AddShortcut('P', 0, new BMessage(kPermissionsSelected));
120 
121 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0);
122 	SetLayout(layout);
123 
124 	BModelOpener modelOpener(TargetModel());
125 	if (TargetModel()->InitCheck() != B_OK)
126 		return;
127 
128 	fHeaderView = new HeaderView(TargetModel());
129 	AddChild(fHeaderView);
130 	BTabView* tabView = new BTabView("tabs");
131 	tabView->SetBorder(B_NO_BORDER);
132 	AddChild(tabView);
133 
134 	fGeneralInfoView = new GeneralInfoView(TargetModel());
135 	tabView->AddTab(fGeneralInfoView);
136 
137 	BRect permissionsBounds(0,
138 		fGeneralInfoView->Bounds().bottom,
139 		fGeneralInfoView->Bounds().right,
140 		fGeneralInfoView->Bounds().bottom + 103);
141 
142 	fPermissionsView = new FilePermissionsView(
143 		permissionsBounds, fModel);
144 	tabView->AddTab(fPermissionsView);
145 
146 	tabView->AddTab(new AttributesView(TargetModel()));
147 
148 	// This window accepts messages before being shown, so let's start the
149 	// looper immediately.
150 	Run();
151 }
152 
153 
154 BInfoWindow::~BInfoWindow()
155 {
156 	// Check to make sure the file panel is destroyed
157 	delete fFilePanel;
158 	delete fModel;
159 }
160 
161 
162 BRect
163 BInfoWindow::InfoWindowRect()
164 {
165 	// starting size of window
166 	return BRect(70, 50, 385, 240);
167 }
168 
169 
170 void
171 BInfoWindow::Quit()
172 {
173 	stop_watching(this);
174 
175 	if (fWindowList) {
176 		AutoLock<LockingList<BWindow> > lock(fWindowList);
177 		fWindowList->RemoveItem(this);
178 	}
179 
180 	fStopCalc = true;
181 
182 	// wait until CalcSize thread has terminated before closing window
183 	status_t result;
184 	wait_for_thread(fCalcThreadID, &result);
185 
186 	_inherited::Quit();
187 }
188 
189 
190 bool
191 BInfoWindow::IsShowing(const node_ref* node) const
192 {
193 	return *TargetModel()->NodeRef() == *node;
194 }
195 
196 
197 void
198 BInfoWindow::Show()
199 {
200 	if (TargetModel()->InitCheck() != B_OK) {
201 		Close();
202 		return;
203 	}
204 
205 	AutoLock<BWindow> lock(this);
206 
207 	// position window appropriately based on index
208 	BRect windRect(InfoWindowRect());
209 	if ((fIndex + 2) % 2 == 1) {
210 		windRect.OffsetBy(320, 0);
211 		fIndex--;
212 	}
213 
214 	windRect.OffsetBy(fIndex * 8, fIndex * 8);
215 
216 	// make sure window is visible on screen
217 	BScreen screen(this);
218 	if (!windRect.Intersects(screen.Frame()))
219 		windRect.OffsetTo(50, 50);
220 
221 	MoveTo(windRect.LeftTop());
222 
223 	// volume case is handled by view
224 	if (!TargetModel()->IsVolume() && !TargetModel()->IsRoot()) {
225 		if (TargetModel()->IsDirectory()) {
226 			// if this is a folder then spawn thread to calculate size
227 			SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
228 			fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
229 				B_NORMAL_PRIORITY, this);
230 			resume_thread(fCalcThreadID);
231 		} else {
232 			fGeneralInfoView->SetLastSize(TargetModel()->StatBuf()->st_size);
233 
234 			BString sizeStr;
235 			GetSizeString(sizeStr, fGeneralInfoView->LastSize(), 0);
236 			SetSizeString(sizeStr.String());
237 		}
238 	}
239 
240 	BString buffer(B_TRANSLATE_COMMENT("%name info", "InfoWindow Title"));
241 	buffer.ReplaceFirst("%name", TargetModel()->Name());
242 	SetTitle(buffer.String());
243 
244 	lock.Unlock();
245 	_inherited::Show();
246 }
247 
248 
249 void
250 BInfoWindow::MessageReceived(BMessage* message)
251 {
252 	switch (message->what) {
253 		case kRestoreState:
254 			Show();
255 			break;
256 
257 		case kOpenSelection:
258 		{
259 			BMessage refsMessage(B_REFS_RECEIVED);
260 			refsMessage.AddRef("refs", fModel->EntryRef());
261 
262 			// add a messenger to the launch message that will be used to
263 			// dispatch scripting calls from apps to the PoseView
264 			refsMessage.AddMessenger("TrackerViewToken", BMessenger(this));
265 			be_app->PostMessage(&refsMessage);
266 			break;
267 		}
268 
269 		case kEditItem:
270 		{
271 			BEntry entry(fModel->EntryRef());
272 			fHeaderView->BeginEditingTitle();
273 			break;
274 		}
275 
276 		case kIdentifyEntry:
277 		{
278 			bool force = (modifiers() & B_OPTION_KEY) != 0;
279 			BEntry entry;
280 			if (entry.SetTo(fModel->EntryRef(), true) == B_OK) {
281 				BPath path;
282 				if (entry.GetPath(&path) == B_OK)
283 					update_mime_info(path.Path(), true, false, force ? 2 : 1);
284 			}
285 			break;
286 		}
287 
288 		case kRecalculateSize:
289 		{
290 			fStopCalc = true;
291 			// Wait until any current CalcSize thread has terminated before
292 			// starting a new one
293 			status_t result;
294 			wait_for_thread(fCalcThreadID, &result);
295 
296 			// Start recalculating..
297 			fStopCalc = false;
298 			SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
299 			fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
300 				B_NORMAL_PRIORITY, this);
301 			resume_thread(fCalcThreadID);
302 			break;
303 		}
304 
305 		case kSetLinkTarget:
306 			OpenFilePanel(fModel->EntryRef());
307 			break;
308 
309 		// An item was dropped into the window
310 		case B_SIMPLE_DATA:
311 			// If we are not a SymLink, just ignore the request
312 			if (!fModel->IsSymLink())
313 				break;
314 			// supposed to fall through
315 		// An item was selected from the file panel
316 		// fall-through
317 		case kNewTargetSelected:
318 		{
319 			// Extract the BEntry, and set its full path to the string value
320 			BEntry targetEntry;
321 			entry_ref ref;
322 			BPath path;
323 
324 			if (message->FindRef("refs", &ref) == B_OK
325 				&& targetEntry.SetTo(&ref, true) == B_OK
326 				&& targetEntry.Exists()) {
327 				// We now have to re-target the broken symlink. Unfortunately,
328 				// there's no way to change the target of an existing symlink.
329 				// So we have to delete the old one and create a new one.
330 				// First, stop watching the broken node
331 				// (we don't want this window to quit when the node
332 				// is removed.)
333 				stop_watching(this);
334 
335 				// Get the parent
336 				BDirectory parent;
337 				BEntry tmpEntry(TargetModel()->EntryRef());
338 				if (tmpEntry.GetParent(&parent) != B_OK)
339 					break;
340 
341 				// Preserve the name
342 				BString name(TargetModel()->Name());
343 
344 				// Extract path for new target
345 				BEntry target(&ref);
346 				BPath targetPath;
347 				if (target.GetPath(&targetPath) != B_OK)
348 					break;
349 
350 				// Preserve the original attributes
351 				AttributeStreamMemoryNode memoryNode;
352 				{
353 					BModelOpener opener(TargetModel());
354 					AttributeStreamFileNode original(TargetModel()->Node());
355 					memoryNode << original;
356 				}
357 
358 				// Delete the broken node.
359 				BEntry oldEntry(TargetModel()->EntryRef());
360 				oldEntry.Remove();
361 
362 				// Create new node
363 				BSymLink link;
364 				parent.CreateSymLink(name.String(), targetPath.Path(), &link);
365 
366 				// Update our Model()
367 				BEntry symEntry(&parent, name.String());
368 				fModel->SetTo(&symEntry);
369 
370 				BModelWriteOpener opener(TargetModel());
371 
372 				// Copy the attributes back
373 				AttributeStreamFileNode newNode(TargetModel()->Node());
374 				newNode << memoryNode;
375 
376 				// Start watching this again
377 				TTracker::WatchNode(TargetModel()->NodeRef(),
378 					B_WATCH_ALL | B_WATCH_MOUNT, this);
379 
380 				// Tell the attribute view about this new model
381 				fGeneralInfoView->ReLinkTargetModel(TargetModel());
382 				fHeaderView->ReLinkTargetModel(TargetModel());
383 			}
384 			break;
385 		}
386 
387 		case B_CANCEL:
388 			// File panel window has closed
389 			delete fFilePanel;
390 			fFilePanel = NULL;
391 			// It's no longer open
392 			fFilePanelOpen = false;
393 			break;
394 
395 		case kUnmountVolume:
396 			// Sanity check that this isn't the boot volume
397 			// (The unmount menu item has been disabled in this
398 			// case, but the shortcut is still active)
399 			if (fModel->IsVolume()) {
400 				BVolume boot;
401 				BVolumeRoster().GetBootVolume(&boot);
402 				BVolume volume(fModel->NodeRef()->device);
403 				if (volume != boot) {
404 					TTracker* tracker = dynamic_cast<TTracker*>(be_app);
405 					if (tracker != NULL)
406 						tracker->SaveAllPoseLocations();
407 
408 					BMessage unmountMessage(kUnmountVolume);
409 					unmountMessage.AddInt32("device_id", volume.Device());
410 					be_app->PostMessage(&unmountMessage);
411 				}
412 			}
413 			break;
414 
415 		case kEmptyTrash:
416 			FSEmptyTrash();
417 			break;
418 
419 		case B_NODE_MONITOR:
420 			switch (message->FindInt32("opcode")) {
421 				case B_ENTRY_REMOVED:
422 				{
423 					node_ref itemNode;
424 					message->FindInt32("device", &itemNode.device);
425 					message->FindInt64("node", &itemNode.node);
426 					// our window itself may be deleted
427 					if (*TargetModel()->NodeRef() == itemNode)
428 						Close();
429 					break;
430 				}
431 
432 				case B_ENTRY_MOVED:
433 				case B_STAT_CHANGED:
434 				case B_ATTR_CHANGED:
435 					fGeneralInfoView->ModelChanged(TargetModel(), message);
436 						// must be called before the
437 						// FilePermissionView::ModelChanged()
438 						// call, because it changes the model...
439 						// (bad style!)
440 					fHeaderView->ModelChanged(TargetModel(), message);
441 
442 					if (fPermissionsView != NULL)
443 						fPermissionsView->ModelChanged(TargetModel());
444 					break;
445 
446 				case B_DEVICE_UNMOUNTED:
447 				{
448 					// We were watching a volume that is no longer
449 					// mounted, we might as well quit
450 					node_ref itemNode;
451 					// Only the device information is available
452 					message->FindInt32("device", &itemNode.device);
453 					if (TargetModel()->NodeRef()->device == itemNode.device)
454 						Close();
455 					break;
456 				}
457 
458 				default:
459 					break;
460 			}
461 			break;
462 
463 		case kPermissionsSelected:
464 		{
465 			BTabView* tabView = (BTabView*)FindView("tabs");
466 			tabView->Select(1);
467 			break;
468 		}
469 
470 		default:
471 			_inherited::MessageReceived(message);
472 			break;
473 	}
474 }
475 
476 
477 void
478 BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount)
479 {
480 	static BStringFormat sizeFormat(B_TRANSLATE(
481 		"{0, plural, one{(# byte)} other{(# bytes)}}"));
482 	static BStringFormat countFormat(B_TRANSLATE(
483 		"{0, plural, one{for # file} other{for # files}}"));
484 
485 	char sizeBuffer[128];
486 	result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer));
487 
488 	if (size >= kKBSize) {
489 		result << " ";
490 
491 		sizeFormat.Format(result, size);
492 			// "bytes" translation could come from string_for_size
493 			// which could be part of the localekit itself
494 	}
495 
496 	if (fileCount != 0) {
497 		result << " ";
498 		countFormat.Format(result, fileCount);
499 	}
500 }
501 
502 
503 int32
504 BInfoWindow::CalcSize(void* castToWindow)
505 {
506 	BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow);
507 	BDirectory dir(window->TargetModel()->EntryRef());
508 	BDirectory trashDir;
509 	FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device);
510 	if (dir.InitCheck() != B_OK) {
511 		if (window->StopCalc())
512 			return B_ERROR;
513 
514 		AutoLock<BWindow> lock(window);
515 		if (!lock)
516 			return B_ERROR;
517 
518 		window->SetSizeString(B_TRANSLATE("Error calculating folder size."));
519 		return B_ERROR;
520 	}
521 
522 	BEntry dirEntry, trashEntry;
523 	dir.GetEntry(&dirEntry);
524 	trashDir.GetEntry(&trashEntry);
525 
526 	BString sizeString;
527 
528 	// check if user has asked for trash dir info
529 	if (dirEntry != trashEntry) {
530 		// if not, perform normal info calculations
531 		off_t size = 0;
532 		int32 fileCount = 0;
533 		int32 dirCount = 0;
534 		CopyLoopControl loopControl;
535 		FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount,
536 			&dirCount);
537 
538 		// got the size value, update the size string
539 		GetSizeString(sizeString, size, fileCount);
540 	} else {
541 		// in the trash case, iterate through and sum up
542 		// size/counts for all present trash dirs
543 		off_t totalSize = 0, currentSize;
544 		int32 totalFileCount = 0, currentFileCount;
545 		int32 totalDirCount = 0, currentDirCount;
546 		BVolumeRoster volRoster;
547 		volRoster.Rewind();
548 		BVolume volume;
549 		while (volRoster.GetNextVolume(&volume) == B_OK) {
550 			if (!volume.IsPersistent())
551 				continue;
552 
553 			currentSize = 0;
554 			currentFileCount = 0;
555 			currentDirCount = 0;
556 
557 			BDirectory trashDir;
558 			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
559 				CopyLoopControl loopControl;
560 				FSRecursiveCalcSize(window, &loopControl, &trashDir,
561 					&currentSize, &currentFileCount, &currentDirCount);
562 				totalSize += currentSize;
563 				totalFileCount += currentFileCount;
564 				totalDirCount += currentDirCount;
565 			}
566 		}
567 		GetSizeString(sizeString, totalSize, totalFileCount);
568 	}
569 
570 	if (window->StopCalc()) {
571 		// window closed, bail
572 		return B_OK;
573 	}
574 
575 	AutoLock<BWindow> lock(window);
576 	if (lock.IsLocked())
577 		window->SetSizeString(sizeString.String());
578 
579 	return B_OK;
580 }
581 
582 
583 void
584 BInfoWindow::SetSizeString(const char* sizeString)
585 {
586 	fGeneralInfoView->SetSizeString(sizeString);
587 }
588 
589 
590 void
591 BInfoWindow::OpenFilePanel(const entry_ref* ref)
592 {
593 	// Open a file dialog box to allow the user to select a new target
594 	// for the sym link
595 	if (fFilePanel == NULL) {
596 		BMessenger runner(this);
597 		BMessage message(kNewTargetSelected);
598 		fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref,
599 			B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE,
600 			false, &message);
601 
602 		if (fFilePanel != NULL) {
603 			fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,
604 				B_TRANSLATE("Select"));
605 			fFilePanel->Window()->ResizeTo(500, 300);
606 			BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:",
607 				"File dialog title for new sym link"));
608 			title.ReplaceFirst("%name", fModel->Name());
609 			fFilePanel->Window()->SetTitle(title.String());
610 			fFilePanel->Show();
611 			fFilePanelOpen = true;
612 		}
613 	} else if (!fFilePanelOpen) {
614 		fFilePanel->Show();
615 		fFilePanelOpen = true;
616 	} else {
617 		fFilePanelOpen = true;
618 		fFilePanel->Window()->Activate(true);
619 	}
620 }
621 
622 
623