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.
36 #include "InfoWindow.h"
45 #include <Directory.h>
49 #include <MenuField.h>
50 #include <MessageFormat.h>
53 #include <NodeMonitor.h>
55 #include <PopUpMenu.h>
59 #include <ScrollView.h>
63 #include <VolumeRoster.h>
65 #include "Attributes.h"
68 #include "DialogPane.h"
70 #include "IconCache.h"
71 #include "IconMenuItem.h"
75 #include "StringForSize.h"
77 #include "WidgetAttributeText.h"
80 #undef B_TRANSLATION_CONTEXT
81 #define B_TRANSLATION_CONTEXT "InfoWindow"
86 // States for tracking the mouse
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
{
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
);
114 class AttributeView
: public BView
{
116 AttributeView(BRect
, Model
*);
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
**,
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
);
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
);
151 void InitStrings(const Model
*);
152 void CheckAndSetSize();
153 void OpenLinkSource();
154 void OpenLinkTarget();
159 BString fModifiedStr
;
176 BMenuField
* fPreferredAppMenu
;
183 track_state fTrackingState
;
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;
224 OpenParentAndSelectOriginal(const entry_ref
* ref
)
228 entry
.GetNodeRef(&node
);
231 entry
.GetParent(&parent
);
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
);
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;
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(),
267 trackingView
->SetTarget(target
);
268 window
->AddChild(trackingView
);
277 // #pragma mark - BInfoWindow
280 BInfoWindow::BInfoWindow(Model
* model
, int32 group_index
,
281 LockingList
<BWindow
>* list
)
283 BWindow(BInfoWindow::InfoWindowRect(false),
284 "InfoWindow", B_TITLED_WINDOW
,
285 B_NOT_RESIZABLE
| B_NOT_ZOOMABLE
, B_CURRENT_WORKSPACE
),
291 fPermissionsView(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
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
));
313 BInfoWindow::~BInfoWindow()
315 // Check to make sure the file panel is destroyed
322 BInfoWindow::InfoWindowRect(bool)
324 // starting size of window
325 return BRect(70, 50, 385, 240);
335 AutoLock
<LockingList
<BWindow
> > lock(fWindowList
);
336 fWindowList
->RemoveItem(this);
341 // wait until CalcSize thread has terminated before closing window
343 wait_for_thread(fCalcThreadID
, &result
);
350 BInfoWindow::IsShowing(const node_ref
* node
) const
352 return *TargetModel()->NodeRef() == *node
;
359 BModelOpener
modelOpener(TargetModel());
360 if (TargetModel()->InitCheck() != B_OK
) {
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
373 if (fModel
->IsExecutable())
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(TargetModel()->IsSymLink()
385 || TargetModel()->IsFile()));
386 if ((fIndex
+ 2) % 2 == 1) {
387 windRect
.OffsetBy(320, 0);
391 windRect
.OffsetBy(fIndex
* 8, fIndex
* 8);
393 // make sure window is visible on screen
394 BScreen
screen(this);
395 if (!windRect
.Intersects(screen
.Frame()))
396 windRect
.OffsetTo(50, 50);
398 MoveTo(windRect
.LeftTop());
400 // volume case is handled by view
401 if (!TargetModel()->IsVolume() && !TargetModel()->IsRoot()) {
402 if (TargetModel()->IsDirectory()) {
403 // if this is a folder then spawn thread to calculate size
404 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS
));
405 fCalcThreadID
= spawn_thread(BInfoWindow::CalcSize
, "CalcSize",
406 B_NORMAL_PRIORITY
, this);
407 resume_thread(fCalcThreadID
);
409 fAttributeView
->SetLastSize(TargetModel()->StatBuf()->st_size
);
412 GetSizeString(sizeStr
, fAttributeView
->LastSize(), 0);
413 SetSizeString(sizeStr
.String());
417 BString
buffer(B_TRANSLATE_COMMENT("%name info", "InfoWindow Title"));
418 buffer
.ReplaceFirst("%name", TargetModel()->Name());
419 SetTitle(buffer
.String());
427 BInfoWindow::MessageReceived(BMessage
* message
)
429 switch (message
->what
) {
436 BMessage
refsMessage(B_REFS_RECEIVED
);
437 refsMessage
.AddRef("refs", fModel
->EntryRef());
439 // add a messenger to the launch message that will be used to
440 // dispatch scripting calls from apps to the PoseView
441 refsMessage
.AddMessenger("TrackerViewToken", BMessenger(this));
442 be_app
->PostMessage(&refsMessage
);
448 BEntry
entry(fModel
->EntryRef());
449 if (!fModel
->HasLocalizedName()
450 && ConfirmChangeIfWellKnownDirectory(&entry
, kRename
)) {
451 fAttributeView
->BeginEditingTitle();
458 bool force
= (modifiers() & B_OPTION_KEY
) != 0;
460 if (entry
.SetTo(fModel
->EntryRef(), true) == B_OK
) {
462 if (entry
.GetPath(&path
) == B_OK
)
463 update_mime_info(path
.Path(), true, false, force
? 2 : 1);
468 case kRecalculateSize
:
471 // Wait until any current CalcSize thread has terminated before
472 // starting a new one
474 wait_for_thread(fCalcThreadID
, &result
);
476 // Start recalculating..
478 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS
));
479 fCalcThreadID
= spawn_thread(BInfoWindow::CalcSize
, "CalcSize",
480 B_NORMAL_PRIORITY
, this);
481 resume_thread(fCalcThreadID
);
486 OpenFilePanel(fModel
->EntryRef());
489 // An item was dropped into the window
491 // If we are not a SymLink, just ignore the request
492 if (!fModel
->IsSymLink())
494 // supposed to fall through
495 // An item was selected from the file panel
497 case kNewTargetSelected
:
499 // Extract the BEntry, and set its full path to the string value
504 if (message
->FindRef("refs", &ref
) == B_OK
505 && targetEntry
.SetTo(&ref
, true) == B_OK
506 && targetEntry
.Exists()) {
507 // We now have to re-target the broken symlink. Unfortunately,
508 // there's no way to change the target of an existing symlink.
509 // So we have to delete the old one and create a new one.
510 // First, stop watching the broken node
511 // (we don't want this window to quit when the node
517 BEntry
tmpEntry(TargetModel()->EntryRef());
518 if (tmpEntry
.GetParent(&parent
) != B_OK
)
522 BString
name(TargetModel()->Name());
524 // Extract path for new target
527 if (target
.GetPath(&targetPath
) != B_OK
)
530 // Preserve the original attributes
531 AttributeStreamMemoryNode memoryNode
;
533 BModelOpener
opener(TargetModel());
534 AttributeStreamFileNode
original(TargetModel()->Node());
535 memoryNode
<< original
;
538 // Delete the broken node.
539 BEntry
oldEntry(TargetModel()->EntryRef());
544 parent
.CreateSymLink(name
.String(), targetPath
.Path(), &link
);
546 // Update our Model()
547 BEntry
symEntry(&parent
, name
.String());
548 fModel
->SetTo(&symEntry
);
550 BModelWriteOpener
opener(TargetModel());
552 // Copy the attributes back
553 AttributeStreamFileNode
newNode(TargetModel()->Node());
554 newNode
<< memoryNode
;
556 // Start watching this again
557 TTracker::WatchNode(TargetModel()->NodeRef(),
558 B_WATCH_ALL
| B_WATCH_MOUNT
, this);
560 // Tell the attribute view about this new model
561 fAttributeView
->ReLinkTargetModel(TargetModel());
567 // File panel window has closed
570 // It's no longer open
571 fFilePanelOpen
= false;
575 // Sanity check that this isn't the boot volume
576 // (The unmount menu item has been disabled in this
577 // case, but the shortcut is still active)
578 if (fModel
->IsVolume()) {
580 BVolumeRoster().GetBootVolume(&boot
);
581 BVolume
volume(fModel
->NodeRef()->device
);
582 if (volume
!= boot
) {
583 TTracker
* tracker
= dynamic_cast<TTracker
*>(be_app
);
585 tracker
->SaveAllPoseLocations();
587 BMessage
unmountMessage(kUnmountVolume
);
588 unmountMessage
.AddInt32("device_id", volume
.Device());
589 be_app
->PostMessage(&unmountMessage
);
599 switch (message
->FindInt32("opcode")) {
600 case B_ENTRY_REMOVED
:
603 message
->FindInt32("device", &itemNode
.device
);
604 message
->FindInt64("node", &itemNode
.node
);
605 // our window itself may be deleted
606 if (*TargetModel()->NodeRef() == itemNode
)
614 fAttributeView
->ModelChanged(TargetModel(), message
);
615 // must be called before the
616 // FilePermissionView::ModelChanged()
617 // call, because it changes the model...
620 if (fPermissionsView
!= NULL
)
621 fPermissionsView
->ModelChanged(TargetModel());
624 case B_DEVICE_UNMOUNTED
:
626 // We were watching a volume that is no longer
627 // mounted, we might as well quit
629 // Only the device information is available
630 message
->FindInt32("device", &itemNode
.device
);
631 if (TargetModel()->NodeRef()->device
== itemNode
.device
)
641 case kPermissionsSelected
:
643 BRect
permissionsBounds(kBorderWidth
+ 1,
644 fAttributeView
->Bounds().bottom
,
645 fAttributeView
->Bounds().right
,
646 fAttributeView
->Bounds().bottom
+ 103);
648 if (fPermissionsView
== NULL
) {
649 // Only true on first call.
650 fPermissionsView
= new FilePermissionsView(
651 permissionsBounds
, fModel
);
652 ResizeBy(0, permissionsBounds
.Height());
653 fAttributeView
->AddChild(fPermissionsView
);
654 fAttributeView
->SetPermissionsSwitchState(kPaneSwitchOpen
);
655 } else if (fPermissionsView
->IsHidden()) {
656 fPermissionsView
->ModelChanged(fModel
);
657 fPermissionsView
->Show();
658 ResizeBy(0, permissionsBounds
.Height());
659 fAttributeView
->SetPermissionsSwitchState(kPaneSwitchOpen
);
661 fPermissionsView
->Hide();
662 ResizeBy(0, -permissionsBounds
.Height());
663 fAttributeView
->SetPermissionsSwitchState(kPaneSwitchClosed
);
669 _inherited::MessageReceived(message
);
676 BInfoWindow::GetSizeString(BString
& result
, off_t size
, int32 fileCount
)
678 static BMessageFormat
sizeFormat(B_TRANSLATE(
679 "{0, plural, one{(# byte)} other{(# bytes)}}"));
680 static BMessageFormat
countFormat(B_TRANSLATE(
681 "{0, plural, one{for # file} other{for # files}}"));
683 char sizeBuffer
[128];
684 result
<< string_for_size((double)size
, sizeBuffer
, sizeof(sizeBuffer
));
686 if (size
>= kKBSize
) {
689 sizeFormat
.Format(result
, size
);
690 // "bytes" translation could come from string_for_size
691 // which could be part of the localekit itself
694 if (fileCount
!= 0) {
696 countFormat
.Format(result
, fileCount
);
702 BInfoWindow::CalcSize(void* castToWindow
)
704 BInfoWindow
* window
= static_cast<BInfoWindow
*>(castToWindow
);
705 BDirectory
dir(window
->TargetModel()->EntryRef());
707 FSGetTrashDir(&trashDir
, window
->TargetModel()->EntryRef()->device
);
708 if (dir
.InitCheck() != B_OK
) {
709 if (window
->StopCalc())
712 AutoLock
<BWindow
> lock(window
);
716 window
->SetSizeString(B_TRANSLATE("Error calculating folder size."));
720 BEntry dirEntry
, trashEntry
;
721 dir
.GetEntry(&dirEntry
);
722 trashDir
.GetEntry(&trashEntry
);
726 // check if user has asked for trash dir info
727 if (dirEntry
!= trashEntry
) {
728 // if not, perform normal info calculations
732 CopyLoopControl loopControl
;
733 FSRecursiveCalcSize(window
, &loopControl
, &dir
, &size
, &fileCount
,
736 // got the size value, update the size string
737 GetSizeString(sizeString
, size
, fileCount
);
739 // in the trash case, iterate through and sum up
740 // size/counts for all present trash dirs
741 off_t totalSize
= 0, currentSize
;
742 int32 totalFileCount
= 0, currentFileCount
;
743 int32 totalDirCount
= 0, currentDirCount
;
744 BVolumeRoster volRoster
;
747 while (volRoster
.GetNextVolume(&volume
) == B_OK
) {
748 if (!volume
.IsPersistent())
752 currentFileCount
= 0;
756 if (FSGetTrashDir(&trashDir
, volume
.Device()) == B_OK
) {
757 CopyLoopControl loopControl
;
758 FSRecursiveCalcSize(window
, &loopControl
, &trashDir
,
759 ¤tSize
, ¤tFileCount
, ¤tDirCount
);
760 totalSize
+= currentSize
;
761 totalFileCount
+= currentFileCount
;
762 totalDirCount
+= currentDirCount
;
765 GetSizeString(sizeString
, totalSize
, totalFileCount
);
768 if (window
->StopCalc()) {
769 // window closed, bail
773 AutoLock
<BWindow
> lock(window
);
775 window
->SetSizeString(sizeString
.String());
782 BInfoWindow::SetSizeString(const char* sizeString
)
785 = dynamic_cast<AttributeView
*>(FindView("attr_view"));
787 view
->SetSizeString(sizeString
);
792 BInfoWindow::OpenFilePanel(const entry_ref
* ref
)
794 // Open a file dialog box to allow the user to select a new target
796 if (fFilePanel
== NULL
) {
797 BMessenger
runner(this);
798 BMessage
message(kNewTargetSelected
);
799 fFilePanel
= new BFilePanel(B_OPEN_PANEL
, &runner
, ref
,
800 B_FILE_NODE
| B_SYMLINK_NODE
| B_DIRECTORY_NODE
,
803 if (fFilePanel
!= NULL
) {
804 fFilePanel
->SetButtonLabel(B_DEFAULT_BUTTON
,
805 B_TRANSLATE("Select"));
806 fFilePanel
->Window()->ResizeTo(500, 300);
807 BString
title(B_TRANSLATE_COMMENT("Link \"%name\" to:",
808 "File dialog title for new sym link"));
809 title
.ReplaceFirst("%name", fModel
->Name());
810 fFilePanel
->Window()->SetTitle(title
.String());
812 fFilePanelOpen
= true;
814 } else if (!fFilePanelOpen
) {
816 fFilePanelOpen
= true;
818 fFilePanelOpen
= true;
819 fFilePanel
->Window()->Activate(true);
824 // #pragma mark - AttributeView
827 AttributeView::AttributeView(BRect rect
, Model
* model
)
829 BView(rect
, "attr_view", B_FOLLOW_ALL_SIDES
,
830 B_WILL_DRAW
| B_PULSE_NEEDED
),
832 fPreferredAppMenu(NULL
),
838 fTrackingState(no_track
),
839 fIsDropTarget(false),
840 fTitleEditView(NULL
),
844 fCurrentLinkColorWhich(B_LINK_TEXT_COLOR
),
845 fCurrentPathColorWhich(fCurrentLinkColorWhich
)
847 // Set view color to standard background grey
848 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
849 SetFont(be_plain_font
);
850 // If the model is a symlink, then we deference the model to
851 // get the targets icon
852 if (fModel
->IsSymLink()) {
853 Model
* resolvedModel
= new Model(model
->EntryRef(), true, true);
854 if (resolvedModel
->InitCheck() == B_OK
)
855 fIconModel
= resolvedModel
;
856 // broken link, just show the symlink
858 delete resolvedModel
;
861 // Create the rect for displaying the icon
862 fIconRect
.Set(0, 0, B_LARGE_ICON
- 1, B_LARGE_ICON
- 1);
863 // Offset taken from BAlert
864 fIconRect
.OffsetBy(kIconHorizOffset
, kIconVertOffset
);
867 // The magic numbers are used to properly calculate the rect so that
868 // when the editing text view is displayed, the position of the text
871 font_height fontMetrics
;
872 GetFont(¤tFont
);
873 currentFont
.GetHeight(&fontMetrics
);
875 fTitleRect
.left
= fIconRect
.right
+ 5;
877 fTitleRect
.bottom
= fontMetrics
.ascent
+ 1;
878 fTitleRect
.right
= min_c(
879 fTitleRect
.left
+ currentFont
.StringWidth(fModel
->Name()),
880 Bounds().Width() - 5);
881 // Offset so that it centers with the icon
882 fTitleRect
.OffsetBy(0,
883 fIconRect
.top
+ ((fIconRect
.Height() - fTitleRect
.Height()) / 2));
884 // Make some room for the border for when we are in edit mode
885 // (Negative numbers increase the size of the rect)
886 fTitleRect
.InsetBy(-1, -2);
890 fSizeRect
.Set(0, 0, 0, 0);
892 // Find offset for attributes, might be overiden below if there
893 // is a prefered handle menu displayed
894 currentFont
.SetSize(currentFont
.Size() - 2);
895 fDivider
= currentFont
.StringWidth(B_TRANSLATE("Description:"))
896 + kBorderMargin
+ kBorderWidth
+ 1;
897 // Add a preferred handler pop-up menu if this item
898 // is a file...This goes in place of the Link To:
900 if (model
->IsFile()) {
901 BMimeType
mime(fModel
->MimeType());
902 BNodeInfo
nodeInfo(fModel
->Node());
904 // But don't add the menu if the file is executable
905 if (!fModel
->IsExecutable()) {
906 float lineHeight
= CurrentFontHeight();
908 BRect
preferredAppRect(kBorderWidth
+ kBorderMargin
,
909 fTitleRect
.bottom
+ (lineHeight
* 7),
910 Bounds().Width() - 5, fTitleRect
.bottom
+ (lineHeight
* 8));
911 fPreferredAppMenu
= new BMenuField(preferredAppRect
, "", "",
913 currentFont
.SetSize(currentFont
.Size() + 2);
914 fDivider
= currentFont
.StringWidth(B_TRANSLATE("Opens with:"))
916 fPreferredAppMenu
->SetDivider(fDivider
);
917 fDivider
+= (preferredAppRect
.left
- 2);
918 fPreferredAppMenu
->SetFont(¤tFont
);
919 fPreferredAppMenu
->SetHighUIColor(B_PANEL_TEXT_COLOR
);
920 fPreferredAppMenu
->SetLabel(B_TRANSLATE("Opens with:"));
922 char prefSignature
[B_MIME_TYPE_LENGTH
];
923 nodeInfo
.GetPreferredApp(prefSignature
);
925 BMessage supportingAppList
;
926 mime
.GetSupportingApps(&supportingAppList
);
928 // Add the default menu item and set it to marked
930 result
= new BMenuItem(B_TRANSLATE("Default application"),
931 new BMessage(kSetPreferredApp
));
932 result
->SetTarget(this);
933 fPreferredAppMenu
->Menu()->AddItem(result
);
934 result
->SetMarked(true);
936 for (int32 index
= 0; ; index
++) {
937 const char* signature
;
938 if (supportingAppList
.FindString("applications", index
,
939 &signature
) != B_OK
) {
943 // Only add separator item if there are more items
945 fPreferredAppMenu
->Menu()->AddSeparatorItem();
947 BMessage
* itemMessage
= new BMessage(kSetPreferredApp
);
948 itemMessage
->AddString("signature", signature
);
950 status_t err
= B_ERROR
;
953 if (signature
&& signature
[0])
954 err
= be_roster
->FindApp(signature
, &entry
);
957 result
= new BMenuItem(signature
, itemMessage
);
959 result
= new BMenuItem(entry
.name
, itemMessage
);
961 result
->SetTarget(this);
962 fPreferredAppMenu
->Menu()->AddItem(result
);
963 if (strcmp(signature
, prefSignature
) == 0)
964 result
->SetMarked(true);
967 AddChild(fPreferredAppMenu
);
971 fPermissionsSwitch
= new PaneSwitch(BRect(), "Permissions");
972 fPermissionsSwitch
->SetMessage(new BMessage(kPermissionsSelected
));
973 fPermissionsSwitch
->SetLabels(NULL
, B_TRANSLATE("Permissions"));
974 AddChild(fPermissionsSwitch
);
975 fPermissionsSwitch
->ResizeToPreferred();
976 fPermissionsSwitch
->MoveTo(kBorderWidth
+ 3,
977 Bounds().bottom
- 3 - fPermissionsSwitch
->Bounds().Height());
983 AttributeView::~AttributeView()
985 if (fPathWindow
->Lock())
988 if (fLinkWindow
->Lock())
991 if (fDescWindow
->Lock())
994 if (fIconModel
!= fModel
)
1000 AttributeView::InitStrings(const Model
* model
)
1003 char kind
[B_MIME_TYPE_LENGTH
];
1005 ASSERT(model
->IsNodeOpen());
1007 BRect
drawBounds(Bounds());
1008 drawBounds
.left
= fDivider
;
1010 // We'll do our own truncation later on in Draw()
1011 WidgetAttributeText::AttrAsString(model
, &fCreatedStr
, kAttrStatCreated
,
1012 B_TIME_TYPE
, drawBounds
.Width() - kBorderMargin
, this);
1013 WidgetAttributeText::AttrAsString(model
, &fModifiedStr
, kAttrStatModified
,
1014 B_TIME_TYPE
, drawBounds
.Width() - kBorderMargin
, this);
1015 WidgetAttributeText::AttrAsString(model
, &fPathStr
, kAttrPath
,
1016 B_STRING_TYPE
, 0, this);
1018 // Use the same method as used to resolve fIconModel, which handles
1019 // both absolute and relative symlinks. if the link is broken, try to
1020 // get a little more information.
1021 if (model
->IsSymLink()) {
1022 bool linked
= false;
1024 Model
resolvedModel(model
->EntryRef(), true, true);
1025 if (resolvedModel
.InitCheck() == B_OK
) {
1026 // Get the path of the link
1027 BPath traversedPath
;
1028 resolvedModel
.GetPath(&traversedPath
);
1030 // If the BPath is initialized, then check the file for existence
1031 if (traversedPath
.InitCheck() == B_OK
) {
1032 BEntry
entry(traversedPath
.Path(), false);
1033 // look at the target itself
1034 if (entry
.InitCheck() == B_OK
&& entry
.Exists())
1039 // always show the target as it is: absolute or relative!
1040 BSymLink
symLink(model
->EntryRef());
1041 char linkToPath
[B_PATH_NAME_LENGTH
];
1042 symLink
.ReadLink(linkToPath
, B_PATH_NAME_LENGTH
);
1043 fLinkToStr
= linkToPath
;
1045 // link points to missing object
1046 fLinkToStr
+= B_TRANSLATE(" (broken)");
1048 } else if (model
->IsExecutable()) {
1049 if (((Model
*)model
)->GetLongVersionString(fDescStr
,
1050 B_APP_VERSION_KIND
) == B_OK
) {
1051 // we want a flat string, so replace all newlines and tabs
1053 fDescStr
.ReplaceAll('\n', ' ');
1054 fDescStr
.ReplaceAll('\t', ' ');
1059 if (mime
.SetType(model
->MimeType()) == B_OK
1060 && mime
.GetShortDescription(kind
) == B_OK
)
1063 if (fKindStr
.Length() == 0)
1064 fKindStr
= model
->MimeType();
1069 AttributeView::AttachedToWindow()
1071 BFont
font(be_plain_font
);
1073 font
.SetSpacing(B_BITMAP_SPACING
);
1077 if (fPreferredAppMenu
)
1078 fPreferredAppMenu
->Menu()->SetTargetForItems(this);
1080 _inherited::AttachedToWindow();
1085 AttributeView::Pulse()
1088 _inherited::Pulse();
1093 AttributeView::ModelChanged(Model
* model
, BMessage
* message
)
1095 BRect
drawBounds(Bounds());
1096 drawBounds
.left
= fDivider
;
1098 switch (message
->FindInt32("opcode")) {
1103 dirNode
.device
= itemNode
.device
= message
->FindInt32("device");
1104 message
->FindInt64("to directory", &dirNode
.node
);
1105 message
->FindInt64("node", &itemNode
.node
);
1108 if (message
->FindString("name", &name
) != B_OK
)
1111 // ensure notification is for us
1112 if (*model
->NodeRef() == itemNode
1113 // For volumes, the device ID is obviously not handled in a
1114 // consistent way; the node monitor sends us the ID of the
1115 // parent device, while the model is set to the device of the
1116 // volume directly - this hack works for volumes that are
1117 // mounted in the root directory
1118 || (model
->IsVolume()
1119 && itemNode
.device
== 1
1120 && itemNode
.node
== model
->NodeRef()->node
)) {
1121 model
->UpdateEntryRef(&dirNode
, name
);
1123 title
<< name
<< B_TRANSLATE(" info");
1124 Window()->SetTitle(title
.String());
1125 WidgetAttributeText::AttrAsString(model
, &fPathStr
, kAttrPath
,
1126 B_STRING_TYPE
, 0, this);
1132 case B_STAT_CHANGED
:
1133 if (model
->OpenNode() == B_OK
) {
1134 WidgetAttributeText::AttrAsString(model
, &fCreatedStr
,
1135 kAttrStatCreated
, B_TIME_TYPE
, drawBounds
.Width()
1136 - kBorderMargin
, this);
1137 WidgetAttributeText::AttrAsString(model
, &fModifiedStr
,
1138 kAttrStatModified
, B_TIME_TYPE
, drawBounds
.Width()
1139 - kBorderMargin
, this);
1141 // don't change the size if it's a directory
1142 if (!model
->IsDirectory()) {
1143 fLastSize
= model
->StatBuf()->st_size
;
1145 BInfoWindow::GetSizeString(fSizeString
, fLastSize
, 0);
1151 case B_ATTR_CHANGED
:
1153 // watch for icon updates
1154 const char* attrName
;
1155 if (message
->FindString("attr", &attrName
) == B_OK
) {
1156 if (strcmp(attrName
, kAttrLargeIcon
) == 0
1157 || strcmp(attrName
, kAttrIcon
) == 0) {
1158 IconCache::sIconCache
->IconChanged(model
->ResolveIfLink());
1160 } else if (strcmp(attrName
, kAttrMIMEType
) == 0) {
1161 if (model
->OpenNode() == B_OK
) {
1162 model
->AttrChanged(attrName
);
1176 // Update the icon stuff
1177 if (fIconModel
!= fModel
) {
1183 if (fModel
->IsSymLink()) {
1184 // if we are looking at a symlink, deference the model and look
1186 Model
* resolvedModel
= new Model(model
->EntryRef(), true, true);
1187 if (resolvedModel
->InitCheck() == B_OK
) {
1188 if (fIconModel
!= fModel
)
1190 fIconModel
= resolvedModel
;
1193 delete resolvedModel
;
1199 drawBounds
.left
= fDivider
;
1200 Invalidate(drawBounds
);
1204 // This only applies to symlinks. If the target of the symlink
1205 // was changed, then we have to update the entire model.
1206 // (Since in order to re-target a symlink, we had to delete
1207 // the old model and create a new one; BSymLink::SetTarget(),
1211 AttributeView::ReLinkTargetModel(Model
* model
)
1214 if (fModel
->IsSymLink()) {
1215 Model
* resolvedModel
= new Model(model
->EntryRef(), true, true);
1216 if (resolvedModel
->InitCheck() == B_OK
) {
1217 if (fIconModel
!= fModel
)
1219 fIconModel
= resolvedModel
;
1221 fIconModel
= fModel
;
1222 delete resolvedModel
;
1226 Invalidate(Bounds());
1231 AttributeView::MouseDown(BPoint where
)
1234 fModel
->GetEntry(&entry
);
1236 // Assume this isn't part of a double click
1237 fDoubleClick
= false;
1239 // Start tracking the mouse if we are in any of the hotspots
1240 if (fLinkRect
.Contains(where
)) {
1241 InvertRect(fLinkRect
);
1242 fTrackingState
= link_track
;
1243 } else if (fPathRect
.Contains(where
)) {
1244 InvertRect(fPathRect
);
1245 fTrackingState
= path_track
;
1246 } else if (fTitleRect
.Contains(where
)) {
1247 if (!fModel
->HasLocalizedName()
1248 && ConfirmChangeIfWellKnownDirectory(&entry
, kRename
, true)) {
1249 BeginEditingTitle();
1251 } else if (fTitleEditView
) {
1252 FinishEditingTitle(true);
1253 } else if (fSizeRect
.Contains(where
)) {
1254 if (fModel
->IsDirectory() && !fModel
->IsVolume()
1255 && !fModel
->IsRoot()) {
1256 InvertRect(fSizeRect
);
1257 fTrackingState
= size_track
;
1259 fTrackingState
= no_track
;
1260 } else if (fIconRect
.Contains(where
)) {
1262 Window()->CurrentMessage()->FindInt32("buttons", (int32
*)&buttons
);
1263 if (SecondaryMouseButtonDown(modifiers(), buttons
)) {
1264 // Show contextual menu
1265 BPopUpMenu
* contextMenu
1266 = new BPopUpMenu("FileContext", false, false);
1268 BuildContextMenu(contextMenu
);
1269 contextMenu
->SetAsyncAutoDestruct(true);
1270 contextMenu
->Go(ConvertToScreen(where
), true, true,
1271 ConvertToScreen(fIconRect
));
1274 // Check to see if the point is actually on part of the icon,
1275 // versus just in the container rect. The icons are always
1276 // the large version
1278 offsetPoint
.x
= where
.x
- fIconRect
.left
;
1279 offsetPoint
.y
= where
.y
- fIconRect
.top
;
1280 if (IconCache::sIconCache
->IconHitTest(offsetPoint
, fIconModel
,
1281 kNormalIcon
, B_LARGE_ICON
)) {
1282 // Can't drag the trash anywhere..
1283 fTrackingState
= fModel
->IsTrash()
1284 ? open_only_track
: icon_track
;
1286 // Check for possible double click
1287 if (abs((int32
)(fClickPoint
.x
- where
.x
)) < kDragSlop
1288 && abs((int32
)(fClickPoint
.y
- where
.y
)) < kDragSlop
) {
1290 Window()->CurrentMessage()->FindInt32("clicks",
1293 // This checks the* previous* click point
1294 if (clickCount
== 2) {
1295 offsetPoint
.x
= fClickPoint
.x
- fIconRect
.left
;
1296 offsetPoint
.y
= fClickPoint
.y
- fIconRect
.top
;
1298 = IconCache::sIconCache
->IconHitTest(offsetPoint
,
1299 fIconModel
, kNormalIcon
, B_LARGE_ICON
);
1306 fClickPoint
= where
;
1308 SetMouseEventMask(B_POINTER_EVENTS
, B_NO_POINTER_HISTORY
);
1313 AttributeView::MouseMoved(BPoint where
, uint32
, const BMessage
* dragMessage
)
1315 if (dragMessage
!= NULL
&& dragMessage
->ReturnAddress() != BMessenger(this)
1316 && dragMessage
->what
== B_SIMPLE_DATA
1317 && BPoseView::CanHandleDragSelection(fModel
, dragMessage
,
1318 (modifiers() & B_CONTROL_KEY
) != 0)) {
1319 // highlight drag target
1320 bool overTarget
= fIconRect
.Contains(where
);
1321 SetDrawingMode(B_OP_OVER
);
1322 if (overTarget
!= fIsDropTarget
) {
1323 IconCache::sIconCache
->Draw(fIconModel
, this, fIconRect
.LeftTop(),
1324 overTarget
? kSelectedIcon
: kNormalIcon
, B_LARGE_ICON
, true);
1325 fIsDropTarget
= overTarget
;
1329 fCurrentLinkColorWhich
= B_LINK_TEXT_COLOR
;
1330 fCurrentPathColorWhich
= fCurrentLinkColorWhich
;
1332 switch (fTrackingState
) {
1334 if (fLinkRect
.Contains(where
) != fMouseDown
) {
1335 fMouseDown
= !fMouseDown
;
1336 InvertRect(fLinkRect
);
1341 if (fPathRect
.Contains(where
) != fMouseDown
) {
1342 fMouseDown
= !fMouseDown
;
1343 InvertRect(fPathRect
);
1348 if (fSizeRect
.Contains(where
) != fMouseDown
) {
1349 fMouseDown
= !fMouseDown
;
1350 InvertRect(fSizeRect
);
1355 if (fMouseDown
&& !fDragging
1356 && (abs((int32
)(where
.x
- fClickPoint
.x
)) > kDragSlop
1357 || abs((int32
)(where
.y
- fClickPoint
.y
)) > kDragSlop
)) {
1358 // Find the required height
1362 float height
= CurrentFontHeight()
1363 + fIconRect
.Height() + 8;
1364 BRect
rect(0, 0, min_c(fIconRect
.Width()
1365 + font
.StringWidth(fModel
->Name()) + 4,
1366 fIconRect
.Width() * 3), height
);
1367 BBitmap
* dragBitmap
= new BBitmap(rect
, B_RGBA32
, true);
1369 BView
* view
= new BView(dragBitmap
->Bounds(), "",
1371 dragBitmap
->AddChild(view
);
1372 view
->SetOrigin(0, 0);
1373 BRect
clipRect(view
->Bounds());
1375 newClip
.Set(clipRect
);
1376 view
->ConstrainClippingRegion(&newClip
);
1378 // Transparent draw magic
1379 view
->SetHighColor(0, 0, 0, 0);
1380 view
->FillRect(view
->Bounds());
1381 view
->SetDrawingMode(B_OP_ALPHA
);
1382 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
1383 textColor
.alpha
= 128;
1384 // set transparency by value
1385 view
->SetHighColor(textColor
);
1386 view
->SetBlendingMode(B_CONSTANT_ALPHA
, B_ALPHA_COMPOSITE
);
1389 float hIconOffset
= (rect
.Width() - fIconRect
.Width()) / 2;
1390 IconCache::sIconCache
->Draw(fIconModel
, view
,
1391 BPoint(hIconOffset
, 0), kNormalIcon
, B_LARGE_ICON
, true);
1393 // See if we need to truncate the string
1394 BString
nameString(fModel
->Name());
1395 if (view
->StringWidth(fModel
->Name()) > rect
.Width()) {
1396 view
->TruncateString(&nameString
, B_TRUNCATE_END
,
1401 font_height fontHeight
;
1402 font
.GetHeight(&fontHeight
);
1403 float leftText
= (view
->StringWidth(nameString
.String())
1404 - fIconRect
.Width()) / 2;
1405 view
->MovePenTo(BPoint(hIconOffset
- leftText
+ 2,
1406 fIconRect
.Height() + (fontHeight
.ascent
+ 2)));
1407 view
->DrawString(nameString
.String());
1410 dragBitmap
->Unlock();
1412 BMessage
dragMessage(B_REFS_RECEIVED
);
1413 dragMessage
.AddPoint("click_pt", fClickPoint
);
1416 GetMouse(&tmpLoc
, &button
);
1418 dragMessage
.AddInt32("buttons", (int32
)button
);
1420 dragMessage
.AddInt32("be:actions",
1421 (modifiers() & B_OPTION_KEY
) != 0
1422 ? B_COPY_TARGET
: B_MOVE_TARGET
);
1423 dragMessage
.AddRef("refs", fModel
->EntryRef());
1424 DragMessage(&dragMessage
, dragBitmap
, B_OP_ALPHA
,
1425 BPoint((fClickPoint
.x
- fIconRect
.left
)
1426 + hIconOffset
, fClickPoint
.y
- fIconRect
.top
), this);
1431 case open_only_track
:
1432 // Special type of entry that can't be renamed or drag and dropped
1433 // It can only be opened by double clicking on the icon
1438 // Only consider this if the window is the active window.
1439 // We have to manually get the mouse here in the event that the
1440 // mouse is over a pop-up window
1443 GetMouse(&point
, &buttons
);
1444 if (Window()->IsActive() && !buttons
) {
1445 // If we are down here, then that means that we're tracking
1446 // the mouse but not from a mouse down. In this case, we're
1447 // just interested in knowing whether or not we need to
1448 // display the "pop-up" version of the path or link text.
1449 BScreen
screen(Window());
1452 float maxWidth
= (Bounds().Width()
1453 - (fDivider
+ kBorderMargin
));
1455 if (fPathRect
.Contains(point
)) {
1456 if (fCurrentPathColorWhich
!= B_LINK_HOVER_COLOR
)
1457 fCurrentPathColorWhich
= B_LINK_HOVER_COLOR
;
1459 if (font
.StringWidth(fPathStr
.String()) > maxWidth
) {
1460 fTrackingState
= no_track
;
1461 BRect
rect(fPathRect
);
1462 rect
.OffsetBy(Window()->Frame().left
,
1463 Window()->Frame().top
);
1465 if (fPathWindow
== NULL
1466 || BMessenger(fPathWindow
).IsValid() == false) {
1467 fPathWindow
= OpenToolTipWindow(screen
, rect
,
1468 "fPathWindow", fPathStr
.String(),
1470 new BMessage(kOpenLinkSource
));
1473 } else if (fLinkRect
.Contains(point
)) {
1475 if (fCurrentLinkColorWhich
!= B_LINK_HOVER_COLOR
)
1476 fCurrentLinkColorWhich
= B_LINK_HOVER_COLOR
;
1478 if (font
.StringWidth(fLinkToStr
.String()) > maxWidth
) {
1479 fTrackingState
= no_track
;
1480 BRect
rect(fLinkRect
);
1481 rect
.OffsetBy(Window()->Frame().left
,
1482 Window()->Frame().top
);
1485 || BMessenger(fLinkWindow
).IsValid() == false) {
1486 fLinkWindow
= OpenToolTipWindow(screen
, rect
,
1487 "fLinkWindow", fLinkToStr
.String(),
1489 new BMessage(kOpenLinkTarget
));
1492 } else if (fDescRect
.Contains(point
)
1493 && font
.StringWidth(fDescStr
.String()) > maxWidth
) {
1494 fTrackingState
= no_track
;
1495 BRect
rect(fDescRect
);
1496 rect
.OffsetBy(Window()->Frame().left
,
1497 Window()->Frame().top
);
1500 || BMessenger(fDescWindow
).IsValid() == false) {
1501 fDescWindow
= OpenToolTipWindow(screen
, rect
,
1502 "fDescWindow", fDescStr
.String(),
1503 BMessenger(this), NULL
);
1511 DelayedInvalidate(16666, fPathRect
);
1512 DelayedInvalidate(16666, fLinkRect
);
1517 AttributeView::OpenLinkSource()
1519 OpenParentAndSelectOriginal(fModel
->EntryRef());
1524 AttributeView::OpenLinkTarget()
1526 Model
resolvedModel(fModel
->EntryRef(), true, true);
1528 if (resolvedModel
.InitCheck() == B_OK
) {
1529 // Get the path of the link
1530 BPath traversedPath
;
1531 resolvedModel
.GetPath(&traversedPath
);
1533 // If the BPath is initialized, then check the file for existence
1534 if (traversedPath
.InitCheck() == B_OK
)
1535 entry
.SetTo(traversedPath
.Path());
1537 if (entry
.InitCheck() != B_OK
|| !entry
.Exists()) {
1538 // Open a file dialog panel to allow the user to relink.
1539 BInfoWindow
* window
= dynamic_cast<BInfoWindow
*>(Window());
1541 window
->OpenFilePanel(fModel
->EntryRef());
1546 printf("Opening link target: %s\n", path
.Path());
1547 OpenParentAndSelectOriginal(&ref
);
1553 AttributeView::MouseUp(BPoint where
)
1555 // Are we in the link rect?
1556 if (fTrackingState
== link_track
&& fLinkRect
.Contains(where
)) {
1557 InvertRect(fLinkRect
);
1559 } else if (fTrackingState
== path_track
&& fPathRect
.Contains(where
)) {
1560 InvertRect(fPathRect
);
1562 } else if ((fTrackingState
== icon_track
1563 || fTrackingState
== open_only_track
)
1564 && fIconRect
.Contains(where
)) {
1565 // If it was a double click, then tell Tracker to open the item
1566 // The CurrentMessage() here does* not* have a "clicks" field,
1567 // which is why we are tracking the clicks with this temp var
1569 // Double click, launch.
1570 BMessage
message(B_REFS_RECEIVED
);
1571 message
.AddRef("refs", fModel
->EntryRef());
1573 // add a messenger to the launch message that will be used to
1574 // dispatch scripting calls from apps to the PoseView
1575 message
.AddMessenger("TrackerViewToken", BMessenger(this));
1576 be_app
->PostMessage(&message
);
1577 fDoubleClick
= false;
1579 } else if (fTrackingState
== size_track
&& fSizeRect
.Contains(where
)) {
1581 Window()->PostMessage(kRecalculateSize
);
1584 // End mouse tracking
1587 fTrackingState
= no_track
;
1593 AttributeView::CheckAndSetSize()
1595 if (fModel
->IsVolume() || fModel
->IsRoot()) {
1596 off_t freeBytes
= 0;
1599 if (fModel
->IsVolume()) {
1600 BVolume
volume(fModel
->NodeRef()->device
);
1601 freeBytes
= volume
.FreeBytes();
1602 capacity
= volume
.Capacity();
1604 // iterate over all volumes
1605 BVolumeRoster volumeRoster
;
1607 while (volumeRoster
.GetNextVolume(&volume
) == B_OK
) {
1608 freeBytes
+= volume
.FreeBytes();
1609 capacity
+= volume
.Capacity();
1613 if (fFreeBytes
== freeBytes
)
1616 fFreeBytes
= freeBytes
;
1618 fSizeString
.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
1621 string_for_size(capacity
, sizeStr
, sizeof(sizeStr
));
1622 fSizeString
.ReplaceFirst("%capacity", sizeStr
);
1623 string_for_size(capacity
- fFreeBytes
, sizeStr
, sizeof(sizeStr
));
1624 fSizeString
.ReplaceFirst("%used", sizeStr
);
1625 string_for_size(fFreeBytes
, sizeStr
, sizeof(sizeStr
));
1626 fSizeString
.ReplaceFirst("%free", sizeStr
);
1628 } else if (fModel
->IsFile()) {
1629 // poll for size changes because they do not get node monitored
1630 // until a file gets closed (with the old BFS)
1632 BModelOpener
opener(fModel
);
1634 if (fModel
->InitCheck() != B_OK
1635 || fModel
->Node()->GetStat(&statBuf
) != B_OK
) {
1639 if (fLastSize
== statBuf
.st_size
)
1642 fLastSize
= statBuf
.st_size
;
1644 BInfoWindow::GetSizeString(fSizeString
, fLastSize
, 0);
1648 BRect
bounds(Bounds());
1649 float lineHeight
= CurrentFontHeight() + 2;
1650 bounds
.Set(fDivider
, fIconRect
.bottom
, bounds
.right
,
1651 fIconRect
.bottom
+ lineHeight
);
1657 AttributeView::MessageReceived(BMessage
* message
)
1659 if (message
->WasDropped()
1660 && message
->what
== B_SIMPLE_DATA
1661 && message
->ReturnAddress() != BMessenger(this)
1662 && fIconRect
.Contains(ConvertFromScreen(message
->DropPoint()))
1663 && BPoseView::CanHandleDragSelection(fModel
, message
,
1664 (modifiers() & B_CONTROL_KEY
) != 0)) {
1665 BPoseView::HandleDropCommon(message
, fModel
, 0, this,
1666 message
->DropPoint());
1667 Invalidate(fIconRect
);
1671 switch (message
->what
) {
1672 case kSetPreferredApp
:
1674 BNode
node(fModel
->EntryRef());
1675 BNodeInfo
nodeInfo(&node
);
1677 const char* newSignature
;
1678 if (message
->FindString("signature", &newSignature
) != B_OK
)
1679 newSignature
= NULL
;
1681 fModel
->SetPreferredAppSignature(newSignature
);
1682 nodeInfo
.SetPreferredApp(newSignature
);
1686 case kOpenLinkSource
:
1690 case kOpenLinkTarget
:
1695 _inherited::MessageReceived(message
);
1702 AttributeView::Draw(BRect
)
1704 // Set the low color for anti-aliasing
1705 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR
));
1707 // Clear the old contents
1708 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR
));
1711 rgb_color labelColor
= ui_color(B_PANEL_TEXT_COLOR
);
1712 rgb_color attributeColor
= mix_color(HighColor(), labelColor
, 192);
1714 // Draw the dark grey area on the left
1715 BRect
drawBounds(Bounds());
1716 drawBounds
.right
= kBorderWidth
;
1717 SetHighUIColor(B_PANEL_BACKGROUND_COLOR
, B_DARKEN_2_TINT
);
1718 FillRect(drawBounds
);
1720 // Draw the icon, straddling the border
1721 SetDrawingMode(B_OP_OVER
);
1722 IconCache::sIconCache
->Draw(fIconModel
, this, fIconRect
.LeftTop(),
1723 kNormalIcon
, B_LARGE_ICON
, true);
1724 SetDrawingMode(B_OP_COPY
);
1726 font_height fontMetrics
;
1728 float lineHeight
= 0;
1730 // Draw the main title if the user is not currently editing it
1731 if (fTitleEditView
== NULL
) {
1732 SetFont(be_bold_font
);
1733 SetFontSize(be_bold_font
->Size());
1734 GetFont(¤tFont
);
1735 currentFont
.GetHeight(&fontMetrics
);
1736 lineHeight
= CurrentFontHeight() + 5;
1737 lineBase
= fTitleRect
.bottom
- fontMetrics
.descent
;
1738 SetHighColor(labelColor
);
1739 MovePenTo(BPoint(fIconRect
.right
+ 6, lineBase
));
1741 // Recalculate the rect width
1742 fTitleRect
.right
= min_c(
1743 fTitleRect
.left
+ currentFont
.StringWidth(fModel
->Name()),
1744 Bounds().Width() - 5);
1745 // Check for possible need of truncation
1746 if (StringWidth(fModel
->Name()) > fTitleRect
.Width()) {
1747 BString
nameString(fModel
->Name());
1748 TruncateString(&nameString
, B_TRUNCATE_END
,
1749 fTitleRect
.Width() - 2);
1750 DrawString(nameString
.String());
1752 DrawString(fModel
->Name());
1755 // Draw the attribute font stuff
1756 SetFont(be_plain_font
);
1757 GetFontHeight(&fontMetrics
);
1758 lineHeight
= CurrentFontHeight() + 5;
1760 // Starting base line for the first string
1761 lineBase
= fTitleRect
.bottom
+ lineHeight
;
1764 SetHighColor(labelColor
);
1765 if (fModel
->IsVolume() || fModel
->IsRoot()) {
1766 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Capacity:"))),
1768 DrawString(B_TRANSLATE("Capacity:"));
1770 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Size:"))),
1772 fSizeRect
.left
= fDivider
+ 2;
1773 fSizeRect
.top
= lineBase
- fontMetrics
.ascent
;
1774 fSizeRect
.bottom
= lineBase
+ fontMetrics
.descent
;
1775 DrawString(B_TRANSLATE("Size:"));
1778 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1779 SetHighColor(attributeColor
);
1780 // Check for possible need of truncation
1781 if (StringWidth(fSizeString
.String())
1782 > (Bounds().Width() - (fDivider
+ kBorderMargin
))) {
1783 BString
tmpString(fSizeString
.String());
1784 TruncateString(&tmpString
, B_TRUNCATE_MIDDLE
,
1785 Bounds().Width() - (fDivider
+ kBorderMargin
));
1786 DrawString(tmpString
.String());
1787 fSizeRect
.right
= fSizeRect
.left
+ StringWidth(tmpString
.String())
1790 DrawString(fSizeString
.String());
1791 fSizeRect
.right
= fSizeRect
.left
+ StringWidth(fSizeString
.String()) + 3;
1793 lineBase
+= lineHeight
;
1796 SetHighColor(labelColor
);
1797 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Created:"))),
1799 DrawString(B_TRANSLATE("Created:"));
1800 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1801 SetHighColor(attributeColor
);
1802 DrawString(fCreatedStr
.String());
1803 lineBase
+= lineHeight
;
1806 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Modified:"))),
1808 SetHighColor(labelColor
);
1809 DrawString(B_TRANSLATE("Modified:"));
1810 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1811 SetHighColor(attributeColor
);
1812 DrawString(fModifiedStr
.String());
1813 lineBase
+= lineHeight
;
1816 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Kind:"))),
1818 SetHighColor(labelColor
);
1819 DrawString(B_TRANSLATE("Kind:"));
1820 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1821 SetHighColor(attributeColor
);
1822 DrawString(fKindStr
.String());
1823 lineBase
+= lineHeight
;
1826 GetFont(&normalFont
);
1829 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Location:"))),
1831 SetHighColor(labelColor
);
1832 DrawString(B_TRANSLATE("Location:"));
1834 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1835 SetHighUIColor(fCurrentPathColorWhich
);
1837 // Check for truncation
1838 if (StringWidth(fPathStr
.String()) > (Bounds().Width()
1839 - (fDivider
+ kBorderMargin
))) {
1840 BString
nameString(fPathStr
.String());
1841 TruncateString(&nameString
, B_TRUNCATE_MIDDLE
,
1842 Bounds().Width() - (fDivider
+ kBorderMargin
));
1843 DrawString(nameString
.String());
1845 DrawString(fPathStr
.String());
1847 // Cache the position of the path
1848 fPathRect
.top
= lineBase
- fontMetrics
.ascent
;
1849 fPathRect
.bottom
= lineBase
+ fontMetrics
.descent
;
1850 fPathRect
.left
= fDivider
+ 2;
1851 fPathRect
.right
= fPathRect
.left
+ StringWidth(fPathStr
.String()) + 3;
1853 lineBase
+= lineHeight
;
1856 if (fModel
->IsSymLink()) {
1857 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Link to:"))),
1859 SetHighColor(labelColor
);
1860 DrawString(B_TRANSLATE("Link to:"));
1861 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1862 SetHighUIColor(fCurrentLinkColorWhich
);
1864 // Check for truncation
1865 if (StringWidth(fLinkToStr
.String()) > (Bounds().Width()
1866 - (fDivider
+ kBorderMargin
))) {
1867 BString
nameString(fLinkToStr
.String());
1868 TruncateString(&nameString
, B_TRUNCATE_MIDDLE
,
1869 Bounds().Width() - (fDivider
+ kBorderMargin
));
1870 DrawString(nameString
.String());
1872 DrawString(fLinkToStr
.String());
1874 // Cache the position of the link field
1875 fLinkRect
.top
= lineBase
- fontMetrics
.ascent
;
1876 fLinkRect
.bottom
= lineBase
+ fontMetrics
.descent
;
1877 fLinkRect
.left
= fDivider
+ 2;
1878 fLinkRect
.right
= fLinkRect
.left
+ StringWidth(fLinkToStr
.String())
1881 // No description field
1882 fDescRect
= BRect(-1, -1, -1, -1);
1883 } else if (fModel
->IsExecutable()) {
1885 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Version:"))),
1887 SetHighColor(labelColor
);
1888 DrawString(B_TRANSLATE("Version:"));
1889 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1890 SetHighColor(attributeColor
);
1892 if (fModel
->GetVersionString(nameString
, B_APP_VERSION_KIND
) == B_OK
)
1893 DrawString(nameString
.String());
1896 lineBase
+= lineHeight
;
1899 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Description:"))),
1901 SetHighColor(labelColor
);
1902 DrawString(B_TRANSLATE("Description:"));
1903 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1904 SetHighColor(attributeColor
);
1905 // Check for truncation
1906 if (StringWidth(fDescStr
.String()) > (Bounds().Width()
1907 - (fDivider
+ kBorderMargin
))) {
1908 BString
nameString(fDescStr
.String());
1909 TruncateString(&nameString
, B_TRUNCATE_MIDDLE
,
1910 Bounds().Width() - (fDivider
+ kBorderMargin
));
1911 DrawString(nameString
.String());
1913 DrawString(fDescStr
.String());
1915 // Cache the position of the description field
1916 fDescRect
.top
= lineBase
- fontMetrics
.ascent
;
1917 fDescRect
.bottom
= lineBase
+ fontMetrics
.descent
;
1918 fDescRect
.left
= fDivider
+ 2;
1919 fDescRect
.right
= fDescRect
.left
+ StringWidth(fDescStr
.String()) + 3;
1922 fLinkRect
= BRect(-1, -1, -1, -1);
1928 AttributeView::BeginEditingTitle()
1930 if (fTitleEditView
!= NULL
)
1933 BFont
font(be_plain_font
);
1934 font
.SetSize(font
.Size() + 2);
1935 BRect
textFrame(fTitleRect
);
1936 textFrame
.right
= Bounds().Width() - 5;
1937 BRect
textRect(textFrame
);
1938 textRect
.OffsetTo(0, 0);
1939 textRect
.InsetBy(1, 1);
1941 // Just make it some really large size, since we don't do any line
1942 // wrapping. The text filter will make sure to scroll the cursor
1945 textRect
.right
= 2000;
1946 fTitleEditView
= new BTextView(textFrame
, "text_editor",
1947 textRect
, &font
, 0, B_FOLLOW_ALL
, B_WILL_DRAW
);
1948 fTitleEditView
->SetText(fModel
->Name());
1949 DisallowFilenameKeys(fTitleEditView
);
1951 // Reset the width of the text rect
1952 textRect
= fTitleEditView
->TextRect();
1953 textRect
.right
= fTitleEditView
->LineWidth() + 20;
1954 fTitleEditView
->SetTextRect(textRect
);
1955 fTitleEditView
->SetWordWrap(false);
1956 // Add filter for catching B_RETURN and B_ESCAPE key's
1957 fTitleEditView
->AddFilter(
1958 new BMessageFilter(B_KEY_DOWN
, AttributeView::TextViewFilter
));
1960 BScrollView
* scrollView
= new BScrollView("BorderView", fTitleEditView
,
1961 0, 0, false, false, B_PLAIN_BORDER
);
1962 AddChild(scrollView
);
1963 fTitleEditView
->SelectAll();
1964 fTitleEditView
->MakeFocus();
1966 Window()->UpdateIfNeeded();
1971 AttributeView::FinishEditingTitle(bool commit
)
1973 if (fTitleEditView
== NULL
)
1976 bool reopen
= false;
1978 const char* text
= fTitleEditView
->Text();
1979 uint32 length
= strlen(text
);
1980 if (commit
&& strcmp(text
, fModel
->Name()) != 0
1981 && length
< B_FILE_NAME_LENGTH
) {
1982 BEntry
entry(fModel
->EntryRef());
1984 if (entry
.InitCheck() == B_OK
1985 && entry
.GetParent(&parent
) == B_OK
) {
1986 if (parent
.Contains(text
)) {
1987 BAlert
* alert
= new BAlert("",
1988 B_TRANSLATE("That name is already taken. "
1989 "Please type another one."),
1991 0, 0, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
1992 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1996 if (fModel
->IsVolume()) {
1997 BVolume
volume(fModel
->NodeRef()->device
);
1998 if (volume
.InitCheck() == B_OK
)
1999 volume
.SetName(text
);
2003 // Adjust the size of the text rect
2004 BFont
currentFont(be_plain_font
);
2005 currentFont
.SetSize(currentFont
.Size() + 2);
2006 fTitleRect
.right
= min_c(fTitleRect
.left
2007 + currentFont
.StringWidth(fTitleEditView
->Text()),
2008 Bounds().Width() - 5);
2011 } else if (length
>= B_FILE_NAME_LENGTH
) {
2012 BAlert
* alert
= new BAlert("",
2013 B_TRANSLATE("That name is too long. Please type another one."),
2015 0, 0, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
2016 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2022 BView
* scrollView
= fTitleEditView
->Parent();
2023 RemoveChild(scrollView
);
2025 fTitleEditView
= NULL
;
2028 BeginEditingTitle();
2033 AttributeView::MakeFocus(bool focus
)
2035 if (!focus
&& fTitleEditView
!= NULL
)
2036 FinishEditingTitle(true);
2041 AttributeView::WindowActivated(bool active
)
2046 if (fTitleEditView
!= NULL
)
2047 FinishEditingTitle(true);
2049 if (fPathWindow
->Lock()) {
2050 fPathWindow
->Quit();
2054 if (fLinkWindow
->Lock()) {
2055 fLinkWindow
->Quit();
2059 if (fDescWindow
->Lock()) {
2060 fDescWindow
->Quit();
2067 AttributeView::CurrentFontHeight()
2071 font_height fontHeight
;
2072 font
.GetHeight(&fontHeight
);
2074 return fontHeight
.ascent
+ fontHeight
.descent
+ fontHeight
.leading
+ 2;
2079 AttributeView::BuildContextMenu(BMenu
* parent
)
2084 // Add navigation menu if this is not a symlink
2085 // Symlink's to directories are OK however!
2086 BEntry
entry(fModel
->EntryRef());
2089 Model
model(&entry
);
2090 bool navigate
= false;
2091 if (model
.InitCheck() == B_OK
) {
2092 if (model
.IsSymLink()) {
2093 // Check if it's to a directory
2094 if (entry
.SetTo(model
.EntryRef(), true) == B_OK
) {
2095 navigate
= entry
.IsDirectory();
2098 } else if (model
.IsDirectory() || model
.IsVolume())
2101 ModelMenuItem
* navigationItem
= NULL
;
2103 navigationItem
= new ModelMenuItem(new Model(model
),
2104 new BNavMenu(model
.Name(), B_REFS_RECEIVED
, be_app
, Window()));
2106 // setup a navigation menu item which will dynamically load items
2107 // as menu items are traversed
2108 BNavMenu
* navMenu
= dynamic_cast<BNavMenu
*>(navigationItem
->Submenu());
2109 if (navMenu
!= NULL
)
2110 navMenu
->SetNavDir(&ref
);
2112 navigationItem
->SetLabel(model
.Name());
2113 navigationItem
->SetEntry(&entry
);
2115 parent
->AddItem(navigationItem
, 0);
2116 parent
->AddItem(new BSeparatorItem(), 1);
2118 BMessage
* message
= new BMessage(B_REFS_RECEIVED
);
2119 message
->AddRef("refs", &ref
);
2120 navigationItem
->SetMessage(message
);
2121 navigationItem
->SetTarget(be_app
);
2124 parent
->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2125 new BMessage(kOpenSelection
), 'O'));
2127 if (!model
.IsDesktop() && !model
.IsRoot() && !model
.IsTrash()
2128 && !fModel
->HasLocalizedName()) {
2129 parent
->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2130 new BMessage(kEditItem
), 'E'));
2131 parent
->AddSeparatorItem();
2133 if (fModel
->IsVolume()) {
2134 BMenuItem
* item
= new BMenuItem(B_TRANSLATE("Unmount"),
2135 new BMessage(kUnmountVolume
), 'U');
2136 parent
->AddItem(item
);
2137 // volume model, enable/disable the Unmount item
2139 BVolumeRoster().GetBootVolume(&boot
);
2141 volume
.SetTo(fModel
->NodeRef()->device
);
2143 item
->SetEnabled(false);
2147 if (!model
.IsRoot() && !model
.IsVolume() && !model
.IsTrash())
2148 parent
->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
2149 new BMessage(kIdentifyEntry
)));
2151 if (model
.IsTrash())
2152 parent
->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2153 new BMessage(kEmptyTrash
)));
2155 BMenuItem
* sizeItem
= NULL
;
2156 if (model
.IsDirectory() && !model
.IsVolume() && !model
.IsRoot()) {
2157 parent
->AddItem(sizeItem
2158 = new BMenuItem(B_TRANSLATE("Recalculate folder size"),
2159 new BMessage(kRecalculateSize
)));
2162 if (model
.IsSymLink()) {
2163 parent
->AddItem(sizeItem
2164 = new BMenuItem(B_TRANSLATE("Set new link target"),
2165 new BMessage(kSetLinkTarget
)));
2168 parent
->AddItem(new BSeparatorItem());
2169 parent
->AddItem(new BMenuItem(B_TRANSLATE("Permissions"),
2170 new BMessage(kPermissionsSelected
), 'P'));
2172 parent
->SetFont(be_plain_font
);
2173 parent
->SetTargetForItems(this);
2175 // Reset the nav menu to be_app
2177 navigationItem
->SetTarget(be_app
);
2179 sizeItem
->SetTarget(Window());
2186 AttributeView::SetPermissionsSwitchState(int32 state
)
2188 fPermissionsSwitch
->SetValue(state
);
2189 fPermissionsSwitch
->Invalidate();
2194 AttributeView::TextViewFilter(BMessage
* message
, BHandler
**,
2195 BMessageFilter
* filter
)
2198 AttributeView
* attribView
= static_cast<AttributeView
*>(
2199 static_cast<BWindow
*>(filter
->Looper())->FindView("attr_view"));
2201 // Adjust the size of the text rect
2202 BRect
nuRect(attribView
->TextView()->TextRect());
2203 nuRect
.right
= attribView
->TextView()->LineWidth() + 20;
2204 attribView
->TextView()->SetTextRect(nuRect
);
2206 // Make sure the cursor is in view
2207 attribView
->TextView()->ScrollToSelection();
2208 if (message
->FindInt8("byte", (int8
*)&key
) != B_OK
)
2209 return B_DISPATCH_MESSAGE
;
2211 if (key
== B_RETURN
|| key
== B_ESCAPE
) {
2212 attribView
->FinishEditingTitle(key
== B_RETURN
);
2213 return B_SKIP_MESSAGE
;
2216 return B_DISPATCH_MESSAGE
;
2221 AttributeView::LastSize() const
2228 AttributeView::SetLastSize(off_t lastSize
)
2230 fLastSize
= lastSize
;
2235 AttributeView::SetSizeString(const char* sizeString
)
2237 fSizeString
= sizeString
;
2239 BRect
bounds(Bounds());
2240 float lineHeight
= CurrentFontHeight() + 6;
2241 bounds
.Set(fDivider
, fIconRect
.bottom
, bounds
.right
,
2242 fIconRect
.bottom
+ lineHeight
);
2250 TrackingView::TrackingView(BRect frame
, const char* str
, BMessage
* message
)
2251 : BControl(frame
, "trackingView", str
, message
, B_FOLLOW_ALL
,
2256 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
2257 SetEventMask(B_POINTER_EVENTS
, 0);
2262 TrackingView::MouseDown(BPoint
)
2264 if (Message() != NULL
) {
2266 fMouseInView
= true;
2267 InvertRect(Bounds());
2273 TrackingView::MouseMoved(BPoint
, uint32 transit
, const BMessage
*)
2275 if ((transit
== B_ENTERED_VIEW
|| transit
== B_EXITED_VIEW
) && fMouseDown
)
2276 InvertRect(Bounds());
2278 fMouseInView
= (transit
== B_ENTERED_VIEW
|| transit
== B_INSIDE_VIEW
);
2279 DelayedInvalidate(16666, Bounds());
2280 if (!fMouseInView
&& !fMouseDown
)
2286 TrackingView::MouseUp(BPoint
)
2288 if (Message() != NULL
) {
2299 TrackingView::Draw(BRect
)
2301 if (Message() != NULL
)
2302 SetHighUIColor(fMouseInView
? B_LINK_HOVER_COLOR
2303 : B_LINK_TEXT_COLOR
);
2305 SetHighUIColor(B_PANEL_TEXT_COLOR
);
2306 SetLowColor(ViewColor());
2308 font_height fontHeight
;
2309 GetFontHeight(&fontHeight
);
2311 DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight
.descent
));