1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35//! Menu items with small icons.
36
37
38#include "IconMenuItem.h"
39
40#include <ControlLook.h>
41#include <Debug.h>
42#include <Menu.h>
43#include <MenuField.h>
44#include <NodeInfo.h>
45
46#include "IconCache.h"
47
48
49static void
50DimmedIconBlitter(BView* view, BPoint where, BBitmap* bitmap, void*)
51{
52	if (bitmap->ColorSpace() == B_RGBA32) {
53		rgb_color oldHighColor = view->HighColor();
54		view->SetHighColor(0, 0, 0, 128);
55		view->SetDrawingMode(B_OP_ALPHA);
56		view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
57		view->DrawBitmap(bitmap, where);
58		view->SetHighColor(oldHighColor);
59	} else {
60		view->SetDrawingMode(B_OP_BLEND);
61		view->DrawBitmap(bitmap, where);
62	}
63	view->SetDrawingMode(B_OP_OVER);
64}
65
66
67//	#pragma mark - ModelMenuItem
68
69
70ModelMenuItem::ModelMenuItem(const Model* model, const char* title,
71	BMessage* message, char shortcut, uint32 modifiers,
72	bool drawText, bool extraPad)
73	:
74	BMenuItem(title, message, shortcut, modifiers),
75	fModel(*model),
76	fHeightDelta(0),
77	fDrawText(drawText),
78	fExtraPad(extraPad)
79{
80	ThrowOnInitCheckError(&fModel);
81	// The 'fExtraPad' field is used to when this menu item is added to
82	// a menubar instead of a menu. Menus and MenuBars space out items
83	// differently (more space around items in a menu). This class wants
84	// to be able to space item the same, no matter where they are. The
85	// fExtraPad field allows for that.
86
87	if (model->IsRoot())
88		SetLabel(model->Name());
89
90	// ModelMenuItem is used in synchronously invoked menus, make sure
91	// we invoke with a timeout
92	SetTimeout(kSynchMenuInvokeTimeout);
93}
94
95
96ModelMenuItem::ModelMenuItem(const Model* model, BMenu* menu, bool drawText,
97	bool extraPad)
98	:
99	BMenuItem(menu),
100	fModel(*model),
101	fHeightDelta(0),
102	fDrawText(drawText),
103	fExtraPad(extraPad)
104{
105	ThrowOnInitCheckError(&fModel);
106	// ModelMenuItem is used in synchronously invoked menus, make sure
107	// we invoke with a timeout
108	SetTimeout(kSynchMenuInvokeTimeout);
109}
110
111
112ModelMenuItem::~ModelMenuItem()
113{
114}
115
116
117status_t
118ModelMenuItem::SetEntry(const BEntry* entry)
119{
120	return fModel.SetTo(entry);
121}
122
123
124void
125ModelMenuItem::DrawContent()
126{
127	if (fDrawText) {
128		BPoint drawPoint(ContentLocation());
129		drawPoint.x += ListIconSize() + (ListIconSize() / 4)
130			+ (fExtraPad ? 6 : 0);
131		if (fHeightDelta > 0)
132			drawPoint.y += ceil(fHeightDelta / 2);
133		Menu()->MovePenTo(drawPoint);
134		_inherited::DrawContent();
135	}
136	DrawIcon();
137}
138
139
140void
141ModelMenuItem::Highlight(bool hilited)
142{
143	_inherited::Highlight(hilited);
144	DrawIcon();
145}
146
147
148void
149ModelMenuItem::DrawIcon()
150{
151	Menu()->PushState();
152
153	BPoint where(ContentLocation());
154	// center icon with text.
155
156	float deltaHeight = fHeightDelta < 0 ? -fHeightDelta : 0;
157	where.y += ceil(deltaHeight / 2);
158
159	if (fExtraPad)
160		where.x += 6;
161
162	Menu()->SetDrawingMode(B_OP_OVER);
163	Menu()->SetLowColor(B_TRANSPARENT_32_BIT);
164
165	// draw small icon, synchronously
166	if (IsEnabled()) {
167		IconCache::sIconCache->Draw(fModel.ResolveIfLink(), Menu(), where,
168			kNormalIcon, BSize(ListIconSize() - 1, ListIconSize() - 1));
169	} else {
170		// dimmed, for now use a special blitter; icon cache should
171		// know how to blit one eventually
172		IconCache::sIconCache->SyncDraw(fModel.ResolveIfLink(), Menu(), where,
173			kNormalIcon, BSize(ListIconSize() - 1, ListIconSize() - 1), DimmedIconBlitter);
174	}
175
176	Menu()->PopState();
177}
178
179
180void
181ModelMenuItem::GetContentSize(float* width, float* height)
182{
183	_inherited::GetContentSize(width, height);
184
185	const float iconSize = ListIconSize();
186	fHeightDelta = iconSize - *height;
187	if (*height < iconSize)
188		*height = iconSize;
189	*width = *width + iconSize / 4 + iconSize + (fExtraPad ? 18 : 0);
190}
191
192
193status_t
194ModelMenuItem::Invoke(BMessage* message)
195{
196	if (Menu() == NULL)
197		return B_ERROR;
198
199	if (!IsEnabled())
200		return B_ERROR;
201
202	if (message == NULL)
203		message = Message();
204
205	if (message == NULL)
206		return B_BAD_VALUE;
207
208	BMessage clone(*message);
209	clone.AddInt32("index", Menu()->IndexOf(this));
210	clone.AddInt64("when", system_time());
211	clone.AddPointer("source", this);
212
213	if ((modifiers() & B_OPTION_KEY) == 0) {
214		// if option not held, remove refs to close to prevent closing
215		// parent window
216		clone.RemoveData("nodeRefsToClose");
217	}
218
219	return BInvoker::Invoke(&clone);
220}
221
222
223//	#pragma mark - SpecialModelMenuItem
224
225
226/*!	A ModelMenuItem subclass that draws its label in italics.
227
228	It's used for example in the "Copy To" menu to indicate some special
229	folders like the parent folder.
230*/
231SpecialModelMenuItem::SpecialModelMenuItem(const Model* model, BMenu* menu)
232	: ModelMenuItem(model, menu)
233{
234}
235
236
237void
238SpecialModelMenuItem::DrawContent()
239{
240	Menu()->PushState();
241
242	BFont font;
243	Menu()->GetFont(&font);
244	font.SetFace(B_ITALIC_FACE);
245	Menu()->SetFont(&font);
246
247	_inherited::DrawContent();
248	Menu()->PopState();
249}
250
251
252//	#pragma mark - IconMenuItem
253
254
255/*!	A menu item that draws an icon alongside the label.
256
257	It's currently used in the mount and new file template menus.
258*/
259IconMenuItem::IconMenuItem(const char* label, BMessage* message, BBitmap* icon,
260	icon_size which)
261	:
262	PositionPassingMenuItem(label, message),
263	fDeviceIcon(NULL),
264	fHeightDelta(0),
265	fWhich(which)
266{
267	SetIcon(icon);
268
269	// IconMenuItem is used in synchronously invoked menus, make sure
270	// we invoke with a timeout
271	SetTimeout(kSynchMenuInvokeTimeout);
272}
273
274
275IconMenuItem::IconMenuItem(const char* label, BMessage* message,
276	const BNodeInfo* nodeInfo, icon_size which)
277	:
278	PositionPassingMenuItem(label, message),
279	fDeviceIcon(NULL),
280	fHeightDelta(0),
281	fWhich(which)
282{
283	if (nodeInfo != NULL) {
284		fDeviceIcon = new BBitmap(BRect(BPoint(0, 0),
285			be_control_look->ComposeIconSize(which)), kDefaultIconDepth);
286		if (nodeInfo->GetTrackerIcon(fDeviceIcon, (icon_size)-1) != B_OK) {
287			delete fDeviceIcon;
288			fDeviceIcon = NULL;
289		}
290	}
291
292	// IconMenuItem is used in synchronously invoked menus, make sure
293	// we invoke with a timeout
294	SetTimeout(kSynchMenuInvokeTimeout);
295}
296
297
298IconMenuItem::IconMenuItem(const char* label, BMessage* message,
299	const char* iconType, icon_size which)
300	:
301	PositionPassingMenuItem(label, message),
302	fDeviceIcon(NULL),
303	fHeightDelta(0),
304	fWhich(which)
305{
306	BMimeType mime(iconType);
307	fDeviceIcon = new BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(which)),
308		kDefaultIconDepth);
309
310	if (mime.GetIcon(fDeviceIcon, which) != B_OK) {
311		BMimeType super;
312		mime.GetSupertype(&super);
313		if (super.GetIcon(fDeviceIcon, which) != B_OK) {
314			delete fDeviceIcon;
315			fDeviceIcon = NULL;
316		}
317	}
318
319	// IconMenuItem is used in synchronously invoked menus, make sure
320	// we invoke with a timeout
321	SetTimeout(kSynchMenuInvokeTimeout);
322}
323
324
325IconMenuItem::IconMenuItem(BMenu* submenu, BMessage* message,
326	const char* iconType, icon_size which)
327	:
328	PositionPassingMenuItem(submenu, message),
329	fDeviceIcon(NULL),
330	fHeightDelta(0),
331	fWhich(which)
332{
333	BMimeType mime(iconType);
334	fDeviceIcon = new BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(which)),
335		kDefaultIconDepth);
336
337	if (mime.GetIcon(fDeviceIcon, which) != B_OK) {
338		BMimeType super;
339		mime.GetSupertype(&super);
340		if (super.GetIcon(fDeviceIcon, which) != B_OK) {
341			delete fDeviceIcon;
342			fDeviceIcon = NULL;
343		}
344	}
345
346	// IconMenuItem is used in synchronously invoked menus, make sure
347	// we invoke with a timeout
348	SetTimeout(kSynchMenuInvokeTimeout);
349}
350
351
352IconMenuItem::IconMenuItem(BMessage* data)
353	:
354	PositionPassingMenuItem(data),
355	fDeviceIcon(NULL),
356	fHeightDelta(0),
357	fWhich(B_MINI_ICON)
358{
359	if (data != NULL) {
360		fWhich = (icon_size)data->GetInt32("_which", B_MINI_ICON);
361
362		fDeviceIcon = new BBitmap(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(fWhich)),
363			kDefaultIconDepth);
364
365		if (data->HasData("_deviceIconBits", B_RAW_TYPE)) {
366			ssize_t numBytes;
367			const void* bits;
368			if (data->FindData("_deviceIconBits", B_RAW_TYPE, &bits, &numBytes)
369					== B_OK) {
370				fDeviceIcon->SetBits(bits, numBytes, (int32)0,
371					kDefaultIconDepth);
372			}
373		}
374	}
375
376	// IconMenuItem is used in synchronously invoked menus, make sure
377	// we invoke with a timeout
378	SetTimeout(kSynchMenuInvokeTimeout);
379}
380
381
382BArchivable*
383IconMenuItem::Instantiate(BMessage* data)
384{
385	//if (validate_instantiation(data, "IconMenuItem"))
386		return new IconMenuItem(data);
387
388	return NULL;
389}
390
391
392status_t
393IconMenuItem::Archive(BMessage* data, bool deep) const
394{
395	status_t result = PositionPassingMenuItem::Archive(data, deep);
396
397	if (result == B_OK)
398		result = data->AddInt32("_which", (int32)fWhich);
399
400	if (result == B_OK && fDeviceIcon != NULL) {
401		result = data->AddData("_deviceIconBits", B_RAW_TYPE,
402			fDeviceIcon->Bits(), fDeviceIcon->BitsLength());
403	}
404
405	return result;
406}
407
408
409IconMenuItem::~IconMenuItem()
410{
411	delete fDeviceIcon;
412}
413
414
415void
416IconMenuItem::GetContentSize(float* width, float* height)
417{
418	_inherited::GetContentSize(width, height);
419
420	int32 iconHeight = fWhich;
421	if (fDeviceIcon != NULL)
422		iconHeight = fDeviceIcon->Bounds().IntegerHeight() + 1;
423
424	fHeightDelta = iconHeight - *height;
425	if (*height < iconHeight)
426		*height = iconHeight;
427
428	*width += 20;
429}
430
431
432void
433IconMenuItem::DrawContent()
434{
435	BPoint drawPoint(ContentLocation());
436	if (fDeviceIcon != NULL)
437		drawPoint.x += (fDeviceIcon->Bounds().Width() + 1) + 4.0f;
438
439	if (fHeightDelta > 0)
440		drawPoint.y += ceilf(fHeightDelta / 2);
441
442	Menu()->MovePenTo(drawPoint);
443	_inherited::DrawContent();
444
445	Menu()->PushState();
446
447	BPoint where(ContentLocation());
448	float deltaHeight = fHeightDelta < 0 ? -fHeightDelta : 0;
449	where.y += ceilf(deltaHeight / 2);
450
451	if (fDeviceIcon != NULL) {
452		if (IsEnabled())
453			Menu()->SetDrawingMode(B_OP_ALPHA);
454		else {
455			Menu()->SetDrawingMode(B_OP_ALPHA);
456			Menu()->SetHighColor(0, 0, 0, 64);
457			Menu()->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
458		}
459		Menu()->DrawBitmapAsync(fDeviceIcon, where);
460	}
461
462	Menu()->PopState();
463}
464
465
466void
467IconMenuItem::SetMarked(bool mark)
468{
469	_inherited::SetMarked(mark);
470
471	if (!mark)
472		return;
473
474	// we are marking the item
475
476	BMenu* menu = Menu();
477	if (menu == NULL)
478		return;
479
480	// we have a parent menu
481
482	BMenu* _menu = menu;
483	while ((_menu = _menu->Supermenu()) != NULL)
484		menu = _menu;
485
486	// went up the hierarchy to found the topmost menu
487
488	if (menu == NULL || menu->Parent() == NULL)
489		return;
490
491	// our topmost menu has a parent
492
493	if (dynamic_cast<BMenuField*>(menu->Parent()) == NULL)
494		return;
495
496	// our topmost menu's parent is a BMenuField
497
498	BMenuItem* topLevelItem = menu->ItemAt((int32)0);
499
500	if (topLevelItem == NULL)
501		return;
502
503	// our topmost menu has a menu item
504
505	IconMenuItem* topLevelIconMenuItem
506		= dynamic_cast<IconMenuItem*>(topLevelItem);
507	if (topLevelIconMenuItem == NULL)
508		return;
509
510	// our topmost menu's item is an IconMenuItem
511
512	// update the icon
513	topLevelIconMenuItem->SetIcon(fDeviceIcon);
514	menu->Invalidate();
515}
516
517
518void
519IconMenuItem::SetIcon(BBitmap* icon)
520{
521	if (icon != NULL) {
522		if (fDeviceIcon != NULL)
523			delete fDeviceIcon;
524
525		fDeviceIcon = new BBitmap(BRect(BPoint(0, 0),
526			be_control_look->ComposeIconSize(fWhich)), icon->ColorSpace());
527		fDeviceIcon->ImportBits(icon);
528	} else {
529		delete fDeviceIcon;
530		fDeviceIcon = NULL;
531	}
532}
533