xref: /haiku/src/kits/tracker/IconMenuItem.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
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:
14 
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.
17 
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.
24 
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.
28 
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.
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 
49 static void
50 DimmedIconBlitter(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 
70 ModelMenuItem::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 
96 ModelMenuItem::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 
112 ModelMenuItem::~ModelMenuItem()
113 {
114 }
115 
116 
117 status_t
118 ModelMenuItem::SetEntry(const BEntry* entry)
119 {
120 	return fModel.SetTo(entry);
121 }
122 
123 
124 void
125 ModelMenuItem::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 
140 void
141 ModelMenuItem::Highlight(bool hilited)
142 {
143 	_inherited::Highlight(hilited);
144 	DrawIcon();
145 }
146 
147 
148 void
149 ModelMenuItem::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 
180 void
181 ModelMenuItem::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 
193 status_t
194 ModelMenuItem::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 */
231 SpecialModelMenuItem::SpecialModelMenuItem(const Model* model, BMenu* menu)
232 	: ModelMenuItem(model, menu)
233 {
234 }
235 
236 
237 void
238 SpecialModelMenuItem::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 */
259 IconMenuItem::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 
275 IconMenuItem::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 
298 IconMenuItem::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 
325 IconMenuItem::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 
352 IconMenuItem::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 
382 BArchivable*
383 IconMenuItem::Instantiate(BMessage* data)
384 {
385 	//if (validate_instantiation(data, "IconMenuItem"))
386 		return new IconMenuItem(data);
387 
388 	return NULL;
389 }
390 
391 
392 status_t
393 IconMenuItem::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 
409 IconMenuItem::~IconMenuItem()
410 {
411 	delete fDeviceIcon;
412 }
413 
414 
415 void
416 IconMenuItem::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 
432 void
433 IconMenuItem::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 
466 void
467 IconMenuItem::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 
518 void
519 IconMenuItem::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