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
20BookmarkBar::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
33BookmarkBar::~BookmarkBar()
34{
35	stop_watching(BMessenger(this));
36	if (!fOverflowMenuAdded)
37		delete fOverflowMenu;
38}
39
40
41void
42BookmarkBar::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
58void
59BookmarkBar::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
140void
141BookmarkBar::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
205BSize
206BookmarkBar::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
224void
225BookmarkBar::_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