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
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their respective
32holders.
33All rights reserved.
34*/
35
36
37#include "TeamMenuItem.h"
38
39#include <string.h>
40#include <stdio.h>
41#include <stdlib.h>
42
43#include <algorithm>
44
45#include <Bitmap.h>
46#include <ControlLook.h>
47#include <Debug.h>
48#include <Font.h>
49#include <Mime.h>
50#include <Region.h>
51#include <Roster.h>
52#include <Resources.h>
53#include <Window.h>
54
55#include "BarApp.h"
56#include "BarMenuBar.h"
57#include "BarView.h"
58#include "ExpandoMenuBar.h"
59#include "ResourceSet.h"
60#include "ShowHideMenuItem.h"
61#include "StatusView.h"
62#include "TeamMenu.h"
63#include "WindowMenu.h"
64#include "WindowMenuItem.h"
65
66
67static float sHPad, sVPad, sLabelOffset = 0.0f;
68
69
70//	#pragma mark - TTeamMenuItem
71
72
73TTeamMenuItem::TTeamMenuItem(BList* team, BBitmap* icon, char* name,
74	char* signature, float width, float height)
75	:
76	TTruncatableMenuItem(new TWindowMenu(team, signature))
77{
78	_Init(team, icon, name, signature, width, height);
79}
80
81
82TTeamMenuItem::TTeamMenuItem(float width, float height)
83	:
84	TTruncatableMenuItem("", NULL)
85{
86	_Init(NULL, NULL, strdup(""), strdup(""), width, height);
87}
88
89
90TTeamMenuItem::~TTeamMenuItem()
91{
92	delete fTeam;
93	delete fIcon;
94	free(fSignature);
95}
96
97
98/*!	Vulcan Death Grip and other team mouse button handling
99
100	\returns true if handled, false otherwise
101*/
102bool
103TTeamMenuItem::HandleMouseDown(BPoint where)
104{
105	BMenu* menu = Menu();
106	if (menu == NULL)
107		return false;
108
109	BWindow* window = menu->Window();
110	if (window == NULL)
111		return false;
112
113	BMessage* message = window->CurrentMessage();
114	if (message == NULL)
115		return false;
116
117	int32 modifiers = 0;
118	int32 buttons = 0;
119	message->FindInt32("modifiers", &modifiers);
120	message->FindInt32("buttons", &buttons);
121
122	// check for three finger salute, a.k.a. Vulcan Death Grip
123	if ((modifiers & B_COMMAND_KEY) != 0
124		&& (modifiers & B_CONTROL_KEY) != 0
125		&& (modifiers & B_SHIFT_KEY) != 0) {
126		BMessage appMessage(B_SOME_APP_QUIT);
127		int32 teamCount = fTeam->CountItems();
128		BMessage quitMessage(teamCount == 1 ? B_SOME_APP_QUIT : kRemoveTeam);
129		quitMessage.AddInt32("itemIndex", menu->IndexOf(this));
130		for (int32 index = 0; index < teamCount; index++) {
131			team_id team = (addr_t)fTeam->ItemAt(index);
132			appMessage.AddInt32("be:team", team);
133			quitMessage.AddInt32("team", team);
134
135			kill_team(team);
136		}
137		be_app->PostMessage(&appMessage);
138		TExpandoMenuBar* expando = dynamic_cast<TExpandoMenuBar*>(menu);
139		window->PostMessage(&quitMessage, expando != NULL ? expando : menu);
140		return true;
141	} else if ((modifiers & B_SHIFT_KEY) == 0
142		&& (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
143		// launch new team
144		be_roster->Launch(Signature());
145		return true;
146	} else if ((modifiers & B_CONTROL_KEY) != 0) {
147		// control click - show all/hide all shortcut
148		BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
149			? kMinimizeTeam : kBringTeamToFront);
150		showMessage.AddInt32("itemIndex", menu->IndexOf(this));
151		window->PostMessage(&showMessage, menu);
152		return true;
153	}
154
155	return false;
156}
157
158
159status_t
160TTeamMenuItem::Invoke(BMessage* message)
161{
162	if (fBarView != NULL) {
163		if (fBarView->InvokeItem(Signature())) {
164			// handles drop on application
165			return B_OK;
166		}
167
168		// if the app could not handle the drag message
169		// and we were dragging, then kill the drag
170		// should never get here, disabled item will not invoke
171		if (fBarView->Dragging())
172			fBarView->DragStop();
173	}
174
175	// bring to front or minimize shortcuts
176	uint32 mods = modifiers();
177	if (mods & B_CONTROL_KEY) {
178		TShowHideMenuItem::TeamShowHideCommon((mods & B_SHIFT_KEY)
179			? B_MINIMIZE_WINDOW : B_BRING_TO_FRONT, Teams());
180	}
181
182	return BMenuItem::Invoke(message);
183}
184
185
186void
187TTeamMenuItem::SetOverrideSelected(bool selected)
188{
189	fOverriddenSelected = selected;
190	Highlight(selected);
191}
192
193
194void
195TTeamMenuItem::SetIcon(BBitmap* icon) {
196	delete fIcon;
197	fIcon = icon;
198}
199
200
201void
202TTeamMenuItem::GetContentSize(float* width, float* height)
203{
204	BMenuItem::GetContentSize(width, height);
205
206	if (fOverrideWidth != -1.0f)
207		*width = fOverrideWidth;
208	else {
209		const float iconSize = static_cast<TBarApp*>(be_app)->TeamIconSize();
210		const float iconPadding = be_control_look->ComposeSpacing(kIconPadding);
211		float iconOnlyWidth = iconSize + iconPadding;
212		if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels)
213			iconOnlyWidth += iconPadding; // add an extra icon padding
214
215		if (fBarView->MiniState()) {
216			if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels)
217				*width = iconOnlyWidth;
218			else
219				*width = gMinimumWindowWidth - (gDragRegionWidth + kGutter) * 2;
220		} else if (!fBarView->Vertical()) {
221			TExpandoMenuBar* menu = static_cast<TExpandoMenuBar*>(Menu());
222			*width = menu->MaxHorizontalItemWidth();
223		} else
224			*width = static_cast<TBarApp*>(be_app)->Settings()->width;
225	}
226
227	if (fOverrideHeight != -1.0f)
228		*height = fOverrideHeight;
229	else
230		*height = fBarView->TeamMenuItemHeight();
231}
232
233
234void
235TTeamMenuItem::Draw()
236{
237	BRect frame = Frame();
238	BMenu* menu = Menu();
239
240	menu->PushState();
241
242	rgb_color menuColor = ui_color(B_MENU_BACKGROUND_COLOR);
243	bool canHandle = !fBarView->Dragging()
244		|| fBarView->AppCanHandleTypes(Signature());
245	uint32 flags = 0;
246	if (_IsSelected() && canHandle)
247		flags |= BControlLook::B_ACTIVATED;
248
249	uint32 borders = BControlLook::B_TOP_BORDER;
250	if (fBarView->Vertical()) {
251		menu->SetHighColor(tint_color(menuColor, B_DARKEN_1_TINT));
252		borders |= BControlLook::B_LEFT_BORDER
253			| BControlLook::B_RIGHT_BORDER;
254		menu->StrokeLine(frame.LeftBottom(), frame.RightBottom());
255		frame.bottom--;
256
257		be_control_look->DrawMenuBarBackground(menu, frame, frame,
258			menuColor, flags, borders);
259	} else {
260		if (flags & BControlLook::B_ACTIVATED)
261			menu->SetHighColor(tint_color(menuColor, B_DARKEN_3_TINT));
262		else
263			menu->SetHighColor(tint_color(menuColor, 1.22));
264		borders |= BControlLook::B_BOTTOM_BORDER;
265		menu->StrokeLine(frame.LeftTop(), frame.LeftBottom());
266		frame.left++;
267
268		be_control_look->DrawButtonBackground(menu, frame, frame,
269			menuColor, flags, borders);
270	}
271
272	menu->MovePenTo(ContentLocation());
273	DrawContent();
274
275	menu->PopState();
276}
277
278
279void
280TTeamMenuItem::DrawContent()
281{
282	BMenu* menu = Menu();
283	BRect frame = Frame();
284
285	if (fIcon != NULL) {
286		if (fIcon->ColorSpace() == B_RGBA32) {
287			menu->SetDrawingMode(B_OP_ALPHA);
288			menu->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
289		} else
290			menu->SetDrawingMode(B_OP_OVER);
291
292		BRect iconBounds = fIcon->Bounds();
293		BRect updateRect = iconBounds;
294		BPoint contentLocation = ContentLocation();
295		BPoint drawLocation = contentLocation + BPoint(sHPad, sVPad);
296		const int32 large = be_control_look->ComposeIconSize(B_LARGE_ICON)
297			.IntegerWidth() + 1;
298
299		if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels
300			|| (fBarView->Vertical() && iconBounds.Width() > large)) {
301			// determine icon location (centered horizontally)
302			float offsetx = contentLocation.x
303				+ floorf((frame.Width() - iconBounds.Width()) / 2);
304			float offsety = contentLocation.y + sVPad + kGutter;
305
306			// draw icon
307			updateRect.OffsetTo(BPoint(offsetx, offsety));
308			menu->DrawBitmapAsync(fIcon, updateRect);
309
310			// determine label position (below icon)
311			drawLocation.x = floorf((frame.Width() - fLabelWidth) / 2);
312			drawLocation.y = frame.top + sVPad + iconBounds.Height() + sVPad;
313		} else {
314			// determine icon location (centered vertically)
315			float offsetx = contentLocation.x + sHPad;
316			float offsety = contentLocation.y +
317				floorf((frame.Height() - iconBounds.Height()) / 2);
318
319			// draw icon
320			updateRect.OffsetTo(BPoint(offsetx, offsety));
321			menu->DrawBitmapAsync(fIcon, updateRect);
322
323			// determine label position (centered vertically)
324			drawLocation.x += iconBounds.Width() + sLabelOffset;
325			drawLocation.y = frame.top
326				+ ceilf((frame.Height() - fLabelHeight) / 2);
327		}
328
329		menu->MovePenTo(drawLocation);
330	}
331
332	// override the drawing of the content when the item is disabled
333	// the wrong lowcolor is used when the item is disabled since the
334	// text color does not change
335	menu->SetDrawingMode(B_OP_OVER);
336	menu->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
337
338	bool canHandle = !fBarView->Dragging()
339		|| fBarView->AppCanHandleTypes(Signature());
340	if (_IsSelected() && IsEnabled() && canHandle)
341		menu->SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
342			B_HIGHLIGHT_BACKGROUND_TINT));
343	else
344		menu->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
345
346	if (IsSelected())
347		menu->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
348	else
349		menu->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
350
351	menu->MovePenBy(0, fLabelAscent);
352
353	// draw label
354	if (!static_cast<TBarApp*>(be_app)->Settings()->hideLabels) {
355		float labelWidth = menu->StringWidth(Label());
356		BPoint penLocation = menu->PenLocation();
357		// truncate to max width
358		float offset = penLocation.x - frame.left;
359		menu->DrawString(Label(labelWidth + offset));
360	}
361
362	// draw expander arrow
363	if (fBarView->Vertical()
364		&& static_cast<TBarApp*>(be_app)->Settings()->superExpando
365		&& fBarView->ExpandoState()) {
366		DrawExpanderArrow();
367	}
368}
369
370
371void
372TTeamMenuItem::DrawExpanderArrow()
373{
374	BRect frame = Frame();
375	BRect rect(0.0f, 0.0f, kSwitchWidth, sHPad + 2.0f);
376	rect.OffsetTo(BPoint(frame.right - rect.Width(),
377		ContentLocation().y + ((frame.Height() - rect.Height()) / 2)));
378
379	float colorTint = B_DARKEN_3_TINT;
380	rgb_color bgColor = ui_color(B_MENU_BACKGROUND_COLOR);
381	if (bgColor.red + bgColor.green + bgColor.blue <= 128 * 3)
382		colorTint = B_LIGHTEN_2_TINT;
383
384	be_control_look->DrawArrowShape(Menu(), rect, Menu()->Frame(),
385		bgColor, fArrowDirection, 0, colorTint);
386}
387
388
389void
390TTeamMenuItem::ToggleExpandState(bool resizeWindow)
391{
392	fExpanded = !fExpanded;
393	fArrowDirection = fExpanded ? BControlLook::B_DOWN_ARROW
394		: BControlLook::B_RIGHT_ARROW;
395
396	if (fExpanded) {
397		// Populate Menu() with the stuff from SubMenu().
398		TWindowMenu* sub = (static_cast<TWindowMenu*>(Submenu()));
399		if (sub != NULL) {
400			// force the menu to update it's contents.
401			bool locked = sub->LockLooper();
402				// if locking the looper failed, the menu is just not visible
403			sub->AttachedToWindow();
404			if (locked)
405				sub->UnlockLooper();
406
407			if (sub->CountItems() > 1) {
408				TExpandoMenuBar* parent = static_cast<TExpandoMenuBar*>(Menu());
409				int myindex = parent->IndexOf(this) + 1;
410
411				TWindowMenuItem* windowItem = NULL;
412				int32 childIndex = 0;
413				int32 totalChildren = sub->CountItems() - 4;
414					// hide, show, close, separator.
415				for (; childIndex < totalChildren; childIndex++) {
416					windowItem = static_cast<TWindowMenuItem*>
417						(sub->RemoveItem((int32)0));
418					parent->AddItem(windowItem, myindex + childIndex);
419					windowItem->SetExpanded(true);
420				}
421				sub->SetExpanded(true, myindex + childIndex);
422
423				if (resizeWindow)
424					parent->SizeWindow(-1);
425			}
426		}
427	} else {
428		// Remove the goodies from the Menu() that should be in the SubMenu();
429		TWindowMenu* sub = static_cast<TWindowMenu*>(Submenu());
430		if (sub != NULL) {
431			TExpandoMenuBar* parent = static_cast<TExpandoMenuBar*>(Menu());
432
433			TWindowMenuItem* windowItem = NULL;
434			int32 childIndex = parent->IndexOf(this) + 1;
435			while (parent->SubmenuAt(childIndex) == NULL
436				&& childIndex < parent->CountItems()) {
437				windowItem = static_cast<TWindowMenuItem*>(
438					parent->RemoveItem(childIndex));
439				sub->AddItem(windowItem, 0);
440				windowItem->SetExpanded(false);
441			}
442			sub->SetExpanded(false, 0);
443
444			if (resizeWindow)
445				parent->SizeWindow(1);
446		}
447	}
448}
449
450
451TWindowMenuItem*
452TTeamMenuItem::ExpandedWindowItem(int32 id)
453{
454	if (!fExpanded) {
455		// Paranoia
456		return NULL;
457	}
458
459	TExpandoMenuBar* parent = static_cast<TExpandoMenuBar*>(Menu());
460	int childIndex = parent->IndexOf(this) + 1;
461
462	while (!parent->SubmenuAt(childIndex)
463		&& childIndex < parent->CountItems()) {
464		TWindowMenuItem* item
465			= static_cast<TWindowMenuItem*>(parent->ItemAt(childIndex));
466		if (item->ID() == id)
467			return item;
468
469		childIndex++;
470	}
471	return NULL;
472}
473
474
475BRect
476TTeamMenuItem::ExpanderBounds() const
477{
478	BRect bounds(Frame());
479	bounds.left = bounds.right - kSwitchWidth;
480	return bounds;
481}
482
483
484//	#pragma mark - Private methods
485
486
487void
488TTeamMenuItem::_Init(BList* team, BBitmap* icon, char* name, char* signature,
489	float width, float height)
490{
491	if (sHPad == 0.0f) {
492		// Initialize the padding values.
493		sHPad = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING);
494		sVPad = ceilf(be_control_look->ComposeSpacing(B_USE_SMALL_SPACING) / 4.0f);
495		sLabelOffset = ceilf((be_control_look->DefaultLabelSpacing() / 3.0f) * 4.0f);
496	}
497
498	fTeam = team;
499	fIcon = icon;
500	fSignature = signature;
501
502	if (name == NULL) {
503		char temp[32];
504		snprintf(temp, sizeof(temp), "team %ld", (addr_t)team->ItemAt(0));
505		name = strdup(temp);
506	}
507
508	SetLabel(name);
509
510	fOverrideWidth = width;
511	fOverrideHeight = height;
512
513	fBarView = static_cast<TBarApp*>(be_app)->BarView();
514
515	// use menu font (parent font not available yet)
516	menu_info info;
517	get_menu_info(&info);
518	BFont font;
519	font.SetFamilyAndStyle(info.f_family, info.f_style);
520	font.SetSize(info.font_size);
521	fLabelWidth = ceilf(font.StringWidth(name));
522	font_height fontHeight;
523	font.GetHeight(&fontHeight);
524	fLabelAscent = ceilf(fontHeight.ascent);
525	fLabelDescent = ceilf(fontHeight.descent + fontHeight.leading);
526	fLabelHeight = fLabelAscent + fLabelDescent;
527
528	fOverriddenSelected = false;
529
530	fExpanded = false;
531	fArrowDirection = BControlLook::B_RIGHT_ARROW;
532}
533
534
535bool
536TTeamMenuItem::_IsSelected() const
537{
538	return IsSelected() || fOverriddenSelected;
539}
540