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(),
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()
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());
385 if ((fIndex
+ 2) % 2 == 1) {
386 windRect
.OffsetBy(320, 0);
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
);
408 fAttributeView
->SetLastSize(TargetModel()->StatBuf()->st_size
);
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());
426 BInfoWindow::MessageReceived(BMessage
* message
)
428 switch (message
->what
) {
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
);
447 BEntry
entry(fModel
->EntryRef());
448 if (!fModel
->HasLocalizedName()
449 && ConfirmChangeIfWellKnownDirectory(&entry
, kRename
)) {
450 fAttributeView
->BeginEditingTitle();
457 bool force
= (modifiers() & B_OPTION_KEY
) != 0;
459 if (entry
.SetTo(fModel
->EntryRef(), true) == B_OK
) {
461 if (entry
.GetPath(&path
) == B_OK
)
462 update_mime_info(path
.Path(), true, false, force
? 2 : 1);
467 case kRecalculateSize
:
470 // Wait until any current CalcSize thread has terminated before
471 // starting a new one
473 wait_for_thread(fCalcThreadID
, &result
);
475 // Start recalculating..
477 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS
));
478 fCalcThreadID
= spawn_thread(BInfoWindow::CalcSize
, "CalcSize",
479 B_NORMAL_PRIORITY
, this);
480 resume_thread(fCalcThreadID
);
485 OpenFilePanel(fModel
->EntryRef());
488 // An item was dropped into the window
490 // If we are not a SymLink, just ignore the request
491 if (!fModel
->IsSymLink())
493 // supposed to fall through
494 // An item was selected from the file panel
496 case kNewTargetSelected
:
498 // Extract the BEntry, and set its full path to the string value
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
516 BEntry
tmpEntry(TargetModel()->EntryRef());
517 if (tmpEntry
.GetParent(&parent
) != B_OK
)
521 BString
name(TargetModel()->Name());
523 // Extract path for new target
526 if (target
.GetPath(&targetPath
) != B_OK
)
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());
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());
566 // File panel window has closed
569 // It's no longer open
570 fFilePanelOpen
= false;
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()) {
579 BVolumeRoster().GetBootVolume(&boot
);
580 BVolume
volume(fModel
->NodeRef()->device
);
581 if (volume
!= boot
) {
582 TTracker
* tracker
= dynamic_cast<TTracker
*>(be_app
);
584 tracker
->SaveAllPoseLocations();
586 BMessage
unmountMessage(kUnmountVolume
);
587 unmountMessage
.AddInt32("device_id", volume
.Device());
588 be_app
->PostMessage(&unmountMessage
);
598 switch (message
->FindInt32("opcode")) {
599 case B_ENTRY_REMOVED
:
602 message
->FindInt32("device", &itemNode
.device
);
603 message
->FindInt64("node", &itemNode
.node
);
604 // our window itself may be deleted
605 if (*TargetModel()->NodeRef() == itemNode
)
613 fAttributeView
->ModelChanged(TargetModel(), message
);
614 // must be called before the
615 // FilePermissionView::ModelChanged()
616 // call, because it changes the model...
619 if (fPermissionsView
!= NULL
)
620 fPermissionsView
->ModelChanged(TargetModel());
623 case B_DEVICE_UNMOUNTED
:
625 // We were watching a volume that is no longer
626 // mounted, we might as well quit
628 // Only the device information is available
629 message
->FindInt32("device", &itemNode
.device
);
630 if (TargetModel()->NodeRef()->device
== itemNode
.device
)
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
);
660 fPermissionsView
->Hide();
661 ResizeBy(0, -permissionsBounds
.Height());
662 fAttributeView
->SetPermissionsSwitchState(kPaneSwitchClosed
);
668 _inherited::MessageReceived(message
);
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
) {
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) {
695 countFormat
.Format(result
, fileCount
);
701 BInfoWindow::CalcSize(void* castToWindow
)
703 BInfoWindow
* window
= static_cast<BInfoWindow
*>(castToWindow
);
704 BDirectory
dir(window
->TargetModel()->EntryRef());
706 FSGetTrashDir(&trashDir
, window
->TargetModel()->EntryRef()->device
);
707 if (dir
.InitCheck() != B_OK
) {
708 if (window
->StopCalc())
711 AutoLock
<BWindow
> lock(window
);
715 window
->SetSizeString(B_TRANSLATE("Error calculating folder size."));
719 BEntry dirEntry
, trashEntry
;
720 dir
.GetEntry(&dirEntry
);
721 trashDir
.GetEntry(&trashEntry
);
725 // check if user has asked for trash dir info
726 if (dirEntry
!= trashEntry
) {
727 // if not, perform normal info calculations
731 CopyLoopControl loopControl
;
732 FSRecursiveCalcSize(window
, &loopControl
, &dir
, &size
, &fileCount
,
735 // got the size value, update the size string
736 GetSizeString(sizeString
, size
, fileCount
);
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
;
746 while (volRoster
.GetNextVolume(&volume
) == B_OK
) {
747 if (!volume
.IsPersistent())
751 currentFileCount
= 0;
755 if (FSGetTrashDir(&trashDir
, volume
.Device()) == B_OK
) {
756 CopyLoopControl loopControl
;
757 FSRecursiveCalcSize(window
, &loopControl
, &trashDir
,
758 ¤tSize
, ¤tFileCount
, ¤tDirCount
);
759 totalSize
+= currentSize
;
760 totalFileCount
+= currentFileCount
;
761 totalDirCount
+= currentDirCount
;
764 GetSizeString(sizeString
, totalSize
, totalFileCount
);
767 if (window
->StopCalc()) {
768 // window closed, bail
772 AutoLock
<BWindow
> lock(window
);
774 window
->SetSizeString(sizeString
.String());
781 BInfoWindow::SetSizeString(const char* sizeString
)
784 = dynamic_cast<AttributeView
*>(FindView("attr_view"));
786 view
->SetSizeString(sizeString
);
791 BInfoWindow::OpenFilePanel(const entry_ref
* ref
)
793 // Open a file dialog box to allow the user to select a new target
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
,
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());
811 fFilePanelOpen
= true;
813 } else if (!fFilePanelOpen
) {
815 fFilePanelOpen
= true;
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
),
831 fPreferredAppMenu(NULL
),
837 fTrackingState(no_track
),
838 fIsDropTarget(false),
839 fTitleEditView(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
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
);
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
870 font_height fontMetrics
;
871 GetFont(¤tFont
);
872 currentFont
.GetHeight(&fontMetrics
);
874 fTitleRect
.left
= fIconRect
.right
+ 5;
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);
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:
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
, "", "",
912 currentFont
.SetSize(currentFont
.Size() + 2);
913 fDivider
= currentFont
.StringWidth(B_TRANSLATE("Opens with:"))
915 fPreferredAppMenu
->SetDivider(fDivider
);
916 fDivider
+= (preferredAppRect
.left
- 2);
917 fPreferredAppMenu
->SetFont(¤tFont
);
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
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
) {
942 // Only add separator item if there are more items
944 fPreferredAppMenu
->Menu()->AddSeparatorItem();
946 BMessage
* itemMessage
= new BMessage(kSetPreferredApp
);
947 itemMessage
->AddString("signature", signature
);
949 status_t err
= B_ERROR
;
952 if (signature
&& signature
[0])
953 err
= be_roster
->FindApp(signature
, &entry
);
956 result
= new BMenuItem(signature
, itemMessage
);
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());
982 AttributeView::~AttributeView()
984 if (fPathWindow
->Lock())
987 if (fLinkWindow
->Lock())
990 if (fDescWindow
->Lock())
993 if (fIconModel
!= fModel
)
999 AttributeView::InitStrings(const Model
* model
)
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())
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
;
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
1052 fDescStr
.ReplaceAll('\n', ' ');
1053 fDescStr
.ReplaceAll('\t', ' ');
1058 if (mime
.SetType(model
->MimeType()) == B_OK
1059 && mime
.GetShortDescription(kind
) == B_OK
)
1062 if (fKindStr
.Length() == 0)
1063 fKindStr
= model
->MimeType();
1068 AttributeView::AttachedToWindow()
1070 BFont
font(be_plain_font
);
1072 font
.SetSpacing(B_BITMAP_SPACING
);
1076 if (fPreferredAppMenu
)
1077 fPreferredAppMenu
->Menu()->SetTargetForItems(this);
1079 _inherited::AttachedToWindow();
1084 AttributeView::Pulse()
1087 _inherited::Pulse();
1092 AttributeView::ModelChanged(Model
* model
, BMessage
* message
)
1094 BRect
drawBounds(Bounds());
1095 drawBounds
.left
= fDivider
;
1097 switch (message
->FindInt32("opcode")) {
1102 dirNode
.device
= itemNode
.device
= message
->FindInt32("device");
1103 message
->FindInt64("to directory", &dirNode
.node
);
1104 message
->FindInt64("node", &itemNode
.node
);
1107 if (message
->FindString("name", &name
) != B_OK
)
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
);
1122 title
<< name
<< B_TRANSLATE(" info");
1123 Window()->SetTitle(title
.String());
1124 WidgetAttributeText::AttrAsString(model
, &fPathStr
, kAttrPath
,
1125 B_STRING_TYPE
, 0, this);
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
;
1144 BInfoWindow::GetSizeString(fSizeString
, fLastSize
, 0);
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());
1159 } else if (strcmp(attrName
, kAttrMIMEType
) == 0) {
1160 if (model
->OpenNode() == B_OK
) {
1161 model
->AttrChanged(attrName
);
1175 // Update the icon stuff
1176 if (fIconModel
!= fModel
) {
1182 if (fModel
->IsSymLink()) {
1183 // if we are looking at a symlink, deference the model and look
1185 Model
* resolvedModel
= new Model(model
->EntryRef(), true, true);
1186 if (resolvedModel
->InitCheck() == B_OK
) {
1187 if (fIconModel
!= fModel
)
1189 fIconModel
= resolvedModel
;
1192 delete resolvedModel
;
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(),
1210 AttributeView::ReLinkTargetModel(Model
* model
)
1213 if (fModel
->IsSymLink()) {
1214 Model
* resolvedModel
= new Model(model
->EntryRef(), true, true);
1215 if (resolvedModel
->InitCheck() == B_OK
) {
1216 if (fIconModel
!= fModel
)
1218 fIconModel
= resolvedModel
;
1220 fIconModel
= fModel
;
1221 delete resolvedModel
;
1225 Invalidate(Bounds());
1230 AttributeView::MouseDown(BPoint where
)
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
;
1258 fTrackingState
= no_track
;
1259 } else if (fIconRect
.Contains(where
)) {
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);
1267 BuildContextMenu(contextMenu
);
1268 contextMenu
->SetAsyncAutoDestruct(true);
1269 contextMenu
->Go(ConvertToScreen(where
), true, true,
1270 ConvertToScreen(fIconRect
));
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
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
) {
1289 Window()->CurrentMessage()->FindInt32("clicks",
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
;
1297 = IconCache::sIconCache
->IconHitTest(offsetPoint
,
1298 fIconModel
, kNormalIcon
, B_LARGE_ICON
);
1305 fClickPoint
= where
;
1307 SetMouseEventMask(B_POINTER_EVENTS
, B_NO_POINTER_HISTORY
);
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
) {
1333 if (fLinkRect
.Contains(where
) != fMouseDown
) {
1334 fMouseDown
= !fMouseDown
;
1335 InvertRect(fLinkRect
);
1340 if (fPathRect
.Contains(where
) != fMouseDown
) {
1341 fMouseDown
= !fMouseDown
;
1342 InvertRect(fPathRect
);
1347 if (fSizeRect
.Contains(where
) != fMouseDown
) {
1348 fMouseDown
= !fMouseDown
;
1349 InvertRect(fSizeRect
);
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
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);
1368 BView
* view
= new BView(dragBitmap
->Bounds(), "",
1370 dragBitmap
->AddChild(view
);
1371 view
->SetOrigin(0, 0);
1372 BRect
clipRect(view
->Bounds());
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
);
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
,
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());
1409 dragBitmap
->Unlock();
1411 BMessage
dragMessage(B_REFS_RECEIVED
);
1412 dragMessage
.AddPoint("click_pt", fClickPoint
);
1415 GetMouse(&tmpLoc
, &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);
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
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
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());
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(),
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
);
1484 || BMessenger(fLinkWindow
).IsValid() == false) {
1485 fLinkWindow
= OpenToolTipWindow(screen
, rect
,
1486 "fLinkWindow", fLinkToStr
.String(),
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
);
1499 || BMessenger(fDescWindow
).IsValid() == false) {
1500 fDescWindow
= OpenToolTipWindow(screen
, rect
,
1501 "fDescWindow", fDescStr
.String(),
1502 BMessenger(this), NULL
);
1510 DelayedInvalidate(16666, fPathRect
);
1511 DelayedInvalidate(16666, fLinkRect
);
1516 AttributeView::OpenLinkSource()
1518 OpenParentAndSelectOriginal(fModel
->EntryRef());
1523 AttributeView::OpenLinkTarget()
1525 Model
resolvedModel(fModel
->EntryRef(), true, true);
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());
1540 window
->OpenFilePanel(fModel
->EntryRef());
1545 printf("Opening link target: %s\n", path
.Path());
1546 OpenParentAndSelectOriginal(&ref
);
1552 AttributeView::MouseUp(BPoint where
)
1554 // Are we in the link rect?
1555 if (fTrackingState
== link_track
&& fLinkRect
.Contains(where
)) {
1556 InvertRect(fLinkRect
);
1558 } else if (fTrackingState
== path_track
&& fPathRect
.Contains(where
)) {
1559 InvertRect(fPathRect
);
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
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
)) {
1580 Window()->PostMessage(kRecalculateSize
);
1583 // End mouse tracking
1586 fTrackingState
= no_track
;
1592 AttributeView::CheckAndSetSize()
1594 if (fModel
->IsVolume() || fModel
->IsRoot()) {
1595 off_t freeBytes
= 0;
1598 if (fModel
->IsVolume()) {
1599 BVolume
volume(fModel
->NodeRef()->device
);
1600 freeBytes
= volume
.FreeBytes();
1601 capacity
= volume
.Capacity();
1603 // iterate over all volumes
1604 BVolumeRoster volumeRoster
;
1606 while (volumeRoster
.GetNextVolume(&volume
) == B_OK
) {
1607 freeBytes
+= volume
.FreeBytes();
1608 capacity
+= volume
.Capacity();
1612 if (fFreeBytes
== freeBytes
)
1615 fFreeBytes
= freeBytes
;
1617 fSizeString
.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
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)
1631 BModelOpener
opener(fModel
);
1633 if (fModel
->InitCheck() != B_OK
1634 || fModel
->Node()->GetStat(&statBuf
) != B_OK
) {
1638 if (fLastSize
== statBuf
.st_size
)
1641 fLastSize
= statBuf
.st_size
;
1643 BInfoWindow::GetSizeString(fSizeString
, fLastSize
, 0);
1647 BRect
bounds(Bounds());
1648 float lineHeight
= CurrentFontHeight() + 2;
1649 bounds
.Set(fDivider
, fIconRect
.bottom
, bounds
.right
,
1650 fIconRect
.bottom
+ lineHeight
);
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
);
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
);
1685 case kOpenLinkSource
:
1689 case kOpenLinkTarget
:
1694 _inherited::MessageReceived(message
);
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
));
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
);
1725 font_height fontMetrics
;
1727 float lineHeight
= 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(¤tFont
);
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());
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
;
1763 SetHighColor(labelColor
);
1764 if (fModel
->IsVolume() || fModel
->IsRoot()) {
1765 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Capacity:"))),
1767 DrawString(B_TRANSLATE("Capacity:"));
1769 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Size:"))),
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())
1789 DrawString(fSizeString
.String());
1790 fSizeRect
.right
= fSizeRect
.left
+ StringWidth(fSizeString
.String()) + 3;
1792 lineBase
+= lineHeight
;
1795 SetHighColor(labelColor
);
1796 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Created:"))),
1798 DrawString(B_TRANSLATE("Created:"));
1799 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1800 SetHighColor(attributeColor
);
1801 DrawString(fCreatedStr
.String());
1802 lineBase
+= lineHeight
;
1805 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Modified:"))),
1807 SetHighColor(labelColor
);
1808 DrawString(B_TRANSLATE("Modified:"));
1809 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1810 SetHighColor(attributeColor
);
1811 DrawString(fModifiedStr
.String());
1812 lineBase
+= lineHeight
;
1815 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Kind:"))),
1817 SetHighColor(labelColor
);
1818 DrawString(B_TRANSLATE("Kind:"));
1819 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1820 SetHighColor(attributeColor
);
1821 DrawString(fKindStr
.String());
1822 lineBase
+= lineHeight
;
1825 GetFont(&normalFont
);
1828 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Location:"))),
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());
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
;
1855 if (fModel
->IsSymLink()) {
1856 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Link to:"))),
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());
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())
1880 // No description field
1881 fDescRect
= BRect(-1, -1, -1, -1);
1882 } else if (fModel
->IsExecutable()) {
1884 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Version:"))),
1886 SetHighColor(labelColor
);
1887 DrawString(B_TRANSLATE("Version:"));
1888 MovePenTo(BPoint(fDivider
+ kDrawMargin
, lineBase
));
1889 SetHighColor(attributeColor
);
1891 if (fModel
->GetVersionString(nameString
, B_APP_VERSION_KIND
) == B_OK
)
1892 DrawString(nameString
.String());
1895 lineBase
+= lineHeight
;
1898 MovePenTo(BPoint(fDivider
- (StringWidth(B_TRANSLATE("Description:"))),
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());
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;
1921 fLinkRect
= BRect(-1, -1, -1, -1);
1927 AttributeView::BeginEditingTitle()
1929 if (fTitleEditView
!= NULL
)
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
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();
1970 AttributeView::FinishEditingTitle(bool commit
)
1972 if (fTitleEditView
== NULL
)
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());
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."),
1990 0, 0, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
1991 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1995 if (fModel
->IsVolume()) {
1996 BVolume
volume(fModel
->NodeRef()->device
);
1997 if (volume
.InitCheck() == B_OK
)
1998 volume
.SetName(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."),
2014 0, 0, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
2015 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2021 BView
* scrollView
= fTitleEditView
->Parent();
2022 RemoveChild(scrollView
);
2024 fTitleEditView
= NULL
;
2027 BeginEditingTitle();
2032 AttributeView::MakeFocus(bool focus
)
2034 if (!focus
&& fTitleEditView
!= NULL
)
2035 FinishEditingTitle(true);
2040 AttributeView::WindowActivated(bool active
)
2045 if (fTitleEditView
!= NULL
)
2046 FinishEditingTitle(true);
2048 if (fPathWindow
->Lock()) {
2049 fPathWindow
->Quit();
2053 if (fLinkWindow
->Lock()) {
2054 fLinkWindow
->Quit();
2058 if (fDescWindow
->Lock()) {
2059 fDescWindow
->Quit();
2066 AttributeView::CurrentFontHeight()
2070 font_height fontHeight
;
2071 font
.GetHeight(&fontHeight
);
2073 return fontHeight
.ascent
+ fontHeight
.descent
+ fontHeight
.leading
+ 2;
2078 AttributeView::BuildContextMenu(BMenu
* parent
)
2083 // Add navigation menu if this is not a symlink
2084 // Symlink's to directories are OK however!
2085 BEntry
entry(fModel
->EntryRef());
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();
2097 } else if (model
.IsDirectory() || model
.IsVolume())
2100 ModelMenuItem
* navigationItem
= NULL
;
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
2138 BVolumeRoster().GetBootVolume(&boot
);
2140 volume
.SetTo(fModel
->NodeRef()->device
);
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
2176 navigationItem
->SetTarget(be_app
);
2178 sizeItem
->SetTarget(Window());
2185 AttributeView::SetPermissionsSwitchState(int32 state
)
2187 fPermissionsSwitch
->SetValue(state
);
2188 fPermissionsSwitch
->Invalidate();
2193 AttributeView::TextViewFilter(BMessage
* message
, BHandler
**,
2194 BMessageFilter
* filter
)
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
;
2220 AttributeView::LastSize() const
2227 AttributeView::SetLastSize(off_t lastSize
)
2229 fLastSize
= lastSize
;
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
);
2249 TrackingView::TrackingView(BRect frame
, const char* str
, BMessage
* message
)
2250 : BControl(frame
, "trackingView", str
, message
, B_FOLLOW_ALL
,
2255 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
2256 SetEventMask(B_POINTER_EVENTS
, 0);
2261 TrackingView::MouseDown(BPoint
)
2263 if (Message() != NULL
) {
2265 fMouseInView
= true;
2266 InvertRect(Bounds());
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
)
2285 TrackingView::MouseUp(BPoint
)
2287 if (Message() != NULL
) {
2298 TrackingView::Draw(BRect
)
2300 if (Message() != NULL
)
2301 SetHighUIColor(fMouseInView
? B_LINK_HOVER_COLOR
2302 : B_LINK_TEXT_COLOR
);
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
));