xref: /haiku/src/apps/webpositive/BookmarkBar.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
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