btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / kits / tracker / InfoWindow.cpp
blob6ade764aeee0bf6fa3efde5775654ad4b47f3566
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
36 #include "InfoWindow.h"
38 #include <string.h>
39 #include <stdio.h>
40 #include <stdlib.h>
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 <MessageFormat.h>
51 #include <Mime.h>
52 #include <NodeInfo.h>
53 #include <NodeMonitor.h>
54 #include <Path.h>
55 #include <PopUpMenu.h>
56 #include <Region.h>
57 #include <Roster.h>
58 #include <Screen.h>
59 #include <ScrollView.h>
60 #include <SymLink.h>
61 #include <TextView.h>
62 #include <Volume.h>
63 #include <VolumeRoster.h>
65 #include "Attributes.h"
66 #include "AutoLock.h"
67 #include "Commands.h"
68 #include "DialogPane.h"
69 #include "FSUtils.h"
70 #include "IconCache.h"
71 #include "IconMenuItem.h"
72 #include "Model.h"
73 #include "NavMenu.h"
74 #include "PoseView.h"
75 #include "StringForSize.h"
76 #include "Tracker.h"
77 #include "WidgetAttributeText.h"
80 #undef B_TRANSLATION_CONTEXT
81 #define B_TRANSLATION_CONTEXT "InfoWindow"
84 namespace BPrivate {
86 // States for tracking the mouse
87 enum track_state {
88 no_track = 0,
89 link_track,
90 path_track,
91 icon_track,
92 size_track,
93 open_only_track
94 // This is for items that can be opened, but can't be
95 // drag and dropped or renamed (Trash, Desktop Folder...)
99 class TrackingView : public BControl {
100 public:
101 TrackingView(BRect, const char* str, BMessage* message);
103 virtual void MouseDown(BPoint);
104 virtual void MouseMoved(BPoint where, uint32 transit, const BMessage*);
105 virtual void MouseUp(BPoint);
106 virtual void Draw(BRect);
108 private:
109 bool fMouseDown;
110 bool fMouseInView;
114 class AttributeView : public BView {
115 public:
116 AttributeView(BRect, Model*);
117 ~AttributeView();
119 void ModelChanged(Model*, BMessage*);
120 void ReLinkTargetModel(Model*);
121 void BeginEditingTitle();
122 void FinishEditingTitle(bool);
123 float CurrentFontHeight();
125 BTextView* TextView() const { return fTitleEditView; }
127 static filter_result TextViewFilter(BMessage*, BHandler**,
128 BMessageFilter*);
130 off_t LastSize() const;
131 void SetLastSize(off_t);
133 void SetSizeString(const char*);
135 status_t BuildContextMenu(BMenu* parent);
137 void SetPermissionsSwitchState(int32 state);
139 protected:
140 virtual void MouseDown(BPoint where);
141 virtual void MouseMoved(BPoint where, uint32, const BMessage* dragMessage);
142 virtual void MouseUp(BPoint where);
143 virtual void MessageReceived(BMessage* message);
144 virtual void AttachedToWindow();
145 virtual void Draw(BRect);
146 virtual void Pulse();
147 virtual void MakeFocus(bool focus);
148 virtual void WindowActivated(bool active);
150 private:
151 void InitStrings(const Model*);
152 void CheckAndSetSize();
153 void OpenLinkSource();
154 void OpenLinkTarget();
156 BString fPathStr;
157 BString fLinkToStr;
158 BString fSizeString;
159 BString fModifiedStr;
160 BString fCreatedStr;
161 BString fKindStr;
162 BString fDescStr;
164 off_t fFreeBytes;
165 off_t fLastSize;
167 BRect fPathRect;
168 BRect fLinkRect;
169 BRect fDescRect;
170 BRect fTitleRect;
171 BRect fIconRect;
172 BRect fSizeRect;
173 BPoint fClickPoint;
174 float fDivider;
176 BMenuField* fPreferredAppMenu;
177 Model* fModel;
178 Model* fIconModel;
179 BBitmap* fIcon;
180 bool fMouseDown;
181 bool fDragging;
182 bool fDoubleClick;
183 track_state fTrackingState;
184 bool fIsDropTarget;
185 BTextView* fTitleEditView;
186 PaneSwitch* fPermissionsSwitch;
187 BWindow* fPathWindow;
188 BWindow* fLinkWindow;
189 BWindow* fDescWindow;
190 color_which fCurrentLinkColorWhich;
191 color_which fCurrentPathColorWhich;
193 typedef BView _inherited;
196 } // namespace BPrivate
199 const float kDrawMargin = 3.0f;
200 const float kBorderMargin = 15.0f;
201 const float kBorderWidth = 32.0f;
203 // Offsets taken from TAlertView::Draw in BAlert.cpp
204 const float kIconHorizOffset = 18.0f;
205 const float kIconVertOffset = 6.0f;
207 // Amount you have to move the mouse before a drag starts
208 const float kDragSlop = 3.0f;
210 const uint32 kSetPreferredApp = 'setp';
211 const uint32 kSelectNewSymTarget = 'snew';
212 const uint32 kNewTargetSelected = 'selc';
213 const uint32 kRecalculateSize = 'resz';
214 const uint32 kSetLinkTarget = 'link';
215 const uint32 kPermissionsSelected = 'sepe';
216 const uint32 kOpenLinkSource = 'opls';
217 const uint32 kOpenLinkTarget = 'oplt';
219 const uint32 kPaneSwitchClosed = 0;
220 const uint32 kPaneSwitchOpen = 2;
223 static void
224 OpenParentAndSelectOriginal(const entry_ref* ref)
226 BEntry entry(ref);
227 node_ref node;
228 entry.GetNodeRef(&node);
230 BEntry parent;
231 entry.GetParent(&parent);
232 entry_ref parentRef;
233 parent.GetRef(&parentRef);
235 BMessage message(B_REFS_RECEIVED);
236 message.AddRef("refs", &parentRef);
237 message.AddData("nodeRefToSelect", B_RAW_TYPE, &node, sizeof(node_ref));
239 be_app->PostMessage(&message);
243 static BWindow*
244 OpenToolTipWindow(BScreen& screen, BRect rect, const char* name,
245 const char* string, BMessenger target, BMessage* message)
247 font_height fontHeight;
248 be_plain_font->GetHeight(&fontHeight);
249 float height = ceilf(fontHeight.ascent + fontHeight.descent);
250 rect.top = floorf(rect.top + (rect.Height() - height) / 2.0f);
251 rect.bottom = rect.top + height;
253 rect.right = rect.left + ceilf(be_plain_font->StringWidth(string)) + 4;
254 if (rect.left < 0)
255 rect.OffsetBy(-rect.left, 0);
256 else if (rect.right > screen.Frame().right)
257 rect.OffsetBy(screen.Frame().right - rect.right, 0);
259 BWindow* window = new BWindow(rect, name, B_BORDERED_WINDOW_LOOK,
260 B_FLOATING_ALL_WINDOW_FEEL,
261 B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
262 | B_NOT_RESIZABLE | B_AVOID_FOCUS | B_NO_WORKSPACE_ACTIVATION
263 | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS);
265 TrackingView* trackingView = new TrackingView(window->Bounds(),
266 string, message);
267 trackingView->SetTarget(target);
268 window->AddChild(trackingView);
270 window->Sync();
271 window->Show();
273 return window;
277 // #pragma mark - BInfoWindow
280 BInfoWindow::BInfoWindow(Model* model, int32 group_index,
281 LockingList<BWindow>* list)
283 BWindow(BInfoWindow::InfoWindowRect(),
284 "InfoWindow", B_TITLED_WINDOW,
285 B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_CURRENT_WORKSPACE),
286 fModel(model),
287 fStopCalc(false),
288 fIndex(group_index),
289 fCalcThreadID(-1),
290 fWindowList(list),
291 fPermissionsView(NULL),
292 fFilePanel(NULL),
293 fFilePanelOpen(false)
295 SetPulseRate(1000000);
296 // we use pulse to check freebytes on volume
298 TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this);
300 // window list is Locked by Tracker around this constructor
301 if (list != NULL)
302 list->AddItem(this);
304 AddShortcut('E', 0, new BMessage(kEditItem));
305 AddShortcut('O', 0, new BMessage(kOpenSelection));
306 AddShortcut('U', 0, new BMessage(kUnmountVolume));
307 AddShortcut('P', 0, new BMessage(kPermissionsSelected));
309 Run();
313 BInfoWindow::~BInfoWindow()
315 // Check to make sure the file panel is destroyed
316 delete fFilePanel;
317 delete fModel;
321 BRect
322 BInfoWindow::InfoWindowRect()
324 // starting size of window
325 return BRect(70, 50, 385, 240);
329 void
330 BInfoWindow::Quit()
332 stop_watching(this);
334 if (fWindowList) {
335 AutoLock<LockingList<BWindow> > lock(fWindowList);
336 fWindowList->RemoveItem(this);
339 fStopCalc = true;
341 // wait until CalcSize thread has terminated before closing window
342 status_t result;
343 wait_for_thread(fCalcThreadID, &result);
345 _inherited::Quit();
349 bool
350 BInfoWindow::IsShowing(const node_ref* node) const
352 return *TargetModel()->NodeRef() == *node;
356 void
357 BInfoWindow::Show()
359 BModelOpener modelOpener(TargetModel());
360 if (TargetModel()->InitCheck() != B_OK) {
361 Close();
362 return;
365 AutoLock<BWindow> lock(this);
367 const BFont* font = be_plain_font;
368 float width = font->StringWidth("This is a really long string which we"
369 "will use to find the window width");
371 // window height depends on file type
372 int lines = 9;
373 if (fModel->IsExecutable())
374 lines++;
375 float height = font->Size() * (lines * 2 + 1);
377 ResizeTo(width, height);
379 BRect attrRect(Bounds());
380 fAttributeView = new AttributeView(attrRect, TargetModel());
381 AddChild(fAttributeView);
383 // position window appropriately based on index
384 BRect windRect(InfoWindowRect());
385 if ((fIndex + 2) % 2 == 1) {
386 windRect.OffsetBy(320, 0);
387 fIndex--;
390 windRect.OffsetBy(fIndex * 8, fIndex * 8);
392 // make sure window is visible on screen
393 BScreen screen(this);
394 if (!windRect.Intersects(screen.Frame()))
395 windRect.OffsetTo(50, 50);
397 MoveTo(windRect.LeftTop());
399 // volume case is handled by view
400 if (!TargetModel()->IsVolume() && !TargetModel()->IsRoot()) {
401 if (TargetModel()->IsDirectory()) {
402 // if this is a folder then spawn thread to calculate size
403 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
404 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
405 B_NORMAL_PRIORITY, this);
406 resume_thread(fCalcThreadID);
407 } else {
408 fAttributeView->SetLastSize(TargetModel()->StatBuf()->st_size);
410 BString sizeStr;
411 GetSizeString(sizeStr, fAttributeView->LastSize(), 0);
412 SetSizeString(sizeStr.String());
416 BString buffer(B_TRANSLATE_COMMENT("%name info", "InfoWindow Title"));
417 buffer.ReplaceFirst("%name", TargetModel()->Name());
418 SetTitle(buffer.String());
420 lock.Unlock();
421 _inherited::Show();
425 void
426 BInfoWindow::MessageReceived(BMessage* message)
428 switch (message->what) {
429 case kRestoreState:
430 Show();
431 break;
433 case kOpenSelection:
435 BMessage refsMessage(B_REFS_RECEIVED);
436 refsMessage.AddRef("refs", fModel->EntryRef());
438 // add a messenger to the launch message that will be used to
439 // dispatch scripting calls from apps to the PoseView
440 refsMessage.AddMessenger("TrackerViewToken", BMessenger(this));
441 be_app->PostMessage(&refsMessage);
442 break;
445 case kEditItem:
447 BEntry entry(fModel->EntryRef());
448 if (!fModel->HasLocalizedName()
449 && ConfirmChangeIfWellKnownDirectory(&entry, kRename)) {
450 fAttributeView->BeginEditingTitle();
452 break;
455 case kIdentifyEntry:
457 bool force = (modifiers() & B_OPTION_KEY) != 0;
458 BEntry entry;
459 if (entry.SetTo(fModel->EntryRef(), true) == B_OK) {
460 BPath path;
461 if (entry.GetPath(&path) == B_OK)
462 update_mime_info(path.Path(), true, false, force ? 2 : 1);
464 break;
467 case kRecalculateSize:
469 fStopCalc = true;
470 // Wait until any current CalcSize thread has terminated before
471 // starting a new one
472 status_t result;
473 wait_for_thread(fCalcThreadID, &result);
475 // Start recalculating..
476 fStopCalc = false;
477 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
478 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
479 B_NORMAL_PRIORITY, this);
480 resume_thread(fCalcThreadID);
481 break;
484 case kSetLinkTarget:
485 OpenFilePanel(fModel->EntryRef());
486 break;
488 // An item was dropped into the window
489 case B_SIMPLE_DATA:
490 // If we are not a SymLink, just ignore the request
491 if (!fModel->IsSymLink())
492 break;
493 // supposed to fall through
494 // An item was selected from the file panel
495 // fall-through
496 case kNewTargetSelected:
498 // Extract the BEntry, and set its full path to the string value
499 BEntry targetEntry;
500 entry_ref ref;
501 BPath path;
503 if (message->FindRef("refs", &ref) == B_OK
504 && targetEntry.SetTo(&ref, true) == B_OK
505 && targetEntry.Exists()) {
506 // We now have to re-target the broken symlink. Unfortunately,
507 // there's no way to change the target of an existing symlink.
508 // So we have to delete the old one and create a new one.
509 // First, stop watching the broken node
510 // (we don't want this window to quit when the node
511 // is removed.)
512 stop_watching(this);
514 // Get the parent
515 BDirectory parent;
516 BEntry tmpEntry(TargetModel()->EntryRef());
517 if (tmpEntry.GetParent(&parent) != B_OK)
518 break;
520 // Preserve the name
521 BString name(TargetModel()->Name());
523 // Extract path for new target
524 BEntry target(&ref);
525 BPath targetPath;
526 if (target.GetPath(&targetPath) != B_OK)
527 break;
529 // Preserve the original attributes
530 AttributeStreamMemoryNode memoryNode;
532 BModelOpener opener(TargetModel());
533 AttributeStreamFileNode original(TargetModel()->Node());
534 memoryNode << original;
537 // Delete the broken node.
538 BEntry oldEntry(TargetModel()->EntryRef());
539 oldEntry.Remove();
541 // Create new node
542 BSymLink link;
543 parent.CreateSymLink(name.String(), targetPath.Path(), &link);
545 // Update our Model()
546 BEntry symEntry(&parent, name.String());
547 fModel->SetTo(&symEntry);
549 BModelWriteOpener opener(TargetModel());
551 // Copy the attributes back
552 AttributeStreamFileNode newNode(TargetModel()->Node());
553 newNode << memoryNode;
555 // Start watching this again
556 TTracker::WatchNode(TargetModel()->NodeRef(),
557 B_WATCH_ALL | B_WATCH_MOUNT, this);
559 // Tell the attribute view about this new model
560 fAttributeView->ReLinkTargetModel(TargetModel());
562 break;
565 case B_CANCEL:
566 // File panel window has closed
567 delete fFilePanel;
568 fFilePanel = NULL;
569 // It's no longer open
570 fFilePanelOpen = false;
571 break;
573 case kUnmountVolume:
574 // Sanity check that this isn't the boot volume
575 // (The unmount menu item has been disabled in this
576 // case, but the shortcut is still active)
577 if (fModel->IsVolume()) {
578 BVolume boot;
579 BVolumeRoster().GetBootVolume(&boot);
580 BVolume volume(fModel->NodeRef()->device);
581 if (volume != boot) {
582 TTracker* tracker = dynamic_cast<TTracker*>(be_app);
583 if (tracker != NULL)
584 tracker->SaveAllPoseLocations();
586 BMessage unmountMessage(kUnmountVolume);
587 unmountMessage.AddInt32("device_id", volume.Device());
588 be_app->PostMessage(&unmountMessage);
591 break;
593 case kEmptyTrash:
594 FSEmptyTrash();
595 break;
597 case B_NODE_MONITOR:
598 switch (message->FindInt32("opcode")) {
599 case B_ENTRY_REMOVED:
601 node_ref itemNode;
602 message->FindInt32("device", &itemNode.device);
603 message->FindInt64("node", &itemNode.node);
604 // our window itself may be deleted
605 if (*TargetModel()->NodeRef() == itemNode)
606 Close();
607 break;
610 case B_ENTRY_MOVED:
611 case B_STAT_CHANGED:
612 case B_ATTR_CHANGED:
613 fAttributeView->ModelChanged(TargetModel(), message);
614 // must be called before the
615 // FilePermissionView::ModelChanged()
616 // call, because it changes the model...
617 // (bad style!)
619 if (fPermissionsView != NULL)
620 fPermissionsView->ModelChanged(TargetModel());
621 break;
623 case B_DEVICE_UNMOUNTED:
625 // We were watching a volume that is no longer
626 // mounted, we might as well quit
627 node_ref itemNode;
628 // Only the device information is available
629 message->FindInt32("device", &itemNode.device);
630 if (TargetModel()->NodeRef()->device == itemNode.device)
631 Close();
632 break;
635 default:
636 break;
638 break;
640 case kPermissionsSelected:
642 BRect permissionsBounds(kBorderWidth + 1,
643 fAttributeView->Bounds().bottom,
644 fAttributeView->Bounds().right,
645 fAttributeView->Bounds().bottom + 103);
647 if (fPermissionsView == NULL) {
648 // Only true on first call.
649 fPermissionsView = new FilePermissionsView(
650 permissionsBounds, fModel);
651 ResizeBy(0, permissionsBounds.Height());
652 fAttributeView->AddChild(fPermissionsView);
653 fAttributeView->SetPermissionsSwitchState(kPaneSwitchOpen);
654 } else if (fPermissionsView->IsHidden()) {
655 fPermissionsView->ModelChanged(fModel);
656 fPermissionsView->Show();
657 ResizeBy(0, permissionsBounds.Height());
658 fAttributeView->SetPermissionsSwitchState(kPaneSwitchOpen);
659 } else {
660 fPermissionsView->Hide();
661 ResizeBy(0, -permissionsBounds.Height());
662 fAttributeView->SetPermissionsSwitchState(kPaneSwitchClosed);
664 break;
667 default:
668 _inherited::MessageReceived(message);
669 break;
674 void
675 BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount)
677 static BMessageFormat sizeFormat(B_TRANSLATE(
678 "{0, plural, one{(# byte)} other{(# bytes)}}"));
679 static BMessageFormat countFormat(B_TRANSLATE(
680 "{0, plural, one{for # file} other{for # files}}"));
682 char sizeBuffer[128];
683 result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer));
685 if (size >= kKBSize) {
686 result << " ";
688 sizeFormat.Format(result, size);
689 // "bytes" translation could come from string_for_size
690 // which could be part of the localekit itself
693 if (fileCount != 0) {
694 result << " ";
695 countFormat.Format(result, fileCount);
700 int32
701 BInfoWindow::CalcSize(void* castToWindow)
703 BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow);
704 BDirectory dir(window->TargetModel()->EntryRef());
705 BDirectory trashDir;
706 FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device);
707 if (dir.InitCheck() != B_OK) {
708 if (window->StopCalc())
709 return B_ERROR;
711 AutoLock<BWindow> lock(window);
712 if (!lock)
713 return B_ERROR;
715 window->SetSizeString(B_TRANSLATE("Error calculating folder size."));
716 return B_ERROR;
719 BEntry dirEntry, trashEntry;
720 dir.GetEntry(&dirEntry);
721 trashDir.GetEntry(&trashEntry);
723 BString sizeString;
725 // check if user has asked for trash dir info
726 if (dirEntry != trashEntry) {
727 // if not, perform normal info calculations
728 off_t size = 0;
729 int32 fileCount = 0;
730 int32 dirCount = 0;
731 CopyLoopControl loopControl;
732 FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount,
733 &dirCount);
735 // got the size value, update the size string
736 GetSizeString(sizeString, size, fileCount);
737 } else {
738 // in the trash case, iterate through and sum up
739 // size/counts for all present trash dirs
740 off_t totalSize = 0, currentSize;
741 int32 totalFileCount = 0, currentFileCount;
742 int32 totalDirCount = 0, currentDirCount;
743 BVolumeRoster volRoster;
744 volRoster.Rewind();
745 BVolume volume;
746 while (volRoster.GetNextVolume(&volume) == B_OK) {
747 if (!volume.IsPersistent())
748 continue;
750 currentSize = 0;
751 currentFileCount = 0;
752 currentDirCount = 0;
754 BDirectory trashDir;
755 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
756 CopyLoopControl loopControl;
757 FSRecursiveCalcSize(window, &loopControl, &trashDir,
758 &currentSize, &currentFileCount, &currentDirCount);
759 totalSize += currentSize;
760 totalFileCount += currentFileCount;
761 totalDirCount += currentDirCount;
764 GetSizeString(sizeString, totalSize, totalFileCount);
767 if (window->StopCalc()) {
768 // window closed, bail
769 return B_OK;
772 AutoLock<BWindow> lock(window);
773 if (lock.IsLocked())
774 window->SetSizeString(sizeString.String());
776 return B_OK;
780 void
781 BInfoWindow::SetSizeString(const char* sizeString)
783 AttributeView* view
784 = dynamic_cast<AttributeView*>(FindView("attr_view"));
785 if (view != NULL)
786 view->SetSizeString(sizeString);
790 void
791 BInfoWindow::OpenFilePanel(const entry_ref* ref)
793 // Open a file dialog box to allow the user to select a new target
794 // for the sym link
795 if (fFilePanel == NULL) {
796 BMessenger runner(this);
797 BMessage message(kNewTargetSelected);
798 fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref,
799 B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE,
800 false, &message);
802 if (fFilePanel != NULL) {
803 fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,
804 B_TRANSLATE("Select"));
805 fFilePanel->Window()->ResizeTo(500, 300);
806 BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:",
807 "File dialog title for new sym link"));
808 title.ReplaceFirst("%name", fModel->Name());
809 fFilePanel->Window()->SetTitle(title.String());
810 fFilePanel->Show();
811 fFilePanelOpen = true;
813 } else if (!fFilePanelOpen) {
814 fFilePanel->Show();
815 fFilePanelOpen = true;
816 } else {
817 fFilePanelOpen = true;
818 fFilePanel->Window()->Activate(true);
823 // #pragma mark - AttributeView
826 AttributeView::AttributeView(BRect rect, Model* model)
828 BView(rect, "attr_view", B_FOLLOW_ALL_SIDES,
829 B_WILL_DRAW | B_PULSE_NEEDED),
830 fDivider(0),
831 fPreferredAppMenu(NULL),
832 fModel(model),
833 fIconModel(model),
834 fMouseDown(false),
835 fDragging(false),
836 fDoubleClick(false),
837 fTrackingState(no_track),
838 fIsDropTarget(false),
839 fTitleEditView(NULL),
840 fPathWindow(NULL),
841 fLinkWindow(NULL),
842 fDescWindow(NULL),
843 fCurrentLinkColorWhich(B_LINK_TEXT_COLOR),
844 fCurrentPathColorWhich(fCurrentLinkColorWhich)
846 // Set view color to standard background grey
847 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
848 SetFont(be_plain_font);
849 // If the model is a symlink, then we deference the model to
850 // get the targets icon
851 if (fModel->IsSymLink()) {
852 Model* resolvedModel = new Model(model->EntryRef(), true, true);
853 if (resolvedModel->InitCheck() == B_OK)
854 fIconModel = resolvedModel;
855 // broken link, just show the symlink
856 else
857 delete resolvedModel;
860 // Create the rect for displaying the icon
861 fIconRect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1);
862 // Offset taken from BAlert
863 fIconRect.OffsetBy(kIconHorizOffset, kIconVertOffset);
865 // The title rect
866 // The magic numbers are used to properly calculate the rect so that
867 // when the editing text view is displayed, the position of the text
868 // does not change.
869 BFont currentFont;
870 font_height fontMetrics;
871 GetFont(&currentFont);
872 currentFont.GetHeight(&fontMetrics);
874 fTitleRect.left = fIconRect.right + 5;
875 fTitleRect.top = 0;
876 fTitleRect.bottom = fontMetrics.ascent + 1;
877 fTitleRect.right = min_c(
878 fTitleRect.left + currentFont.StringWidth(fModel->Name()),
879 Bounds().Width() - 5);
880 // Offset so that it centers with the icon
881 fTitleRect.OffsetBy(0,
882 fIconRect.top + ((fIconRect.Height() - fTitleRect.Height()) / 2));
883 // Make some room for the border for when we are in edit mode
884 // (Negative numbers increase the size of the rect)
885 fTitleRect.InsetBy(-1, -2);
887 fFreeBytes = -1;
888 fSizeString = "";
889 fSizeRect.Set(0, 0, 0, 0);
891 // Find offset for attributes, might be overiden below if there
892 // is a prefered handle menu displayed
893 currentFont.SetSize(currentFont.Size() - 2);
894 fDivider = currentFont.StringWidth(B_TRANSLATE("Description:"))
895 + kBorderMargin + kBorderWidth + 1;
896 // Add a preferred handler pop-up menu if this item
897 // is a file...This goes in place of the Link To:
898 // string...
899 if (model->IsFile()) {
900 BMimeType mime(fModel->MimeType());
901 BNodeInfo nodeInfo(fModel->Node());
903 // But don't add the menu if the file is executable
904 if (!fModel->IsExecutable()) {
905 float lineHeight = CurrentFontHeight();
907 BRect preferredAppRect(kBorderWidth + kBorderMargin,
908 fTitleRect.bottom + (lineHeight * 7),
909 Bounds().Width() - 5, fTitleRect.bottom + (lineHeight * 8));
910 fPreferredAppMenu = new BMenuField(preferredAppRect, "", "",
911 new BPopUpMenu(""));
912 currentFont.SetSize(currentFont.Size() + 2);
913 fDivider = currentFont.StringWidth(B_TRANSLATE("Opens with:"))
914 + 5;
915 fPreferredAppMenu->SetDivider(fDivider);
916 fDivider += (preferredAppRect.left - 2);
917 fPreferredAppMenu->SetFont(&currentFont);
918 fPreferredAppMenu->SetHighUIColor(B_PANEL_TEXT_COLOR);
919 fPreferredAppMenu->SetLabel(B_TRANSLATE("Opens with:"));
921 char prefSignature[B_MIME_TYPE_LENGTH];
922 nodeInfo.GetPreferredApp(prefSignature);
924 BMessage supportingAppList;
925 mime.GetSupportingApps(&supportingAppList);
927 // Add the default menu item and set it to marked
928 BMenuItem* result;
929 result = new BMenuItem(B_TRANSLATE("Default application"),
930 new BMessage(kSetPreferredApp));
931 result->SetTarget(this);
932 fPreferredAppMenu->Menu()->AddItem(result);
933 result->SetMarked(true);
935 for (int32 index = 0; ; index++) {
936 const char* signature;
937 if (supportingAppList.FindString("applications", index,
938 &signature) != B_OK) {
939 break;
942 // Only add separator item if there are more items
943 if (index == 0)
944 fPreferredAppMenu->Menu()->AddSeparatorItem();
946 BMessage* itemMessage = new BMessage(kSetPreferredApp);
947 itemMessage->AddString("signature", signature);
949 status_t err = B_ERROR;
950 entry_ref entry;
952 if (signature && signature[0])
953 err = be_roster->FindApp(signature, &entry);
955 if (err != B_OK)
956 result = new BMenuItem(signature, itemMessage);
957 else
958 result = new BMenuItem(entry.name, itemMessage);
960 result->SetTarget(this);
961 fPreferredAppMenu->Menu()->AddItem(result);
962 if (strcmp(signature, prefSignature) == 0)
963 result->SetMarked(true);
966 AddChild(fPreferredAppMenu);
970 fPermissionsSwitch = new PaneSwitch(BRect(), "Permissions");
971 fPermissionsSwitch->SetMessage(new BMessage(kPermissionsSelected));
972 fPermissionsSwitch->SetLabels(NULL, B_TRANSLATE("Permissions"));
973 AddChild(fPermissionsSwitch);
974 fPermissionsSwitch->ResizeToPreferred();
975 fPermissionsSwitch->MoveTo(kBorderWidth + 3,
976 Bounds().bottom - 3 - fPermissionsSwitch->Bounds().Height());
978 InitStrings(model);
982 AttributeView::~AttributeView()
984 if (fPathWindow->Lock())
985 fPathWindow->Quit();
987 if (fLinkWindow->Lock())
988 fLinkWindow->Quit();
990 if (fDescWindow->Lock())
991 fDescWindow->Quit();
993 if (fIconModel != fModel)
994 delete fIconModel;
998 void
999 AttributeView::InitStrings(const Model* model)
1001 BMimeType mime;
1002 char kind[B_MIME_TYPE_LENGTH];
1004 ASSERT(model->IsNodeOpen());
1006 BRect drawBounds(Bounds());
1007 drawBounds.left = fDivider;
1009 // We'll do our own truncation later on in Draw()
1010 WidgetAttributeText::AttrAsString(model, &fCreatedStr, kAttrStatCreated,
1011 B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this);
1012 WidgetAttributeText::AttrAsString(model, &fModifiedStr, kAttrStatModified,
1013 B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this);
1014 WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
1015 B_STRING_TYPE, 0, this);
1017 // Use the same method as used to resolve fIconModel, which handles
1018 // both absolute and relative symlinks. if the link is broken, try to
1019 // get a little more information.
1020 if (model->IsSymLink()) {
1021 bool linked = false;
1023 Model resolvedModel(model->EntryRef(), true, true);
1024 if (resolvedModel.InitCheck() == B_OK) {
1025 // Get the path of the link
1026 BPath traversedPath;
1027 resolvedModel.GetPath(&traversedPath);
1029 // If the BPath is initialized, then check the file for existence
1030 if (traversedPath.InitCheck() == B_OK) {
1031 BEntry entry(traversedPath.Path(), false);
1032 // look at the target itself
1033 if (entry.InitCheck() == B_OK && entry.Exists())
1034 linked = true;
1038 // always show the target as it is: absolute or relative!
1039 BSymLink symLink(model->EntryRef());
1040 char linkToPath[B_PATH_NAME_LENGTH];
1041 symLink.ReadLink(linkToPath, B_PATH_NAME_LENGTH);
1042 fLinkToStr = linkToPath;
1043 if (!linked) {
1044 // link points to missing object
1045 fLinkToStr += B_TRANSLATE(" (broken)");
1047 } else if (model->IsExecutable()) {
1048 if (((Model*)model)->GetLongVersionString(fDescStr,
1049 B_APP_VERSION_KIND) == B_OK) {
1050 // we want a flat string, so replace all newlines and tabs
1051 // with spaces
1052 fDescStr.ReplaceAll('\n', ' ');
1053 fDescStr.ReplaceAll('\t', ' ');
1054 } else
1055 fDescStr = "-";
1058 if (mime.SetType(model->MimeType()) == B_OK
1059 && mime.GetShortDescription(kind) == B_OK)
1060 fKindStr = kind;
1062 if (fKindStr.Length() == 0)
1063 fKindStr = model->MimeType();
1067 void
1068 AttributeView::AttachedToWindow()
1070 BFont font(be_plain_font);
1072 font.SetSpacing(B_BITMAP_SPACING);
1073 SetFont(&font);
1075 CheckAndSetSize();
1076 if (fPreferredAppMenu)
1077 fPreferredAppMenu->Menu()->SetTargetForItems(this);
1079 _inherited::AttachedToWindow();
1083 void
1084 AttributeView::Pulse()
1086 CheckAndSetSize();
1087 _inherited::Pulse();
1091 void
1092 AttributeView::ModelChanged(Model* model, BMessage* message)
1094 BRect drawBounds(Bounds());
1095 drawBounds.left = fDivider;
1097 switch (message->FindInt32("opcode")) {
1098 case B_ENTRY_MOVED:
1100 node_ref dirNode;
1101 node_ref itemNode;
1102 dirNode.device = itemNode.device = message->FindInt32("device");
1103 message->FindInt64("to directory", &dirNode.node);
1104 message->FindInt64("node", &itemNode.node);
1106 const char* name;
1107 if (message->FindString("name", &name) != B_OK)
1108 return;
1110 // ensure notification is for us
1111 if (*model->NodeRef() == itemNode
1112 // For volumes, the device ID is obviously not handled in a
1113 // consistent way; the node monitor sends us the ID of the
1114 // parent device, while the model is set to the device of the
1115 // volume directly - this hack works for volumes that are
1116 // mounted in the root directory
1117 || (model->IsVolume()
1118 && itemNode.device == 1
1119 && itemNode.node == model->NodeRef()->node)) {
1120 model->UpdateEntryRef(&dirNode, name);
1121 BString title;
1122 title << name << B_TRANSLATE(" info");
1123 Window()->SetTitle(title.String());
1124 WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
1125 B_STRING_TYPE, 0, this);
1126 Invalidate();
1128 break;
1131 case B_STAT_CHANGED:
1132 if (model->OpenNode() == B_OK) {
1133 WidgetAttributeText::AttrAsString(model, &fCreatedStr,
1134 kAttrStatCreated, B_TIME_TYPE, drawBounds.Width()
1135 - kBorderMargin, this);
1136 WidgetAttributeText::AttrAsString(model, &fModifiedStr,
1137 kAttrStatModified, B_TIME_TYPE, drawBounds.Width()
1138 - kBorderMargin, this);
1140 // don't change the size if it's a directory
1141 if (!model->IsDirectory()) {
1142 fLastSize = model->StatBuf()->st_size;
1143 fSizeString = "";
1144 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
1146 model->CloseNode();
1148 break;
1150 case B_ATTR_CHANGED:
1152 // watch for icon updates
1153 const char* attrName;
1154 if (message->FindString("attr", &attrName) == B_OK) {
1155 if (strcmp(attrName, kAttrLargeIcon) == 0
1156 || strcmp(attrName, kAttrIcon) == 0) {
1157 IconCache::sIconCache->IconChanged(model->ResolveIfLink());
1158 Invalidate();
1159 } else if (strcmp(attrName, kAttrMIMEType) == 0) {
1160 if (model->OpenNode() == B_OK) {
1161 model->AttrChanged(attrName);
1162 InitStrings(model);
1163 model->CloseNode();
1165 Invalidate();
1168 break;
1171 default:
1172 break;
1175 // Update the icon stuff
1176 if (fIconModel != fModel) {
1177 delete fIconModel;
1178 fIconModel = NULL;
1181 fModel = model;
1182 if (fModel->IsSymLink()) {
1183 // if we are looking at a symlink, deference the model and look
1184 // at the target
1185 Model* resolvedModel = new Model(model->EntryRef(), true, true);
1186 if (resolvedModel->InitCheck() == B_OK) {
1187 if (fIconModel != fModel)
1188 delete fIconModel;
1189 fIconModel = resolvedModel;
1190 } else {
1191 fIconModel = model;
1192 delete resolvedModel;
1194 InitStrings(model);
1195 Invalidate();
1198 drawBounds.left = fDivider;
1199 Invalidate(drawBounds);
1203 // This only applies to symlinks. If the target of the symlink
1204 // was changed, then we have to update the entire model.
1205 // (Since in order to re-target a symlink, we had to delete
1206 // the old model and create a new one; BSymLink::SetTarget(),
1207 // would be nice)
1209 void
1210 AttributeView::ReLinkTargetModel(Model* model)
1212 fModel = model;
1213 if (fModel->IsSymLink()) {
1214 Model* resolvedModel = new Model(model->EntryRef(), true, true);
1215 if (resolvedModel->InitCheck() == B_OK) {
1216 if (fIconModel != fModel)
1217 delete fIconModel;
1218 fIconModel = resolvedModel;
1219 } else {
1220 fIconModel = fModel;
1221 delete resolvedModel;
1224 InitStrings(model);
1225 Invalidate(Bounds());
1229 void
1230 AttributeView::MouseDown(BPoint where)
1232 BEntry entry;
1233 fModel->GetEntry(&entry);
1235 // Assume this isn't part of a double click
1236 fDoubleClick = false;
1238 // Start tracking the mouse if we are in any of the hotspots
1239 if (fLinkRect.Contains(where)) {
1240 InvertRect(fLinkRect);
1241 fTrackingState = link_track;
1242 } else if (fPathRect.Contains(where)) {
1243 InvertRect(fPathRect);
1244 fTrackingState = path_track;
1245 } else if (fTitleRect.Contains(where)) {
1246 if (!fModel->HasLocalizedName()
1247 && ConfirmChangeIfWellKnownDirectory(&entry, kRename, true)) {
1248 BeginEditingTitle();
1250 } else if (fTitleEditView) {
1251 FinishEditingTitle(true);
1252 } else if (fSizeRect.Contains(where)) {
1253 if (fModel->IsDirectory() && !fModel->IsVolume()
1254 && !fModel->IsRoot()) {
1255 InvertRect(fSizeRect);
1256 fTrackingState = size_track;
1257 } else
1258 fTrackingState = no_track;
1259 } else if (fIconRect.Contains(where)) {
1260 uint32 buttons;
1261 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
1262 if (SecondaryMouseButtonDown(modifiers(), buttons)) {
1263 // Show contextual menu
1264 BPopUpMenu* contextMenu
1265 = new BPopUpMenu("FileContext", false, false);
1266 if (contextMenu) {
1267 BuildContextMenu(contextMenu);
1268 contextMenu->SetAsyncAutoDestruct(true);
1269 contextMenu->Go(ConvertToScreen(where), true, true,
1270 ConvertToScreen(fIconRect));
1272 } else {
1273 // Check to see if the point is actually on part of the icon,
1274 // versus just in the container rect. The icons are always
1275 // the large version
1276 BPoint offsetPoint;
1277 offsetPoint.x = where.x - fIconRect.left;
1278 offsetPoint.y = where.y - fIconRect.top;
1279 if (IconCache::sIconCache->IconHitTest(offsetPoint, fIconModel,
1280 kNormalIcon, B_LARGE_ICON)) {
1281 // Can't drag the trash anywhere..
1282 fTrackingState = fModel->IsTrash()
1283 ? open_only_track : icon_track;
1285 // Check for possible double click
1286 if (abs((int32)(fClickPoint.x - where.x)) < kDragSlop
1287 && abs((int32)(fClickPoint.y - where.y)) < kDragSlop) {
1288 int32 clickCount;
1289 Window()->CurrentMessage()->FindInt32("clicks",
1290 &clickCount);
1292 // This checks the* previous* click point
1293 if (clickCount == 2) {
1294 offsetPoint.x = fClickPoint.x - fIconRect.left;
1295 offsetPoint.y = fClickPoint.y - fIconRect.top;
1296 fDoubleClick
1297 = IconCache::sIconCache->IconHitTest(offsetPoint,
1298 fIconModel, kNormalIcon, B_LARGE_ICON);
1305 fClickPoint = where;
1306 fMouseDown = true;
1307 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1311 void
1312 AttributeView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
1314 if (dragMessage != NULL && dragMessage->ReturnAddress() != BMessenger(this)
1315 && dragMessage->what == B_SIMPLE_DATA
1316 && BPoseView::CanHandleDragSelection(fModel, dragMessage,
1317 (modifiers() & B_CONTROL_KEY) != 0)) {
1318 // highlight drag target
1319 bool overTarget = fIconRect.Contains(where);
1320 SetDrawingMode(B_OP_OVER);
1321 if (overTarget != fIsDropTarget) {
1322 IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
1323 overTarget ? kSelectedIcon : kNormalIcon, B_LARGE_ICON, true);
1324 fIsDropTarget = overTarget;
1328 fCurrentLinkColorWhich = B_LINK_TEXT_COLOR;
1329 fCurrentPathColorWhich = fCurrentLinkColorWhich;
1331 switch (fTrackingState) {
1332 case link_track:
1333 if (fLinkRect.Contains(where) != fMouseDown) {
1334 fMouseDown = !fMouseDown;
1335 InvertRect(fLinkRect);
1337 break;
1339 case path_track:
1340 if (fPathRect.Contains(where) != fMouseDown) {
1341 fMouseDown = !fMouseDown;
1342 InvertRect(fPathRect);
1344 break;
1346 case size_track:
1347 if (fSizeRect.Contains(where) != fMouseDown) {
1348 fMouseDown = !fMouseDown;
1349 InvertRect(fSizeRect);
1351 break;
1353 case icon_track:
1354 if (fMouseDown && !fDragging
1355 && (abs((int32)(where.x - fClickPoint.x)) > kDragSlop
1356 || abs((int32)(where.y - fClickPoint.y)) > kDragSlop)) {
1357 // Find the required height
1358 BFont font;
1359 GetFont(&font);
1361 float height = CurrentFontHeight()
1362 + fIconRect.Height() + 8;
1363 BRect rect(0, 0, min_c(fIconRect.Width()
1364 + font.StringWidth(fModel->Name()) + 4,
1365 fIconRect.Width() * 3), height);
1366 BBitmap* dragBitmap = new BBitmap(rect, B_RGBA32, true);
1367 dragBitmap->Lock();
1368 BView* view = new BView(dragBitmap->Bounds(), "",
1369 B_FOLLOW_NONE, 0);
1370 dragBitmap->AddChild(view);
1371 view->SetOrigin(0, 0);
1372 BRect clipRect(view->Bounds());
1373 BRegion newClip;
1374 newClip.Set(clipRect);
1375 view->ConstrainClippingRegion(&newClip);
1377 // Transparent draw magic
1378 view->SetHighColor(0, 0, 0, 0);
1379 view->FillRect(view->Bounds());
1380 view->SetDrawingMode(B_OP_ALPHA);
1381 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
1382 textColor.alpha = 128;
1383 // set transparency by value
1384 view->SetHighColor(textColor);
1385 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
1387 // Draw the icon
1388 float hIconOffset = (rect.Width() - fIconRect.Width()) / 2;
1389 IconCache::sIconCache->Draw(fIconModel, view,
1390 BPoint(hIconOffset, 0), kNormalIcon, B_LARGE_ICON, true);
1392 // See if we need to truncate the string
1393 BString nameString(fModel->Name());
1394 if (view->StringWidth(fModel->Name()) > rect.Width()) {
1395 view->TruncateString(&nameString, B_TRUNCATE_END,
1396 rect.Width() - 5);
1399 // Draw the label
1400 font_height fontHeight;
1401 font.GetHeight(&fontHeight);
1402 float leftText = (view->StringWidth(nameString.String())
1403 - fIconRect.Width()) / 2;
1404 view->MovePenTo(BPoint(hIconOffset - leftText + 2,
1405 fIconRect.Height() + (fontHeight.ascent + 2)));
1406 view->DrawString(nameString.String());
1408 view->Sync();
1409 dragBitmap->Unlock();
1411 BMessage dragMessage(B_REFS_RECEIVED);
1412 dragMessage.AddPoint("click_pt", fClickPoint);
1413 BPoint tmpLoc;
1414 uint32 button;
1415 GetMouse(&tmpLoc, &button);
1416 if (button)
1417 dragMessage.AddInt32("buttons", (int32)button);
1419 dragMessage.AddInt32("be:actions",
1420 (modifiers() & B_OPTION_KEY) != 0
1421 ? B_COPY_TARGET : B_MOVE_TARGET);
1422 dragMessage.AddRef("refs", fModel->EntryRef());
1423 DragMessage(&dragMessage, dragBitmap, B_OP_ALPHA,
1424 BPoint((fClickPoint.x - fIconRect.left)
1425 + hIconOffset, fClickPoint.y - fIconRect.top), this);
1426 fDragging = true;
1428 break;
1430 case open_only_track :
1431 // Special type of entry that can't be renamed or drag and dropped
1432 // It can only be opened by double clicking on the icon
1433 break;
1435 default:
1437 // Only consider this if the window is the active window.
1438 // We have to manually get the mouse here in the event that the
1439 // mouse is over a pop-up window
1440 uint32 buttons;
1441 BPoint point;
1442 GetMouse(&point, &buttons);
1443 if (Window()->IsActive() && !buttons) {
1444 // If we are down here, then that means that we're tracking
1445 // the mouse but not from a mouse down. In this case, we're
1446 // just interested in knowing whether or not we need to
1447 // display the "pop-up" version of the path or link text.
1448 BScreen screen(Window());
1449 BFont font;
1450 GetFont(&font);
1451 float maxWidth = (Bounds().Width()
1452 - (fDivider + kBorderMargin));
1454 if (fPathRect.Contains(point)) {
1455 if (fCurrentPathColorWhich != B_LINK_HOVER_COLOR)
1456 fCurrentPathColorWhich = B_LINK_HOVER_COLOR;
1458 if (font.StringWidth(fPathStr.String()) > maxWidth) {
1459 fTrackingState = no_track;
1460 BRect rect(fPathRect);
1461 rect.OffsetBy(Window()->Frame().left,
1462 Window()->Frame().top);
1464 if (fPathWindow == NULL
1465 || BMessenger(fPathWindow).IsValid() == false) {
1466 fPathWindow = OpenToolTipWindow(screen, rect,
1467 "fPathWindow", fPathStr.String(),
1468 BMessenger(this),
1469 new BMessage(kOpenLinkSource));
1472 } else if (fLinkRect.Contains(point)) {
1474 if (fCurrentLinkColorWhich != B_LINK_HOVER_COLOR)
1475 fCurrentLinkColorWhich = B_LINK_HOVER_COLOR;
1477 if (font.StringWidth(fLinkToStr.String()) > maxWidth) {
1478 fTrackingState = no_track;
1479 BRect rect(fLinkRect);
1480 rect.OffsetBy(Window()->Frame().left,
1481 Window()->Frame().top);
1483 if (!fLinkWindow
1484 || BMessenger(fLinkWindow).IsValid() == false) {
1485 fLinkWindow = OpenToolTipWindow(screen, rect,
1486 "fLinkWindow", fLinkToStr.String(),
1487 BMessenger(this),
1488 new BMessage(kOpenLinkTarget));
1491 } else if (fDescRect.Contains(point)
1492 && font.StringWidth(fDescStr.String()) > maxWidth) {
1493 fTrackingState = no_track;
1494 BRect rect(fDescRect);
1495 rect.OffsetBy(Window()->Frame().left,
1496 Window()->Frame().top);
1498 if (!fDescWindow
1499 || BMessenger(fDescWindow).IsValid() == false) {
1500 fDescWindow = OpenToolTipWindow(screen, rect,
1501 "fDescWindow", fDescStr.String(),
1502 BMessenger(this), NULL);
1506 break;
1510 DelayedInvalidate(16666, fPathRect);
1511 DelayedInvalidate(16666, fLinkRect);
1515 void
1516 AttributeView::OpenLinkSource()
1518 OpenParentAndSelectOriginal(fModel->EntryRef());
1522 void
1523 AttributeView::OpenLinkTarget()
1525 Model resolvedModel(fModel->EntryRef(), true, true);
1526 BEntry entry;
1527 if (resolvedModel.InitCheck() == B_OK) {
1528 // Get the path of the link
1529 BPath traversedPath;
1530 resolvedModel.GetPath(&traversedPath);
1532 // If the BPath is initialized, then check the file for existence
1533 if (traversedPath.InitCheck() == B_OK)
1534 entry.SetTo(traversedPath.Path());
1536 if (entry.InitCheck() != B_OK || !entry.Exists()) {
1537 // Open a file dialog panel to allow the user to relink.
1538 BInfoWindow* window = dynamic_cast<BInfoWindow*>(Window());
1539 if (window != NULL)
1540 window->OpenFilePanel(fModel->EntryRef());
1541 } else {
1542 entry_ref ref;
1543 entry.GetRef(&ref);
1544 BPath path(&ref);
1545 printf("Opening link target: %s\n", path.Path());
1546 OpenParentAndSelectOriginal(&ref);
1551 void
1552 AttributeView::MouseUp(BPoint where)
1554 // Are we in the link rect?
1555 if (fTrackingState == link_track && fLinkRect.Contains(where)) {
1556 InvertRect(fLinkRect);
1557 OpenLinkTarget();
1558 } else if (fTrackingState == path_track && fPathRect.Contains(where)) {
1559 InvertRect(fPathRect);
1560 OpenLinkSource();
1561 } else if ((fTrackingState == icon_track
1562 || fTrackingState == open_only_track)
1563 && fIconRect.Contains(where)) {
1564 // If it was a double click, then tell Tracker to open the item
1565 // The CurrentMessage() here does* not* have a "clicks" field,
1566 // which is why we are tracking the clicks with this temp var
1567 if (fDoubleClick) {
1568 // Double click, launch.
1569 BMessage message(B_REFS_RECEIVED);
1570 message.AddRef("refs", fModel->EntryRef());
1572 // add a messenger to the launch message that will be used to
1573 // dispatch scripting calls from apps to the PoseView
1574 message.AddMessenger("TrackerViewToken", BMessenger(this));
1575 be_app->PostMessage(&message);
1576 fDoubleClick = false;
1578 } else if (fTrackingState == size_track && fSizeRect.Contains(where)) {
1579 // Recalculate size
1580 Window()->PostMessage(kRecalculateSize);
1583 // End mouse tracking
1584 fMouseDown = false;
1585 fDragging = false;
1586 fTrackingState = no_track;
1591 void
1592 AttributeView::CheckAndSetSize()
1594 if (fModel->IsVolume() || fModel->IsRoot()) {
1595 off_t freeBytes = 0;
1596 off_t capacity = 0;
1598 if (fModel->IsVolume()) {
1599 BVolume volume(fModel->NodeRef()->device);
1600 freeBytes = volume.FreeBytes();
1601 capacity = volume.Capacity();
1602 } else {
1603 // iterate over all volumes
1604 BVolumeRoster volumeRoster;
1605 BVolume volume;
1606 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
1607 freeBytes += volume.FreeBytes();
1608 capacity += volume.Capacity();
1612 if (fFreeBytes == freeBytes)
1613 return;
1615 fFreeBytes = freeBytes;
1617 fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
1619 char sizeStr[128];
1620 string_for_size(capacity, sizeStr, sizeof(sizeStr));
1621 fSizeString.ReplaceFirst("%capacity", sizeStr);
1622 string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr));
1623 fSizeString.ReplaceFirst("%used", sizeStr);
1624 string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr));
1625 fSizeString.ReplaceFirst("%free", sizeStr);
1627 } else if (fModel->IsFile()) {
1628 // poll for size changes because they do not get node monitored
1629 // until a file gets closed (with the old BFS)
1630 StatStruct statBuf;
1631 BModelOpener opener(fModel);
1633 if (fModel->InitCheck() != B_OK
1634 || fModel->Node()->GetStat(&statBuf) != B_OK) {
1635 return;
1638 if (fLastSize == statBuf.st_size)
1639 return;
1641 fLastSize = statBuf.st_size;
1642 fSizeString = "";
1643 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
1644 } else
1645 return;
1647 BRect bounds(Bounds());
1648 float lineHeight = CurrentFontHeight() + 2;
1649 bounds.Set(fDivider, fIconRect.bottom, bounds.right,
1650 fIconRect.bottom + lineHeight);
1651 Invalidate(bounds);
1655 void
1656 AttributeView::MessageReceived(BMessage* message)
1658 if (message->WasDropped()
1659 && message->what == B_SIMPLE_DATA
1660 && message->ReturnAddress() != BMessenger(this)
1661 && fIconRect.Contains(ConvertFromScreen(message->DropPoint()))
1662 && BPoseView::CanHandleDragSelection(fModel, message,
1663 (modifiers() & B_CONTROL_KEY) != 0)) {
1664 BPoseView::HandleDropCommon(message, fModel, 0, this,
1665 message->DropPoint());
1666 Invalidate(fIconRect);
1667 return;
1670 switch (message->what) {
1671 case kSetPreferredApp:
1673 BNode node(fModel->EntryRef());
1674 BNodeInfo nodeInfo(&node);
1676 const char* newSignature;
1677 if (message->FindString("signature", &newSignature) != B_OK)
1678 newSignature = NULL;
1680 fModel->SetPreferredAppSignature(newSignature);
1681 nodeInfo.SetPreferredApp(newSignature);
1682 break;
1685 case kOpenLinkSource:
1686 OpenLinkSource();
1687 break;
1689 case kOpenLinkTarget:
1690 OpenLinkTarget();
1691 break;
1693 default:
1694 _inherited::MessageReceived(message);
1695 break;
1700 void
1701 AttributeView::Draw(BRect)
1703 // Set the low color for anti-aliasing
1704 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
1706 // Clear the old contents
1707 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
1708 FillRect(Bounds());
1710 rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
1711 rgb_color attributeColor = mix_color(HighColor(), labelColor, 192);
1713 // Draw the dark grey area on the left
1714 BRect drawBounds(Bounds());
1715 drawBounds.right = kBorderWidth;
1716 SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_2_TINT);
1717 FillRect(drawBounds);
1719 // Draw the icon, straddling the border
1720 SetDrawingMode(B_OP_OVER);
1721 IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
1722 kNormalIcon, B_LARGE_ICON, true);
1723 SetDrawingMode(B_OP_COPY);
1724 // Font information
1725 font_height fontMetrics;
1726 BFont currentFont;
1727 float lineHeight = 0;
1728 float lineBase = 0;
1729 // Draw the main title if the user is not currently editing it
1730 if (fTitleEditView == NULL) {
1731 SetFont(be_bold_font);
1732 SetFontSize(be_bold_font->Size());
1733 GetFont(&currentFont);
1734 currentFont.GetHeight(&fontMetrics);
1735 lineHeight = CurrentFontHeight() + 5;
1736 lineBase = fTitleRect.bottom - fontMetrics.descent;
1737 SetHighColor(labelColor);
1738 MovePenTo(BPoint(fIconRect.right + 6, lineBase));
1740 // Recalculate the rect width
1741 fTitleRect.right = min_c(
1742 fTitleRect.left + currentFont.StringWidth(fModel->Name()),
1743 Bounds().Width() - 5);
1744 // Check for possible need of truncation
1745 if (StringWidth(fModel->Name()) > fTitleRect.Width()) {
1746 BString nameString(fModel->Name());
1747 TruncateString(&nameString, B_TRUNCATE_END,
1748 fTitleRect.Width() - 2);
1749 DrawString(nameString.String());
1750 } else
1751 DrawString(fModel->Name());
1754 // Draw the attribute font stuff
1755 SetFont(be_plain_font);
1756 GetFontHeight(&fontMetrics);
1757 lineHeight = CurrentFontHeight() + 5;
1759 // Starting base line for the first string
1760 lineBase = fTitleRect.bottom + lineHeight;
1762 // Capacity/size
1763 SetHighColor(labelColor);
1764 if (fModel->IsVolume() || fModel->IsRoot()) {
1765 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))),
1766 lineBase));
1767 DrawString(B_TRANSLATE("Capacity:"));
1768 } else {
1769 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))),
1770 lineBase));
1771 fSizeRect.left = fDivider + 2;
1772 fSizeRect.top = lineBase - fontMetrics.ascent;
1773 fSizeRect.bottom = lineBase + fontMetrics.descent;
1774 DrawString(B_TRANSLATE("Size:"));
1777 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1778 SetHighColor(attributeColor);
1779 // Check for possible need of truncation
1780 if (StringWidth(fSizeString.String())
1781 > (Bounds().Width() - (fDivider + kBorderMargin))) {
1782 BString tmpString(fSizeString.String());
1783 TruncateString(&tmpString, B_TRUNCATE_MIDDLE,
1784 Bounds().Width() - (fDivider + kBorderMargin));
1785 DrawString(tmpString.String());
1786 fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String())
1787 + 3;
1788 } else {
1789 DrawString(fSizeString.String());
1790 fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3;
1792 lineBase += lineHeight;
1794 // Created
1795 SetHighColor(labelColor);
1796 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))),
1797 lineBase));
1798 DrawString(B_TRANSLATE("Created:"));
1799 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1800 SetHighColor(attributeColor);
1801 DrawString(fCreatedStr.String());
1802 lineBase += lineHeight;
1804 // Modified
1805 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))),
1806 lineBase));
1807 SetHighColor(labelColor);
1808 DrawString(B_TRANSLATE("Modified:"));
1809 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1810 SetHighColor(attributeColor);
1811 DrawString(fModifiedStr.String());
1812 lineBase += lineHeight;
1814 // Kind
1815 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))),
1816 lineBase));
1817 SetHighColor(labelColor);
1818 DrawString(B_TRANSLATE("Kind:"));
1819 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1820 SetHighColor(attributeColor);
1821 DrawString(fKindStr.String());
1822 lineBase += lineHeight;
1824 BFont normalFont;
1825 GetFont(&normalFont);
1827 // Path
1828 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))),
1829 lineBase));
1830 SetHighColor(labelColor);
1831 DrawString(B_TRANSLATE("Location:"));
1833 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1834 SetHighUIColor(fCurrentPathColorWhich);
1836 // Check for truncation
1837 if (StringWidth(fPathStr.String()) > (Bounds().Width()
1838 - (fDivider + kBorderMargin))) {
1839 BString nameString(fPathStr.String());
1840 TruncateString(&nameString, B_TRUNCATE_MIDDLE,
1841 Bounds().Width() - (fDivider + kBorderMargin));
1842 DrawString(nameString.String());
1843 } else
1844 DrawString(fPathStr.String());
1846 // Cache the position of the path
1847 fPathRect.top = lineBase - fontMetrics.ascent;
1848 fPathRect.bottom = lineBase + fontMetrics.descent;
1849 fPathRect.left = fDivider + 2;
1850 fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3;
1852 lineBase += lineHeight;
1854 // Link to/version
1855 if (fModel->IsSymLink()) {
1856 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))),
1857 lineBase));
1858 SetHighColor(labelColor);
1859 DrawString(B_TRANSLATE("Link to:"));
1860 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1861 SetHighUIColor(fCurrentLinkColorWhich);
1863 // Check for truncation
1864 if (StringWidth(fLinkToStr.String()) > (Bounds().Width()
1865 - (fDivider + kBorderMargin))) {
1866 BString nameString(fLinkToStr.String());
1867 TruncateString(&nameString, B_TRUNCATE_MIDDLE,
1868 Bounds().Width() - (fDivider + kBorderMargin));
1869 DrawString(nameString.String());
1870 } else
1871 DrawString(fLinkToStr.String());
1873 // Cache the position of the link field
1874 fLinkRect.top = lineBase - fontMetrics.ascent;
1875 fLinkRect.bottom = lineBase + fontMetrics.descent;
1876 fLinkRect.left = fDivider + 2;
1877 fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String())
1878 + 3;
1880 // No description field
1881 fDescRect = BRect(-1, -1, -1, -1);
1882 } else if (fModel->IsExecutable()) {
1883 //Version
1884 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))),
1885 lineBase));
1886 SetHighColor(labelColor);
1887 DrawString(B_TRANSLATE("Version:"));
1888 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1889 SetHighColor(attributeColor);
1890 BString nameString;
1891 if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK)
1892 DrawString(nameString.String());
1893 else
1894 DrawString("-");
1895 lineBase += lineHeight;
1897 // Description
1898 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))),
1899 lineBase));
1900 SetHighColor(labelColor);
1901 DrawString(B_TRANSLATE("Description:"));
1902 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1903 SetHighColor(attributeColor);
1904 // Check for truncation
1905 if (StringWidth(fDescStr.String()) > (Bounds().Width()
1906 - (fDivider + kBorderMargin))) {
1907 BString nameString(fDescStr.String());
1908 TruncateString(&nameString, B_TRUNCATE_MIDDLE,
1909 Bounds().Width() - (fDivider + kBorderMargin));
1910 DrawString(nameString.String());
1911 } else
1912 DrawString(fDescStr.String());
1914 // Cache the position of the description field
1915 fDescRect.top = lineBase - fontMetrics.ascent;
1916 fDescRect.bottom = lineBase + fontMetrics.descent;
1917 fDescRect.left = fDivider + 2;
1918 fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3;
1920 // No link field
1921 fLinkRect = BRect(-1, -1, -1, -1);
1926 void
1927 AttributeView::BeginEditingTitle()
1929 if (fTitleEditView != NULL)
1930 return;
1932 BFont font(be_plain_font);
1933 font.SetSize(font.Size() + 2);
1934 BRect textFrame(fTitleRect);
1935 textFrame.right = Bounds().Width() - 5;
1936 BRect textRect(textFrame);
1937 textRect.OffsetTo(0, 0);
1938 textRect.InsetBy(1, 1);
1940 // Just make it some really large size, since we don't do any line
1941 // wrapping. The text filter will make sure to scroll the cursor
1942 // into position
1944 textRect.right = 2000;
1945 fTitleEditView = new BTextView(textFrame, "text_editor",
1946 textRect, &font, 0, B_FOLLOW_ALL, B_WILL_DRAW);
1947 fTitleEditView->SetText(fModel->Name());
1948 DisallowFilenameKeys(fTitleEditView);
1950 // Reset the width of the text rect
1951 textRect = fTitleEditView->TextRect();
1952 textRect.right = fTitleEditView->LineWidth() + 20;
1953 fTitleEditView->SetTextRect(textRect);
1954 fTitleEditView->SetWordWrap(false);
1955 // Add filter for catching B_RETURN and B_ESCAPE key's
1956 fTitleEditView->AddFilter(
1957 new BMessageFilter(B_KEY_DOWN, AttributeView::TextViewFilter));
1959 BScrollView* scrollView = new BScrollView("BorderView", fTitleEditView,
1960 0, 0, false, false, B_PLAIN_BORDER);
1961 AddChild(scrollView);
1962 fTitleEditView->SelectAll();
1963 fTitleEditView->MakeFocus();
1965 Window()->UpdateIfNeeded();
1969 void
1970 AttributeView::FinishEditingTitle(bool commit)
1972 if (fTitleEditView == NULL)
1973 return;
1975 bool reopen = false;
1977 const char* text = fTitleEditView->Text();
1978 uint32 length = strlen(text);
1979 if (commit && strcmp(text, fModel->Name()) != 0
1980 && length < B_FILE_NAME_LENGTH) {
1981 BEntry entry(fModel->EntryRef());
1982 BDirectory parent;
1983 if (entry.InitCheck() == B_OK
1984 && entry.GetParent(&parent) == B_OK) {
1985 if (parent.Contains(text)) {
1986 BAlert* alert = new BAlert("",
1987 B_TRANSLATE("That name is already taken. "
1988 "Please type another one."),
1989 B_TRANSLATE("OK"),
1990 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1991 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1992 alert->Go();
1993 reopen = true;
1994 } else {
1995 if (fModel->IsVolume()) {
1996 BVolume volume(fModel->NodeRef()->device);
1997 if (volume.InitCheck() == B_OK)
1998 volume.SetName(text);
1999 } else
2000 entry.Rename(text);
2002 // Adjust the size of the text rect
2003 BFont currentFont(be_plain_font);
2004 currentFont.SetSize(currentFont.Size() + 2);
2005 fTitleRect.right = min_c(fTitleRect.left
2006 + currentFont.StringWidth(fTitleEditView->Text()),
2007 Bounds().Width() - 5);
2010 } else if (length >= B_FILE_NAME_LENGTH) {
2011 BAlert* alert = new BAlert("",
2012 B_TRANSLATE("That name is too long. Please type another one."),
2013 B_TRANSLATE("OK"),
2014 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2015 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2016 alert->Go();
2017 reopen = true;
2020 // Remove view
2021 BView* scrollView = fTitleEditView->Parent();
2022 RemoveChild(scrollView);
2023 delete scrollView;
2024 fTitleEditView = NULL;
2026 if (reopen)
2027 BeginEditingTitle();
2031 void
2032 AttributeView::MakeFocus(bool focus)
2034 if (!focus && fTitleEditView != NULL)
2035 FinishEditingTitle(true);
2039 void
2040 AttributeView::WindowActivated(bool active)
2042 if (active)
2043 return;
2045 if (fTitleEditView != NULL)
2046 FinishEditingTitle(true);
2048 if (fPathWindow->Lock()) {
2049 fPathWindow->Quit();
2050 fPathWindow = NULL;
2053 if (fLinkWindow->Lock()) {
2054 fLinkWindow->Quit();
2055 fLinkWindow = NULL;
2058 if (fDescWindow->Lock()) {
2059 fDescWindow->Quit();
2060 fDescWindow = NULL;
2065 float
2066 AttributeView::CurrentFontHeight()
2068 BFont font;
2069 GetFont(&font);
2070 font_height fontHeight;
2071 font.GetHeight(&fontHeight);
2073 return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
2077 status_t
2078 AttributeView::BuildContextMenu(BMenu* parent)
2080 if (parent == NULL)
2081 return B_BAD_VALUE;
2083 // Add navigation menu if this is not a symlink
2084 // Symlink's to directories are OK however!
2085 BEntry entry(fModel->EntryRef());
2086 entry_ref ref;
2087 entry.GetRef(&ref);
2088 Model model(&entry);
2089 bool navigate = false;
2090 if (model.InitCheck() == B_OK) {
2091 if (model.IsSymLink()) {
2092 // Check if it's to a directory
2093 if (entry.SetTo(model.EntryRef(), true) == B_OK) {
2094 navigate = entry.IsDirectory();
2095 entry.GetRef(&ref);
2097 } else if (model.IsDirectory() || model.IsVolume())
2098 navigate = true;
2100 ModelMenuItem* navigationItem = NULL;
2101 if (navigate) {
2102 navigationItem = new ModelMenuItem(new Model(model),
2103 new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, Window()));
2105 // setup a navigation menu item which will dynamically load items
2106 // as menu items are traversed
2107 BNavMenu* navMenu = dynamic_cast<BNavMenu*>(navigationItem->Submenu());
2108 if (navMenu != NULL)
2109 navMenu->SetNavDir(&ref);
2111 navigationItem->SetLabel(model.Name());
2112 navigationItem->SetEntry(&entry);
2114 parent->AddItem(navigationItem, 0);
2115 parent->AddItem(new BSeparatorItem(), 1);
2117 BMessage* message = new BMessage(B_REFS_RECEIVED);
2118 message->AddRef("refs", &ref);
2119 navigationItem->SetMessage(message);
2120 navigationItem->SetTarget(be_app);
2123 parent->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2124 new BMessage(kOpenSelection), 'O'));
2126 if (!model.IsDesktop() && !model.IsRoot() && !model.IsTrash()
2127 && !fModel->HasLocalizedName()) {
2128 parent->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2129 new BMessage(kEditItem), 'E'));
2130 parent->AddSeparatorItem();
2132 if (fModel->IsVolume()) {
2133 BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
2134 new BMessage(kUnmountVolume), 'U');
2135 parent->AddItem(item);
2136 // volume model, enable/disable the Unmount item
2137 BVolume boot;
2138 BVolumeRoster().GetBootVolume(&boot);
2139 BVolume volume;
2140 volume.SetTo(fModel->NodeRef()->device);
2141 if (volume == boot)
2142 item->SetEnabled(false);
2146 if (!model.IsRoot() && !model.IsVolume() && !model.IsTrash())
2147 parent->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
2148 new BMessage(kIdentifyEntry)));
2150 if (model.IsTrash())
2151 parent->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2152 new BMessage(kEmptyTrash)));
2154 BMenuItem* sizeItem = NULL;
2155 if (model.IsDirectory() && !model.IsVolume() && !model.IsRoot()) {
2156 parent->AddItem(sizeItem
2157 = new BMenuItem(B_TRANSLATE("Recalculate folder size"),
2158 new BMessage(kRecalculateSize)));
2161 if (model.IsSymLink()) {
2162 parent->AddItem(sizeItem
2163 = new BMenuItem(B_TRANSLATE("Set new link target"),
2164 new BMessage(kSetLinkTarget)));
2167 parent->AddItem(new BSeparatorItem());
2168 parent->AddItem(new BMenuItem(B_TRANSLATE("Permissions"),
2169 new BMessage(kPermissionsSelected), 'P'));
2171 parent->SetFont(be_plain_font);
2172 parent->SetTargetForItems(this);
2174 // Reset the nav menu to be_app
2175 if (navigate)
2176 navigationItem->SetTarget(be_app);
2177 if (sizeItem)
2178 sizeItem->SetTarget(Window());
2180 return B_OK;
2184 void
2185 AttributeView::SetPermissionsSwitchState(int32 state)
2187 fPermissionsSwitch->SetValue(state);
2188 fPermissionsSwitch->Invalidate();
2192 filter_result
2193 AttributeView::TextViewFilter(BMessage* message, BHandler**,
2194 BMessageFilter* filter)
2196 uchar key;
2197 AttributeView* attribView = static_cast<AttributeView*>(
2198 static_cast<BWindow*>(filter->Looper())->FindView("attr_view"));
2200 // Adjust the size of the text rect
2201 BRect nuRect(attribView->TextView()->TextRect());
2202 nuRect.right = attribView->TextView()->LineWidth() + 20;
2203 attribView->TextView()->SetTextRect(nuRect);
2205 // Make sure the cursor is in view
2206 attribView->TextView()->ScrollToSelection();
2207 if (message->FindInt8("byte", (int8*)&key) != B_OK)
2208 return B_DISPATCH_MESSAGE;
2210 if (key == B_RETURN || key == B_ESCAPE) {
2211 attribView->FinishEditingTitle(key == B_RETURN);
2212 return B_SKIP_MESSAGE;
2215 return B_DISPATCH_MESSAGE;
2219 off_t
2220 AttributeView::LastSize() const
2222 return fLastSize;
2226 void
2227 AttributeView::SetLastSize(off_t lastSize)
2229 fLastSize = lastSize;
2233 void
2234 AttributeView::SetSizeString(const char* sizeString)
2236 fSizeString = sizeString;
2238 BRect bounds(Bounds());
2239 float lineHeight = CurrentFontHeight() + 6;
2240 bounds.Set(fDivider, fIconRect.bottom, bounds.right,
2241 fIconRect.bottom + lineHeight);
2242 Invalidate(bounds);
2246 // #pragma mark -
2249 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message)
2250 : BControl(frame, "trackingView", str, message, B_FOLLOW_ALL,
2251 B_WILL_DRAW),
2252 fMouseDown(false),
2253 fMouseInView(false)
2255 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
2256 SetEventMask(B_POINTER_EVENTS, 0);
2260 void
2261 TrackingView::MouseDown(BPoint)
2263 if (Message() != NULL) {
2264 fMouseDown = true;
2265 fMouseInView = true;
2266 InvertRect(Bounds());
2271 void
2272 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*)
2274 if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown)
2275 InvertRect(Bounds());
2277 fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW);
2278 DelayedInvalidate(16666, Bounds());
2279 if (!fMouseInView && !fMouseDown)
2280 Window()->Close();
2284 void
2285 TrackingView::MouseUp(BPoint)
2287 if (Message() != NULL) {
2288 if (fMouseInView)
2289 Invoke();
2291 fMouseDown = false;
2292 Window()->Close();
2297 void
2298 TrackingView::Draw(BRect)
2300 if (Message() != NULL)
2301 SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR
2302 : B_LINK_TEXT_COLOR);
2303 else
2304 SetHighUIColor(B_PANEL_TEXT_COLOR);
2305 SetLowColor(ViewColor());
2307 font_height fontHeight;
2308 GetFontHeight(&fontHeight);
2310 DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent));