1 /* 2 * Copyright 2014, Adrien Destugues <pulkomandy@pulkomandy.tk>. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "BookmarkBar.h" 8 9 #include <Directory.h> 10 #include <Entry.h> 11 #include <IconMenuItem.h> 12 #include <Messenger.h> 13 #include <Window.h> 14 15 #include "NavMenu.h" 16 17 #include <stdio.h> 18 19 20 BookmarkBar::BookmarkBar(const char* title, BHandler* target, 21 const entry_ref* navDir) 22 : 23 BMenuBar(title) 24 { 25 SetFlags(Flags() | B_FRAME_EVENTS); 26 BEntry(navDir).GetNodeRef(&fNodeRef); 27 28 fOverflowMenu = new BMenu(B_UTF8_ELLIPSIS); 29 fOverflowMenuAdded = false; 30 } 31 32 33 BookmarkBar::~BookmarkBar() 34 { 35 stop_watching(BMessenger(this)); 36 if (!fOverflowMenuAdded) 37 delete fOverflowMenu; 38 } 39 40 41 void 42 BookmarkBar::AttachedToWindow() 43 { 44 BMenuBar::AttachedToWindow(); 45 watch_node(&fNodeRef, B_WATCH_DIRECTORY, BMessenger(this)); 46 47 // Enumerate initial directory content 48 BDirectory dir(&fNodeRef); 49 BEntry bookmark; 50 while (dir.GetNextEntry(&bookmark, true) == B_OK) { 51 node_ref ref; 52 if (bookmark.GetNodeRef(&ref) == B_OK) 53 _AddItem(ref.node, &bookmark); 54 } 55 } 56 57 58 void 59 BookmarkBar::MessageReceived(BMessage* message) 60 { 61 switch (message->what) { 62 case B_NODE_MONITOR: 63 { 64 int32 opcode = message->FindInt32("opcode"); 65 ino_t inode = message->FindInt64("node"); 66 switch (opcode) { 67 case B_ENTRY_CREATED: 68 { 69 entry_ref ref; 70 const char* name; 71 72 message->FindInt32("device", &ref.device); 73 message->FindInt64("directory", &ref.directory); 74 message->FindString("name", &name); 75 ref.set_name(name); 76 77 BEntry entry(&ref, true); 78 if (entry.InitCheck() == B_OK) 79 _AddItem(inode, &entry); 80 break; 81 } 82 case B_ENTRY_MOVED: 83 { 84 entry_ref ref; 85 const char* name; 86 87 message->FindInt32("device", &ref.device); 88 message->FindInt64("to directory", &ref.directory); 89 message->FindString("name", &name); 90 ref.set_name(name); 91 92 if (fItemsMap[inode] == NULL) { 93 BEntry entry(&ref, true); 94 _AddItem(inode, &entry); 95 break; 96 } else { 97 // Existing item. Check if it's a rename or a move 98 // to some other folder. 99 ino_t from, to; 100 message->FindInt64("to directory", &to); 101 message->FindInt64("from directory", &from); 102 if (from == to) { 103 const char* name; 104 if (message->FindString("name", &name) == B_OK) 105 fItemsMap[inode]->SetLabel(name); 106 107 BMessage* itemMessage = new BMessage( 108 B_REFS_RECEIVED); 109 itemMessage->AddRef("refs", &ref); 110 fItemsMap[inode]->SetMessage(itemMessage); 111 112 break; 113 } 114 } 115 116 // fall through: the item was moved from here to 117 // elsewhere, remove it from the bar. 118 } 119 case B_ENTRY_REMOVED: 120 { 121 IconMenuItem* item = fItemsMap[inode]; 122 RemoveItem(item); 123 fOverflowMenu->RemoveItem(item); 124 fItemsMap.erase(inode); 125 delete item; 126 127 // Reevaluate whether the "more" menu is still needed 128 BRect rect = Bounds(); 129 FrameResized(rect.Width(), rect.Height()); 130 } 131 } 132 return; 133 } 134 } 135 136 BMenuBar::MessageReceived(message); 137 } 138 139 140 void 141 BookmarkBar::FrameResized(float width, float height) 142 { 143 int32 count = CountItems(); 144 145 // Account for the "more" menu, in terms of item count and space occupied 146 int32 overflowMenuWidth = 0; 147 if (IndexOf(fOverflowMenu) != B_ERROR) { 148 count--; 149 // Ignore the width of the "more" menu if it would disappear after 150 // removing a bookmark from it. 151 if (fOverflowMenu->CountItems() > 1) 152 overflowMenuWidth = 32; 153 } 154 155 int32 i = 0; 156 float rightmost = 0.f; 157 while (i < count) { 158 BMenuItem* item = ItemAt(i); 159 BRect frame = item->Frame(); 160 if (frame.right > width - overflowMenuWidth) 161 break; 162 rightmost = frame.right; 163 i++; 164 } 165 166 if (i == count) { 167 // See if we can move some items from the "more" menu in the remaining 168 // space. 169 BMenuItem* extraItem = fOverflowMenu->ItemAt(0); 170 while (extraItem != NULL) { 171 BRect frame = extraItem->Frame(); 172 if (frame.Width() + rightmost > width - overflowMenuWidth) 173 break; 174 AddItem(fOverflowMenu->RemoveItem((int32)0), i); 175 i++; 176 177 rightmost = ItemAt(i)->Frame().right; 178 if (fOverflowMenu->CountItems() <= 1) 179 overflowMenuWidth = 0; 180 extraItem = fOverflowMenu->ItemAt(0); 181 } 182 if (fOverflowMenu->CountItems() == 0) { 183 RemoveItem(fOverflowMenu); 184 fOverflowMenuAdded = false; 185 } 186 187 } else { 188 // Remove any overflowing item and move them to the "more" menu. 189 // Counting backwards avoids complications when indices shift 190 // after an item is removed, and keeps bookmarks in the same order, 191 // provided they are added at index 0 of the "more" menu. 192 for (int j = count - 1; j >= i; j--) 193 fOverflowMenu->AddItem(RemoveItem(j), 0); 194 195 if (IndexOf(fOverflowMenu) == B_ERROR) { 196 AddItem(fOverflowMenu); 197 fOverflowMenuAdded = true; 198 } 199 } 200 201 BMenuBar::FrameResized(width, height); 202 } 203 204 205 BSize 206 BookmarkBar::MinSize() 207 { 208 BSize size = BMenuBar::MinSize(); 209 210 // We only need space to show the "more" button. 211 size.width = 32; 212 213 // We need enough vertical space to show bookmark icons. 214 if (size.height < 20) 215 size.height = 20; 216 217 return size; 218 } 219 220 221 // #pragma mark - private methods 222 223 224 void 225 BookmarkBar::_AddItem(ino_t inode, BEntry* entry) 226 { 227 char name[B_FILE_NAME_LENGTH]; 228 entry->GetName(name); 229 230 // make sure the item doesn't already exists 231 if (fItemsMap[inode] != NULL) 232 return; 233 234 entry_ref ref; 235 entry->GetRef(&ref); 236 237 IconMenuItem* item = NULL; 238 239 if (entry->IsDirectory()) { 240 BNavMenu* menu = new BNavMenu(name, B_REFS_RECEIVED, Window()); 241 menu->SetNavDir(&ref); 242 item = new IconMenuItem(menu, NULL, 243 "application/x-vnd.Be-directory", B_MINI_ICON); 244 245 } else { 246 BNode node(entry); 247 BNodeInfo info(&node); 248 249 BMessage* message = new BMessage(B_REFS_RECEIVED); 250 message->AddRef("refs", &ref); 251 item = new IconMenuItem(name, message, &info, B_MINI_ICON); 252 } 253 254 int32 count = CountItems(); 255 if (IndexOf(fOverflowMenu) != B_ERROR) 256 count--; 257 258 BMenuBar::AddItem(item, count); 259 fItemsMap[inode] = item; 260 261 // Move the item to the "more" menu if it overflows. 262 BRect rect = Bounds(); 263 FrameResized(rect.Width(), rect.Height()); 264 } 265