xref: /haiku/src/kits/tracker/infowindow/InfoWindow.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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 			if (!fModel->HasLocalizedName()
273 				&& ConfirmChangeIfWellKnownDirectory(&entry, kRename)) {
274 				fHeaderView->BeginEditingTitle();
275 			}
276 			break;
277 		}
278 
279 		case kIdentifyEntry:
280 		{
281 			bool force = (modifiers() & B_OPTION_KEY) != 0;
282 			BEntry entry;
283 			if (entry.SetTo(fModel->EntryRef(), true) == B_OK) {
284 				BPath path;
285 				if (entry.GetPath(&path) == B_OK)
286 					update_mime_info(path.Path(), true, false, force ? 2 : 1);
287 			}
288 			break;
289 		}
290 
291 		case kRecalculateSize:
292 		{
293 			fStopCalc = true;
294 			// Wait until any current CalcSize thread has terminated before
295 			// starting a new one
296 			status_t result;
297 			wait_for_thread(fCalcThreadID, &result);
298 
299 			// Start recalculating..
300 			fStopCalc = false;
301 			SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
302 			fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
303 				B_NORMAL_PRIORITY, this);
304 			resume_thread(fCalcThreadID);
305 			break;
306 		}
307 
308 		case kSetLinkTarget:
309 			OpenFilePanel(fModel->EntryRef());
310 			break;
311 
312 		// An item was dropped into the window
313 		case B_SIMPLE_DATA:
314 			// If we are not a SymLink, just ignore the request
315 			if (!fModel->IsSymLink())
316 				break;
317 			// supposed to fall through
318 		// An item was selected from the file panel
319 		// fall-through
320 		case kNewTargetSelected:
321 		{
322 			// Extract the BEntry, and set its full path to the string value
323 			BEntry targetEntry;
324 			entry_ref ref;
325 			BPath path;
326 
327 			if (message->FindRef("refs", &ref) == B_OK
328 				&& targetEntry.SetTo(&ref, true) == B_OK
329 				&& targetEntry.Exists()) {
330 				// We now have to re-target the broken symlink. Unfortunately,
331 				// there's no way to change the target of an existing symlink.
332 				// So we have to delete the old one and create a new one.
333 				// First, stop watching the broken node
334 				// (we don't want this window to quit when the node
335 				// is removed.)
336 				stop_watching(this);
337 
338 				// Get the parent
339 				BDirectory parent;
340 				BEntry tmpEntry(TargetModel()->EntryRef());
341 				if (tmpEntry.GetParent(&parent) != B_OK)
342 					break;
343 
344 				// Preserve the name
345 				BString name(TargetModel()->Name());
346 
347 				// Extract path for new target
348 				BEntry target(&ref);
349 				BPath targetPath;
350 				if (target.GetPath(&targetPath) != B_OK)
351 					break;
352 
353 				// Preserve the original attributes
354 				AttributeStreamMemoryNode memoryNode;
355 				{
356 					BModelOpener opener(TargetModel());
357 					AttributeStreamFileNode original(TargetModel()->Node());
358 					memoryNode << original;
359 				}
360 
361 				// Delete the broken node.
362 				BEntry oldEntry(TargetModel()->EntryRef());
363 				oldEntry.Remove();
364 
365 				// Create new node
366 				BSymLink link;
367 				parent.CreateSymLink(name.String(), targetPath.Path(), &link);
368 
369 				// Update our Model()
370 				BEntry symEntry(&parent, name.String());
371 				fModel->SetTo(&symEntry);
372 
373 				BModelWriteOpener opener(TargetModel());
374 
375 				// Copy the attributes back
376 				AttributeStreamFileNode newNode(TargetModel()->Node());
377 				newNode << memoryNode;
378 
379 				// Start watching this again
380 				TTracker::WatchNode(TargetModel()->NodeRef(),
381 					B_WATCH_ALL | B_WATCH_MOUNT, this);
382 
383 				// Tell the attribute view about this new model
384 				fGeneralInfoView->ReLinkTargetModel(TargetModel());
385 				fHeaderView->ReLinkTargetModel(TargetModel());
386 			}
387 			break;
388 		}
389 
390 		case B_CANCEL:
391 			// File panel window has closed
392 			delete fFilePanel;
393 			fFilePanel = NULL;
394 			// It's no longer open
395 			fFilePanelOpen = false;
396 			break;
397 
398 		case kUnmountVolume:
399 			// Sanity check that this isn't the boot volume
400 			// (The unmount menu item has been disabled in this
401 			// case, but the shortcut is still active)
402 			if (fModel->IsVolume()) {
403 				BVolume boot;
404 				BVolumeRoster().GetBootVolume(&boot);
405 				BVolume volume(fModel->NodeRef()->device);
406 				if (volume != boot) {
407 					TTracker* tracker = dynamic_cast<TTracker*>(be_app);
408 					if (tracker != NULL)
409 						tracker->SaveAllPoseLocations();
410 
411 					BMessage unmountMessage(kUnmountVolume);
412 					unmountMessage.AddInt32("device_id", volume.Device());
413 					be_app->PostMessage(&unmountMessage);
414 				}
415 			}
416 			break;
417 
418 		case kEmptyTrash:
419 			FSEmptyTrash();
420 			break;
421 
422 		case B_NODE_MONITOR:
423 			switch (message->FindInt32("opcode")) {
424 				case B_ENTRY_REMOVED:
425 				{
426 					node_ref itemNode;
427 					message->FindInt32("device", &itemNode.device);
428 					message->FindInt64("node", &itemNode.node);
429 					// our window itself may be deleted
430 					if (*TargetModel()->NodeRef() == itemNode)
431 						Close();
432 					break;
433 				}
434 
435 				case B_ENTRY_MOVED:
436 				case B_STAT_CHANGED:
437 				case B_ATTR_CHANGED:
438 					fGeneralInfoView->ModelChanged(TargetModel(), message);
439 						// must be called before the
440 						// FilePermissionView::ModelChanged()
441 						// call, because it changes the model...
442 						// (bad style!)
443 					fHeaderView->ModelChanged(TargetModel(), message);
444 
445 					if (fPermissionsView != NULL)
446 						fPermissionsView->ModelChanged(TargetModel());
447 					break;
448 
449 				case B_DEVICE_UNMOUNTED:
450 				{
451 					// We were watching a volume that is no longer
452 					// mounted, we might as well quit
453 					node_ref itemNode;
454 					// Only the device information is available
455 					message->FindInt32("device", &itemNode.device);
456 					if (TargetModel()->NodeRef()->device == itemNode.device)
457 						Close();
458 					break;
459 				}
460 
461 				default:
462 					break;
463 			}
464 			break;
465 
466 		case kPermissionsSelected:
467 		{
468 			BTabView* tabView = (BTabView*)FindView("tabs");
469 			tabView->Select(1);
470 			break;
471 		}
472 
473 		default:
474 			_inherited::MessageReceived(message);
475 			break;
476 	}
477 }
478 
479 
480 void
481 BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount)
482 {
483 	static BStringFormat sizeFormat(B_TRANSLATE(
484 		"{0, plural, one{(# byte)} other{(# bytes)}}"));
485 	static BStringFormat countFormat(B_TRANSLATE(
486 		"{0, plural, one{for # file} other{for # files}}"));
487 
488 	char sizeBuffer[128];
489 	result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer));
490 
491 	if (size >= kKBSize) {
492 		result << " ";
493 
494 		sizeFormat.Format(result, size);
495 			// "bytes" translation could come from string_for_size
496 			// which could be part of the localekit itself
497 	}
498 
499 	if (fileCount != 0) {
500 		result << " ";
501 		countFormat.Format(result, fileCount);
502 	}
503 }
504 
505 
506 int32
507 BInfoWindow::CalcSize(void* castToWindow)
508 {
509 	BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow);
510 	BDirectory dir(window->TargetModel()->EntryRef());
511 	BDirectory trashDir;
512 	FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device);
513 	if (dir.InitCheck() != B_OK) {
514 		if (window->StopCalc())
515 			return B_ERROR;
516 
517 		AutoLock<BWindow> lock(window);
518 		if (!lock)
519 			return B_ERROR;
520 
521 		window->SetSizeString(B_TRANSLATE("Error calculating folder size."));
522 		return B_ERROR;
523 	}
524 
525 	BEntry dirEntry, trashEntry;
526 	dir.GetEntry(&dirEntry);
527 	trashDir.GetEntry(&trashEntry);
528 
529 	BString sizeString;
530 
531 	// check if user has asked for trash dir info
532 	if (dirEntry != trashEntry) {
533 		// if not, perform normal info calculations
534 		off_t size = 0;
535 		int32 fileCount = 0;
536 		int32 dirCount = 0;
537 		CopyLoopControl loopControl;
538 		FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount,
539 			&dirCount);
540 
541 		// got the size value, update the size string
542 		GetSizeString(sizeString, size, fileCount);
543 	} else {
544 		// in the trash case, iterate through and sum up
545 		// size/counts for all present trash dirs
546 		off_t totalSize = 0, currentSize;
547 		int32 totalFileCount = 0, currentFileCount;
548 		int32 totalDirCount = 0, currentDirCount;
549 		BVolumeRoster volRoster;
550 		volRoster.Rewind();
551 		BVolume volume;
552 		while (volRoster.GetNextVolume(&volume) == B_OK) {
553 			if (!volume.IsPersistent())
554 				continue;
555 
556 			currentSize = 0;
557 			currentFileCount = 0;
558 			currentDirCount = 0;
559 
560 			BDirectory trashDir;
561 			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
562 				CopyLoopControl loopControl;
563 				FSRecursiveCalcSize(window, &loopControl, &trashDir,
564 					&currentSize, &currentFileCount, &currentDirCount);
565 				totalSize += currentSize;
566 				totalFileCount += currentFileCount;
567 				totalDirCount += currentDirCount;
568 			}
569 		}
570 		GetSizeString(sizeString, totalSize, totalFileCount);
571 	}
572 
573 	if (window->StopCalc()) {
574 		// window closed, bail
575 		return B_OK;
576 	}
577 
578 	AutoLock<BWindow> lock(window);
579 	if (lock.IsLocked())
580 		window->SetSizeString(sizeString.String());
581 
582 	return B_OK;
583 }
584 
585 
586 void
587 BInfoWindow::SetSizeString(const char* sizeString)
588 {
589 	fGeneralInfoView->SetSizeString(sizeString);
590 }
591 
592 
593 void
594 BInfoWindow::OpenFilePanel(const entry_ref* ref)
595 {
596 	// Open a file dialog box to allow the user to select a new target
597 	// for the sym link
598 	if (fFilePanel == NULL) {
599 		BMessenger runner(this);
600 		BMessage message(kNewTargetSelected);
601 		fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref,
602 			B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE,
603 			false, &message);
604 
605 		if (fFilePanel != NULL) {
606 			fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,
607 				B_TRANSLATE("Select"));
608 			fFilePanel->Window()->ResizeTo(500, 300);
609 			BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:",
610 				"File dialog title for new sym link"));
611 			title.ReplaceFirst("%name", fModel->Name());
612 			fFilePanel->Window()->SetTitle(title.String());
613 			fFilePanel->Show();
614 			fFilePanelOpen = true;
615 		}
616 	} else if (!fFilePanelOpen) {
617 		fFilePanel->Show();
618 		fFilePanelOpen = true;
619 	} else {
620 		fFilePanelOpen = true;
621 		fFilePanel->Window()->Activate(true);
622 	}
623 }
624 
625 
626