RemoteDrawingEngine: Reduce RP_READ_BITMAP result timeout.
[haiku.git] / src / kits / tracker / InfoWindow.cpp
blobce5e92f70b9b5a847bc0c122c5748bb0c7a8ac8e
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
36 #include "InfoWindow.h"
38 #include <string.h>
39 #include <stdio.h>
40 #include <stdlib.h>
42 #include <Alert.h>
43 #include <Catalog.h>
44 #include <Debug.h>
45 #include <Directory.h>
46 #include <File.h>
47 #include <Font.h>
48 #include <Locale.h>
49 #include <MenuField.h>
50 #include <MessageFormat.h>
51 #include <Mime.h>
52 #include <NodeInfo.h>
53 #include <NodeMonitor.h>
54 #include <Path.h>
55 #include <PopUpMenu.h>
56 #include <Region.h>
57 #include <Roster.h>
58 #include <Screen.h>
59 #include <ScrollView.h>
60 #include <SymLink.h>
61 #include <TextView.h>
62 #include <Volume.h>
63 #include <VolumeRoster.h>
65 #include "Attributes.h"
66 #include "AutoLock.h"
67 #include "Commands.h"
68 #include "DialogPane.h"
69 #include "FSUtils.h"
70 #include "IconCache.h"
71 #include "IconMenuItem.h"
72 #include "Model.h"
73 #include "NavMenu.h"
74 #include "PoseView.h"
75 #include "StringForSize.h"
76 #include "Tracker.h"
77 #include "WidgetAttributeText.h"
80 #undef B_TRANSLATION_CONTEXT
81 #define B_TRANSLATION_CONTEXT "InfoWindow"
84 namespace BPrivate {
86 // States for tracking the mouse
87 enum track_state {
88 no_track = 0,
89 link_track,
90 path_track,
91 icon_track,
92 size_track,
93 open_only_track
94 // This is for items that can be opened, but can't be
95 // drag and dropped or renamed (Trash, Desktop Folder...)
99 class TrackingView : public BControl {
100 public:
101 TrackingView(BRect, const char* str, BMessage* message);
103 virtual void MouseDown(BPoint);
104 virtual void MouseMoved(BPoint where, uint32 transit, const BMessage*);
105 virtual void MouseUp(BPoint);
106 virtual void Draw(BRect);
108 private:
109 bool fMouseDown;
110 bool fMouseInView;
114 class AttributeView : public BView {
115 public:
116 AttributeView(BRect, Model*);
117 ~AttributeView();
119 void ModelChanged(Model*, BMessage*);
120 void ReLinkTargetModel(Model*);
121 void BeginEditingTitle();
122 void FinishEditingTitle(bool);
123 float CurrentFontHeight();
125 BTextView* TextView() const { return fTitleEditView; }
127 static filter_result TextViewFilter(BMessage*, BHandler**,
128 BMessageFilter*);
130 off_t LastSize() const;
131 void SetLastSize(off_t);
133 void SetSizeString(const char*);
135 status_t BuildContextMenu(BMenu* parent);
137 void SetPermissionsSwitchState(int32 state);
139 protected:
140 virtual void MouseDown(BPoint where);
141 virtual void MouseMoved(BPoint where, uint32, const BMessage* dragMessage);
142 virtual void MouseUp(BPoint where);
143 virtual void MessageReceived(BMessage* message);
144 virtual void AttachedToWindow();
145 virtual void Draw(BRect);
146 virtual void Pulse();
147 virtual void MakeFocus(bool focus);
148 virtual void WindowActivated(bool active);
150 private:
151 void InitStrings(const Model*);
152 void CheckAndSetSize();
153 void OpenLinkSource();
154 void OpenLinkTarget();
156 BString fPathStr;
157 BString fLinkToStr;
158 BString fSizeString;
159 BString fModifiedStr;
160 BString fCreatedStr;
161 BString fKindStr;
162 BString fDescStr;
164 off_t fFreeBytes;
165 off_t fLastSize;
167 BRect fPathRect;
168 BRect fLinkRect;
169 BRect fDescRect;
170 BRect fTitleRect;
171 BRect fIconRect;
172 BRect fSizeRect;
173 BPoint fClickPoint;
174 float fDivider;
176 BMenuField* fPreferredAppMenu;
177 Model* fModel;
178 Model* fIconModel;
179 BBitmap* fIcon;
180 bool fMouseDown;
181 bool fDragging;
182 bool fDoubleClick;
183 track_state fTrackingState;
184 bool fIsDropTarget;
185 BTextView* fTitleEditView;
186 PaneSwitch* fPermissionsSwitch;
187 BWindow* fPathWindow;
188 BWindow* fLinkWindow;
189 BWindow* fDescWindow;
190 color_which fCurrentLinkColorWhich;
191 color_which fCurrentPathColorWhich;
193 typedef BView _inherited;
196 } // namespace BPrivate
199 const float kDrawMargin = 3.0f;
200 const float kBorderMargin = 15.0f;
201 const float kBorderWidth = 32.0f;
203 // Offsets taken from TAlertView::Draw in BAlert.cpp
204 const float kIconHorizOffset = 18.0f;
205 const float kIconVertOffset = 6.0f;
207 // Amount you have to move the mouse before a drag starts
208 const float kDragSlop = 3.0f;
210 const uint32 kSetPreferredApp = 'setp';
211 const uint32 kSelectNewSymTarget = 'snew';
212 const uint32 kNewTargetSelected = 'selc';
213 const uint32 kRecalculateSize = 'resz';
214 const uint32 kSetLinkTarget = 'link';
215 const uint32 kPermissionsSelected = 'sepe';
216 const uint32 kOpenLinkSource = 'opls';
217 const uint32 kOpenLinkTarget = 'oplt';
219 const uint32 kPaneSwitchClosed = 0;
220 const uint32 kPaneSwitchOpen = 2;
223 static void
224 OpenParentAndSelectOriginal(const entry_ref* ref)
226 BEntry entry(ref);
227 node_ref node;
228 entry.GetNodeRef(&node);
230 BEntry parent;
231 entry.GetParent(&parent);
232 entry_ref parentRef;
233 parent.GetRef(&parentRef);
235 BMessage message(B_REFS_RECEIVED);
236 message.AddRef("refs", &parentRef);
237 message.AddData("nodeRefToSelect", B_RAW_TYPE, &node, sizeof(node_ref));
239 be_app->PostMessage(&message);
243 static BWindow*
244 OpenToolTipWindow(BScreen& screen, BRect rect, const char* name,
245 const char* string, BMessenger target, BMessage* message)
247 font_height fontHeight;
248 be_plain_font->GetHeight(&fontHeight);
249 float height = ceilf(fontHeight.ascent + fontHeight.descent);
250 rect.top = floorf(rect.top + (rect.Height() - height) / 2.0f);
251 rect.bottom = rect.top + height;
253 rect.right = rect.left + ceilf(be_plain_font->StringWidth(string)) + 4;
254 if (rect.left < 0)
255 rect.OffsetBy(-rect.left, 0);
256 else if (rect.right > screen.Frame().right)
257 rect.OffsetBy(screen.Frame().right - rect.right, 0);
259 BWindow* window = new BWindow(rect, name, B_BORDERED_WINDOW_LOOK,
260 B_FLOATING_ALL_WINDOW_FEEL,
261 B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
262 | B_NOT_RESIZABLE | B_AVOID_FOCUS | B_NO_WORKSPACE_ACTIVATION
263 | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS);
265 TrackingView* trackingView = new TrackingView(window->Bounds(),
266 string, message);
267 trackingView->SetTarget(target);
268 window->AddChild(trackingView);
270 window->Sync();
271 window->Show();
273 return window;
277 // #pragma mark - BInfoWindow
280 BInfoWindow::BInfoWindow(Model* model, int32 group_index,
281 LockingList<BWindow>* list)
283 BWindow(BInfoWindow::InfoWindowRect(false),
284 "InfoWindow", B_TITLED_WINDOW,
285 B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_CURRENT_WORKSPACE),
286 fModel(model),
287 fStopCalc(false),
288 fIndex(group_index),
289 fCalcThreadID(-1),
290 fWindowList(list),
291 fPermissionsView(NULL),
292 fFilePanel(NULL),
293 fFilePanelOpen(false)
295 SetPulseRate(1000000);
296 // we use pulse to check freebytes on volume
298 TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this);
300 // window list is Locked by Tracker around this constructor
301 if (list != NULL)
302 list->AddItem(this);
304 AddShortcut('E', 0, new BMessage(kEditItem));
305 AddShortcut('O', 0, new BMessage(kOpenSelection));
306 AddShortcut('U', 0, new BMessage(kUnmountVolume));
307 AddShortcut('P', 0, new BMessage(kPermissionsSelected));
309 Run();
313 BInfoWindow::~BInfoWindow()
315 // Check to make sure the file panel is destroyed
316 delete fFilePanel;
317 delete fModel;
321 BRect
322 BInfoWindow::InfoWindowRect(bool)
324 // starting size of window
325 return BRect(70, 50, 385, 240);
329 void
330 BInfoWindow::Quit()
332 stop_watching(this);
334 if (fWindowList) {
335 AutoLock<LockingList<BWindow> > lock(fWindowList);
336 fWindowList->RemoveItem(this);
339 fStopCalc = true;
341 // wait until CalcSize thread has terminated before closing window
342 status_t result;
343 wait_for_thread(fCalcThreadID, &result);
345 _inherited::Quit();
349 bool
350 BInfoWindow::IsShowing(const node_ref* node) const
352 return *TargetModel()->NodeRef() == *node;
356 void
357 BInfoWindow::Show()
359 BModelOpener modelOpener(TargetModel());
360 if (TargetModel()->InitCheck() != B_OK) {
361 Close();
362 return;
365 AutoLock<BWindow> lock(this);
367 const BFont* font = be_plain_font;
368 float width = font->StringWidth("This is a really long string which we"
369 "will use to find the window width");
371 // window height depends on file type
372 int lines = 9;
373 if (fModel->IsExecutable())
374 lines++;
375 float height = font->Size() * (lines * 2 + 1);
377 ResizeTo(width, height);
379 BRect attrRect(Bounds());
380 fAttributeView = new AttributeView(attrRect, TargetModel());
381 AddChild(fAttributeView);
383 // position window appropriately based on index
384 BRect windRect(InfoWindowRect(TargetModel()->IsSymLink()
385 || TargetModel()->IsFile()));
386 if ((fIndex + 2) % 2 == 1) {
387 windRect.OffsetBy(320, 0);
388 fIndex--;
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);
408 } else {
409 fAttributeView->SetLastSize(TargetModel()->StatBuf()->st_size);
411 BString sizeStr;
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());
421 lock.Unlock();
422 _inherited::Show();
426 void
427 BInfoWindow::MessageReceived(BMessage* message)
429 switch (message->what) {
430 case kRestoreState:
431 Show();
432 break;
434 case kOpenSelection:
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);
443 break;
446 case kEditItem:
448 BEntry entry(fModel->EntryRef());
449 if (!fModel->HasLocalizedName()
450 && ConfirmChangeIfWellKnownDirectory(&entry, kRename)) {
451 fAttributeView->BeginEditingTitle();
453 break;
456 case kIdentifyEntry:
458 bool force = (modifiers() & B_OPTION_KEY) != 0;
459 BEntry entry;
460 if (entry.SetTo(fModel->EntryRef(), true) == B_OK) {
461 BPath path;
462 if (entry.GetPath(&path) == B_OK)
463 update_mime_info(path.Path(), true, false, force ? 2 : 1);
465 break;
468 case kRecalculateSize:
470 fStopCalc = true;
471 // Wait until any current CalcSize thread has terminated before
472 // starting a new one
473 status_t result;
474 wait_for_thread(fCalcThreadID, &result);
476 // Start recalculating..
477 fStopCalc = false;
478 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
479 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
480 B_NORMAL_PRIORITY, this);
481 resume_thread(fCalcThreadID);
482 break;
485 case kSetLinkTarget:
486 OpenFilePanel(fModel->EntryRef());
487 break;
489 // An item was dropped into the window
490 case B_SIMPLE_DATA:
491 // If we are not a SymLink, just ignore the request
492 if (!fModel->IsSymLink())
493 break;
494 // supposed to fall through
495 // An item was selected from the file panel
496 // fall-through
497 case kNewTargetSelected:
499 // Extract the BEntry, and set its full path to the string value
500 BEntry targetEntry;
501 entry_ref ref;
502 BPath path;
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
512 // is removed.)
513 stop_watching(this);
515 // Get the parent
516 BDirectory parent;
517 BEntry tmpEntry(TargetModel()->EntryRef());
518 if (tmpEntry.GetParent(&parent) != B_OK)
519 break;
521 // Preserve the name
522 BString name(TargetModel()->Name());
524 // Extract path for new target
525 BEntry target(&ref);
526 BPath targetPath;
527 if (target.GetPath(&targetPath) != B_OK)
528 break;
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());
540 oldEntry.Remove();
542 // Create new node
543 BSymLink link;
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());
563 break;
566 case B_CANCEL:
567 // File panel window has closed
568 delete fFilePanel;
569 fFilePanel = NULL;
570 // It's no longer open
571 fFilePanelOpen = false;
572 break;
574 case kUnmountVolume:
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()) {
579 BVolume boot;
580 BVolumeRoster().GetBootVolume(&boot);
581 BVolume volume(fModel->NodeRef()->device);
582 if (volume != boot) {
583 TTracker* tracker = dynamic_cast<TTracker*>(be_app);
584 if (tracker != NULL)
585 tracker->SaveAllPoseLocations();
587 BMessage unmountMessage(kUnmountVolume);
588 unmountMessage.AddInt32("device_id", volume.Device());
589 be_app->PostMessage(&unmountMessage);
592 break;
594 case kEmptyTrash:
595 FSEmptyTrash();
596 break;
598 case B_NODE_MONITOR:
599 switch (message->FindInt32("opcode")) {
600 case B_ENTRY_REMOVED:
602 node_ref itemNode;
603 message->FindInt32("device", &itemNode.device);
604 message->FindInt64("node", &itemNode.node);
605 // our window itself may be deleted
606 if (*TargetModel()->NodeRef() == itemNode)
607 Close();
608 break;
611 case B_ENTRY_MOVED:
612 case B_STAT_CHANGED:
613 case B_ATTR_CHANGED:
614 fAttributeView->ModelChanged(TargetModel(), message);
615 // must be called before the
616 // FilePermissionView::ModelChanged()
617 // call, because it changes the model...
618 // (bad style!)
620 if (fPermissionsView != NULL)
621 fPermissionsView->ModelChanged(TargetModel());
622 break;
624 case B_DEVICE_UNMOUNTED:
626 // We were watching a volume that is no longer
627 // mounted, we might as well quit
628 node_ref itemNode;
629 // Only the device information is available
630 message->FindInt32("device", &itemNode.device);
631 if (TargetModel()->NodeRef()->device == itemNode.device)
632 Close();
633 break;
636 default:
637 break;
639 break;
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);
660 } else {
661 fPermissionsView->Hide();
662 ResizeBy(0, -permissionsBounds.Height());
663 fAttributeView->SetPermissionsSwitchState(kPaneSwitchClosed);
665 break;
668 default:
669 _inherited::MessageReceived(message);
670 break;
675 void
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) {
687 result << " ";
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) {
695 result << " ";
696 countFormat.Format(result, fileCount);
701 int32
702 BInfoWindow::CalcSize(void* castToWindow)
704 BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow);
705 BDirectory dir(window->TargetModel()->EntryRef());
706 BDirectory trashDir;
707 FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device);
708 if (dir.InitCheck() != B_OK) {
709 if (window->StopCalc())
710 return B_ERROR;
712 AutoLock<BWindow> lock(window);
713 if (!lock)
714 return B_ERROR;
716 window->SetSizeString(B_TRANSLATE("Error calculating folder size."));
717 return B_ERROR;
720 BEntry dirEntry, trashEntry;
721 dir.GetEntry(&dirEntry);
722 trashDir.GetEntry(&trashEntry);
724 BString sizeString;
726 // check if user has asked for trash dir info
727 if (dirEntry != trashEntry) {
728 // if not, perform normal info calculations
729 off_t size = 0;
730 int32 fileCount = 0;
731 int32 dirCount = 0;
732 CopyLoopControl loopControl;
733 FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount,
734 &dirCount);
736 // got the size value, update the size string
737 GetSizeString(sizeString, size, fileCount);
738 } else {
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;
745 volRoster.Rewind();
746 BVolume volume;
747 while (volRoster.GetNextVolume(&volume) == B_OK) {
748 if (!volume.IsPersistent())
749 continue;
751 currentSize = 0;
752 currentFileCount = 0;
753 currentDirCount = 0;
755 BDirectory trashDir;
756 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
757 CopyLoopControl loopControl;
758 FSRecursiveCalcSize(window, &loopControl, &trashDir,
759 &currentSize, &currentFileCount, &currentDirCount);
760 totalSize += currentSize;
761 totalFileCount += currentFileCount;
762 totalDirCount += currentDirCount;
765 GetSizeString(sizeString, totalSize, totalFileCount);
768 if (window->StopCalc()) {
769 // window closed, bail
770 return B_OK;
773 AutoLock<BWindow> lock(window);
774 if (lock.IsLocked())
775 window->SetSizeString(sizeString.String());
777 return B_OK;
781 void
782 BInfoWindow::SetSizeString(const char* sizeString)
784 AttributeView* view
785 = dynamic_cast<AttributeView*>(FindView("attr_view"));
786 if (view != NULL)
787 view->SetSizeString(sizeString);
791 void
792 BInfoWindow::OpenFilePanel(const entry_ref* ref)
794 // Open a file dialog box to allow the user to select a new target
795 // for the sym link
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,
801 false, &message);
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());
811 fFilePanel->Show();
812 fFilePanelOpen = true;
814 } else if (!fFilePanelOpen) {
815 fFilePanel->Show();
816 fFilePanelOpen = true;
817 } else {
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),
831 fDivider(0),
832 fPreferredAppMenu(NULL),
833 fModel(model),
834 fIconModel(model),
835 fMouseDown(false),
836 fDragging(false),
837 fDoubleClick(false),
838 fTrackingState(no_track),
839 fIsDropTarget(false),
840 fTitleEditView(NULL),
841 fPathWindow(NULL),
842 fLinkWindow(NULL),
843 fDescWindow(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
857 else
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);
866 // The title rect
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
869 // does not change.
870 BFont currentFont;
871 font_height fontMetrics;
872 GetFont(&currentFont);
873 currentFont.GetHeight(&fontMetrics);
875 fTitleRect.left = fIconRect.right + 5;
876 fTitleRect.top = 0;
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);
888 fFreeBytes = -1;
889 fSizeString = "";
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:
899 // string...
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, "", "",
912 new BPopUpMenu(""));
913 currentFont.SetSize(currentFont.Size() + 2);
914 fDivider = currentFont.StringWidth(B_TRANSLATE("Opens with:"))
915 + 5;
916 fPreferredAppMenu->SetDivider(fDivider);
917 fDivider += (preferredAppRect.left - 2);
918 fPreferredAppMenu->SetFont(&currentFont);
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
929 BMenuItem* result;
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) {
940 break;
943 // Only add separator item if there are more items
944 if (index == 0)
945 fPreferredAppMenu->Menu()->AddSeparatorItem();
947 BMessage* itemMessage = new BMessage(kSetPreferredApp);
948 itemMessage->AddString("signature", signature);
950 status_t err = B_ERROR;
951 entry_ref entry;
953 if (signature && signature[0])
954 err = be_roster->FindApp(signature, &entry);
956 if (err != B_OK)
957 result = new BMenuItem(signature, itemMessage);
958 else
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());
979 InitStrings(model);
983 AttributeView::~AttributeView()
985 if (fPathWindow->Lock())
986 fPathWindow->Quit();
988 if (fLinkWindow->Lock())
989 fLinkWindow->Quit();
991 if (fDescWindow->Lock())
992 fDescWindow->Quit();
994 if (fIconModel != fModel)
995 delete fIconModel;
999 void
1000 AttributeView::InitStrings(const Model* model)
1002 BMimeType mime;
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())
1035 linked = true;
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;
1044 if (!linked) {
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
1052 // with spaces
1053 fDescStr.ReplaceAll('\n', ' ');
1054 fDescStr.ReplaceAll('\t', ' ');
1055 } else
1056 fDescStr = "-";
1059 if (mime.SetType(model->MimeType()) == B_OK
1060 && mime.GetShortDescription(kind) == B_OK)
1061 fKindStr = kind;
1063 if (fKindStr.Length() == 0)
1064 fKindStr = model->MimeType();
1068 void
1069 AttributeView::AttachedToWindow()
1071 BFont font(be_plain_font);
1073 font.SetSpacing(B_BITMAP_SPACING);
1074 SetFont(&font);
1076 CheckAndSetSize();
1077 if (fPreferredAppMenu)
1078 fPreferredAppMenu->Menu()->SetTargetForItems(this);
1080 _inherited::AttachedToWindow();
1084 void
1085 AttributeView::Pulse()
1087 CheckAndSetSize();
1088 _inherited::Pulse();
1092 void
1093 AttributeView::ModelChanged(Model* model, BMessage* message)
1095 BRect drawBounds(Bounds());
1096 drawBounds.left = fDivider;
1098 switch (message->FindInt32("opcode")) {
1099 case B_ENTRY_MOVED:
1101 node_ref dirNode;
1102 node_ref itemNode;
1103 dirNode.device = itemNode.device = message->FindInt32("device");
1104 message->FindInt64("to directory", &dirNode.node);
1105 message->FindInt64("node", &itemNode.node);
1107 const char* name;
1108 if (message->FindString("name", &name) != B_OK)
1109 return;
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);
1122 BString title;
1123 title << name << B_TRANSLATE(" info");
1124 Window()->SetTitle(title.String());
1125 WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
1126 B_STRING_TYPE, 0, this);
1127 Invalidate();
1129 break;
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;
1144 fSizeString = "";
1145 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
1147 model->CloseNode();
1149 break;
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());
1159 Invalidate();
1160 } else if (strcmp(attrName, kAttrMIMEType) == 0) {
1161 if (model->OpenNode() == B_OK) {
1162 model->AttrChanged(attrName);
1163 InitStrings(model);
1164 model->CloseNode();
1166 Invalidate();
1169 break;
1172 default:
1173 break;
1176 // Update the icon stuff
1177 if (fIconModel != fModel) {
1178 delete fIconModel;
1179 fIconModel = NULL;
1182 fModel = model;
1183 if (fModel->IsSymLink()) {
1184 // if we are looking at a symlink, deference the model and look
1185 // at the target
1186 Model* resolvedModel = new Model(model->EntryRef(), true, true);
1187 if (resolvedModel->InitCheck() == B_OK) {
1188 if (fIconModel != fModel)
1189 delete fIconModel;
1190 fIconModel = resolvedModel;
1191 } else {
1192 fIconModel = model;
1193 delete resolvedModel;
1195 InitStrings(model);
1196 Invalidate();
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(),
1208 // would be nice)
1210 void
1211 AttributeView::ReLinkTargetModel(Model* model)
1213 fModel = model;
1214 if (fModel->IsSymLink()) {
1215 Model* resolvedModel = new Model(model->EntryRef(), true, true);
1216 if (resolvedModel->InitCheck() == B_OK) {
1217 if (fIconModel != fModel)
1218 delete fIconModel;
1219 fIconModel = resolvedModel;
1220 } else {
1221 fIconModel = fModel;
1222 delete resolvedModel;
1225 InitStrings(model);
1226 Invalidate(Bounds());
1230 void
1231 AttributeView::MouseDown(BPoint where)
1233 BEntry entry;
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;
1258 } else
1259 fTrackingState = no_track;
1260 } else if (fIconRect.Contains(where)) {
1261 uint32 buttons;
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);
1267 if (contextMenu) {
1268 BuildContextMenu(contextMenu);
1269 contextMenu->SetAsyncAutoDestruct(true);
1270 contextMenu->Go(ConvertToScreen(where), true, true,
1271 ConvertToScreen(fIconRect));
1273 } else {
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
1277 BPoint offsetPoint;
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) {
1289 int32 clickCount;
1290 Window()->CurrentMessage()->FindInt32("clicks",
1291 &clickCount);
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;
1297 fDoubleClick
1298 = IconCache::sIconCache->IconHitTest(offsetPoint,
1299 fIconModel, kNormalIcon, B_LARGE_ICON);
1306 fClickPoint = where;
1307 fMouseDown = true;
1308 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1312 void
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) {
1333 case link_track:
1334 if (fLinkRect.Contains(where) != fMouseDown) {
1335 fMouseDown = !fMouseDown;
1336 InvertRect(fLinkRect);
1338 break;
1340 case path_track:
1341 if (fPathRect.Contains(where) != fMouseDown) {
1342 fMouseDown = !fMouseDown;
1343 InvertRect(fPathRect);
1345 break;
1347 case size_track:
1348 if (fSizeRect.Contains(where) != fMouseDown) {
1349 fMouseDown = !fMouseDown;
1350 InvertRect(fSizeRect);
1352 break;
1354 case icon_track:
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
1359 BFont font;
1360 GetFont(&font);
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);
1368 dragBitmap->Lock();
1369 BView* view = new BView(dragBitmap->Bounds(), "",
1370 B_FOLLOW_NONE, 0);
1371 dragBitmap->AddChild(view);
1372 view->SetOrigin(0, 0);
1373 BRect clipRect(view->Bounds());
1374 BRegion newClip;
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);
1388 // Draw the icon
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,
1397 rect.Width() - 5);
1400 // Draw the label
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());
1409 view->Sync();
1410 dragBitmap->Unlock();
1412 BMessage dragMessage(B_REFS_RECEIVED);
1413 dragMessage.AddPoint("click_pt", fClickPoint);
1414 BPoint tmpLoc;
1415 uint32 button;
1416 GetMouse(&tmpLoc, &button);
1417 if (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);
1427 fDragging = true;
1429 break;
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
1434 break;
1436 default:
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
1441 uint32 buttons;
1442 BPoint point;
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());
1450 BFont font;
1451 GetFont(&font);
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(),
1469 BMessenger(this),
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);
1484 if (!fLinkWindow
1485 || BMessenger(fLinkWindow).IsValid() == false) {
1486 fLinkWindow = OpenToolTipWindow(screen, rect,
1487 "fLinkWindow", fLinkToStr.String(),
1488 BMessenger(this),
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);
1499 if (!fDescWindow
1500 || BMessenger(fDescWindow).IsValid() == false) {
1501 fDescWindow = OpenToolTipWindow(screen, rect,
1502 "fDescWindow", fDescStr.String(),
1503 BMessenger(this), NULL);
1507 break;
1511 DelayedInvalidate(16666, fPathRect);
1512 DelayedInvalidate(16666, fLinkRect);
1516 void
1517 AttributeView::OpenLinkSource()
1519 OpenParentAndSelectOriginal(fModel->EntryRef());
1523 void
1524 AttributeView::OpenLinkTarget()
1526 Model resolvedModel(fModel->EntryRef(), true, true);
1527 BEntry entry;
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());
1540 if (window != NULL)
1541 window->OpenFilePanel(fModel->EntryRef());
1542 } else {
1543 entry_ref ref;
1544 entry.GetRef(&ref);
1545 BPath path(&ref);
1546 printf("Opening link target: %s\n", path.Path());
1547 OpenParentAndSelectOriginal(&ref);
1552 void
1553 AttributeView::MouseUp(BPoint where)
1555 // Are we in the link rect?
1556 if (fTrackingState == link_track && fLinkRect.Contains(where)) {
1557 InvertRect(fLinkRect);
1558 OpenLinkTarget();
1559 } else if (fTrackingState == path_track && fPathRect.Contains(where)) {
1560 InvertRect(fPathRect);
1561 OpenLinkSource();
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
1568 if (fDoubleClick) {
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)) {
1580 // Recalculate size
1581 Window()->PostMessage(kRecalculateSize);
1584 // End mouse tracking
1585 fMouseDown = false;
1586 fDragging = false;
1587 fTrackingState = no_track;
1592 void
1593 AttributeView::CheckAndSetSize()
1595 if (fModel->IsVolume() || fModel->IsRoot()) {
1596 off_t freeBytes = 0;
1597 off_t capacity = 0;
1599 if (fModel->IsVolume()) {
1600 BVolume volume(fModel->NodeRef()->device);
1601 freeBytes = volume.FreeBytes();
1602 capacity = volume.Capacity();
1603 } else {
1604 // iterate over all volumes
1605 BVolumeRoster volumeRoster;
1606 BVolume volume;
1607 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
1608 freeBytes += volume.FreeBytes();
1609 capacity += volume.Capacity();
1613 if (fFreeBytes == freeBytes)
1614 return;
1616 fFreeBytes = freeBytes;
1618 fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
1620 char sizeStr[128];
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)
1631 StatStruct statBuf;
1632 BModelOpener opener(fModel);
1634 if (fModel->InitCheck() != B_OK
1635 || fModel->Node()->GetStat(&statBuf) != B_OK) {
1636 return;
1639 if (fLastSize == statBuf.st_size)
1640 return;
1642 fLastSize = statBuf.st_size;
1643 fSizeString = "";
1644 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
1645 } else
1646 return;
1648 BRect bounds(Bounds());
1649 float lineHeight = CurrentFontHeight() + 2;
1650 bounds.Set(fDivider, fIconRect.bottom, bounds.right,
1651 fIconRect.bottom + lineHeight);
1652 Invalidate(bounds);
1656 void
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);
1668 return;
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);
1683 break;
1686 case kOpenLinkSource:
1687 OpenLinkSource();
1688 break;
1690 case kOpenLinkTarget:
1691 OpenLinkTarget();
1692 break;
1694 default:
1695 _inherited::MessageReceived(message);
1696 break;
1701 void
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));
1709 FillRect(Bounds());
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);
1725 // Font information
1726 font_height fontMetrics;
1727 BFont currentFont;
1728 float lineHeight = 0;
1729 float lineBase = 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(&currentFont);
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());
1751 } else
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;
1763 // Capacity/size
1764 SetHighColor(labelColor);
1765 if (fModel->IsVolume() || fModel->IsRoot()) {
1766 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))),
1767 lineBase));
1768 DrawString(B_TRANSLATE("Capacity:"));
1769 } else {
1770 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))),
1771 lineBase));
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())
1788 + 3;
1789 } else {
1790 DrawString(fSizeString.String());
1791 fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3;
1793 lineBase += lineHeight;
1795 // Created
1796 SetHighColor(labelColor);
1797 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))),
1798 lineBase));
1799 DrawString(B_TRANSLATE("Created:"));
1800 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1801 SetHighColor(attributeColor);
1802 DrawString(fCreatedStr.String());
1803 lineBase += lineHeight;
1805 // Modified
1806 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))),
1807 lineBase));
1808 SetHighColor(labelColor);
1809 DrawString(B_TRANSLATE("Modified:"));
1810 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1811 SetHighColor(attributeColor);
1812 DrawString(fModifiedStr.String());
1813 lineBase += lineHeight;
1815 // Kind
1816 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))),
1817 lineBase));
1818 SetHighColor(labelColor);
1819 DrawString(B_TRANSLATE("Kind:"));
1820 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1821 SetHighColor(attributeColor);
1822 DrawString(fKindStr.String());
1823 lineBase += lineHeight;
1825 BFont normalFont;
1826 GetFont(&normalFont);
1828 // Path
1829 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))),
1830 lineBase));
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());
1844 } else
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;
1855 // Link to/version
1856 if (fModel->IsSymLink()) {
1857 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))),
1858 lineBase));
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());
1871 } else
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())
1879 + 3;
1881 // No description field
1882 fDescRect = BRect(-1, -1, -1, -1);
1883 } else if (fModel->IsExecutable()) {
1884 //Version
1885 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))),
1886 lineBase));
1887 SetHighColor(labelColor);
1888 DrawString(B_TRANSLATE("Version:"));
1889 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
1890 SetHighColor(attributeColor);
1891 BString nameString;
1892 if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK)
1893 DrawString(nameString.String());
1894 else
1895 DrawString("-");
1896 lineBase += lineHeight;
1898 // Description
1899 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))),
1900 lineBase));
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());
1912 } else
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;
1921 // No link field
1922 fLinkRect = BRect(-1, -1, -1, -1);
1927 void
1928 AttributeView::BeginEditingTitle()
1930 if (fTitleEditView != NULL)
1931 return;
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
1943 // into position
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();
1970 void
1971 AttributeView::FinishEditingTitle(bool commit)
1973 if (fTitleEditView == NULL)
1974 return;
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());
1983 BDirectory parent;
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."),
1990 B_TRANSLATE("OK"),
1991 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1992 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1993 alert->Go();
1994 reopen = true;
1995 } else {
1996 if (fModel->IsVolume()) {
1997 BVolume volume(fModel->NodeRef()->device);
1998 if (volume.InitCheck() == B_OK)
1999 volume.SetName(text);
2000 } else
2001 entry.Rename(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."),
2014 B_TRANSLATE("OK"),
2015 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2016 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2017 alert->Go();
2018 reopen = true;
2021 // Remove view
2022 BView* scrollView = fTitleEditView->Parent();
2023 RemoveChild(scrollView);
2024 delete scrollView;
2025 fTitleEditView = NULL;
2027 if (reopen)
2028 BeginEditingTitle();
2032 void
2033 AttributeView::MakeFocus(bool focus)
2035 if (!focus && fTitleEditView != NULL)
2036 FinishEditingTitle(true);
2040 void
2041 AttributeView::WindowActivated(bool active)
2043 if (active)
2044 return;
2046 if (fTitleEditView != NULL)
2047 FinishEditingTitle(true);
2049 if (fPathWindow->Lock()) {
2050 fPathWindow->Quit();
2051 fPathWindow = NULL;
2054 if (fLinkWindow->Lock()) {
2055 fLinkWindow->Quit();
2056 fLinkWindow = NULL;
2059 if (fDescWindow->Lock()) {
2060 fDescWindow->Quit();
2061 fDescWindow = NULL;
2066 float
2067 AttributeView::CurrentFontHeight()
2069 BFont font;
2070 GetFont(&font);
2071 font_height fontHeight;
2072 font.GetHeight(&fontHeight);
2074 return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
2078 status_t
2079 AttributeView::BuildContextMenu(BMenu* parent)
2081 if (parent == NULL)
2082 return B_BAD_VALUE;
2084 // Add navigation menu if this is not a symlink
2085 // Symlink's to directories are OK however!
2086 BEntry entry(fModel->EntryRef());
2087 entry_ref ref;
2088 entry.GetRef(&ref);
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();
2096 entry.GetRef(&ref);
2098 } else if (model.IsDirectory() || model.IsVolume())
2099 navigate = true;
2101 ModelMenuItem* navigationItem = NULL;
2102 if (navigate) {
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
2138 BVolume boot;
2139 BVolumeRoster().GetBootVolume(&boot);
2140 BVolume volume;
2141 volume.SetTo(fModel->NodeRef()->device);
2142 if (volume == boot)
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
2176 if (navigate)
2177 navigationItem->SetTarget(be_app);
2178 if (sizeItem)
2179 sizeItem->SetTarget(Window());
2181 return B_OK;
2185 void
2186 AttributeView::SetPermissionsSwitchState(int32 state)
2188 fPermissionsSwitch->SetValue(state);
2189 fPermissionsSwitch->Invalidate();
2193 filter_result
2194 AttributeView::TextViewFilter(BMessage* message, BHandler**,
2195 BMessageFilter* filter)
2197 uchar key;
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;
2220 off_t
2221 AttributeView::LastSize() const
2223 return fLastSize;
2227 void
2228 AttributeView::SetLastSize(off_t lastSize)
2230 fLastSize = lastSize;
2234 void
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);
2243 Invalidate(bounds);
2247 // #pragma mark -
2250 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message)
2251 : BControl(frame, "trackingView", str, message, B_FOLLOW_ALL,
2252 B_WILL_DRAW),
2253 fMouseDown(false),
2254 fMouseInView(false)
2256 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
2257 SetEventMask(B_POINTER_EVENTS, 0);
2261 void
2262 TrackingView::MouseDown(BPoint)
2264 if (Message() != NULL) {
2265 fMouseDown = true;
2266 fMouseInView = true;
2267 InvertRect(Bounds());
2272 void
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)
2281 Window()->Close();
2285 void
2286 TrackingView::MouseUp(BPoint)
2288 if (Message() != NULL) {
2289 if (fMouseInView)
2290 Invoke();
2292 fMouseDown = false;
2293 Window()->Close();
2298 void
2299 TrackingView::Draw(BRect)
2301 if (Message() != NULL)
2302 SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR
2303 : B_LINK_TEXT_COLOR);
2304 else
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));