xref: /haiku/src/apps/launchbox/PadView.cpp (revision 526e86ac79f9899c43e2def55f82754a5c449f8a)
1 /*
2  * Copyright 2006-2009, Stephan Aßmus <superstippi@gmx.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "PadView.h"
7 
8 #include <stdio.h>
9 
10 #include <Application.h>
11 #include <GroupLayout.h>
12 #include <MenuItem.h>
13 #include <Message.h>
14 #include <PopUpMenu.h>
15 #include <Region.h>
16 #include <Screen.h>
17 #include <SpaceLayoutItem.h>
18 
19 #include "LaunchButton.h"
20 #include "MainWindow.h"
21 
22 
23 static bigtime_t sActivationDelay = 40000;
24 static const uint32 kIconSizes[] = { 16, 20, 24, 32, 40, 48, 64 };
25 
26 
27 enum {
28 	MSG_TOGGLE_LAYOUT			= 'tgll',
29 	MSG_SET_ICON_SIZE			= 'stis',
30 	MSG_SET_IGNORE_DOUBLECLICK	= 'strd'
31 };
32 
33 
34 PadView::PadView(const char* name)
35 	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE, NULL),
36 	  fDragging(false),
37 	  fClickTime(0),
38 	  fButtonLayout(new BGroupLayout(B_VERTICAL, 4)),
39 	  fIconSize(DEFAULT_ICON_SIZE)
40 {
41 	SetViewColor(B_TRANSPARENT_32_BIT);
42 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
43 	get_click_speed(&sActivationDelay);
44 
45 	fButtonLayout->SetInsets(2, 7, 2, 2);
46 	SetLayout(fButtonLayout);
47 }
48 
49 
50 PadView::~PadView()
51 {
52 }
53 
54 
55 void
56 PadView::Draw(BRect updateRect)
57 {
58 	rgb_color background = LowColor();
59 	rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT);
60 	rgb_color shadow = tint_color(background, B_DARKEN_2_TINT);
61 	BRect r(Bounds());
62 	BeginLineArray(4);
63 		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), light);
64 		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), light);
65 		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), shadow);
66 		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), shadow);
67 	EndLineArray();
68 	r.InsetBy(1.0, 1.0);
69 	StrokeRect(r, B_SOLID_LOW);
70 	r.InsetBy(1.0, 1.0);
71 	// dots along top
72 	BPoint dot = r.LeftTop();
73 	int32 current;
74 	int32 stop;
75 	BPoint offset;
76 	BPoint next;
77 	if (Orientation() == B_VERTICAL) {
78 		current = (int32)dot.x;
79 		stop = (int32)r.right;
80 		offset = BPoint(0, 1);
81 		next = BPoint(1, -4);
82 		r.top += 5.0;
83 	} else {
84 		current = (int32)dot.y;
85 		stop = (int32)r.bottom;
86 		offset = BPoint(1, 0);
87 		next = BPoint(-4, 1);
88 		r.left += 5.0;
89 	}
90 	int32 num = 1;
91 	while (current <= stop) {
92 		rgb_color col1;
93 		rgb_color col2;
94 		switch (num) {
95 			case 1:
96 				col1 = shadow;
97 				col2 = background;
98 				break;
99 			case 2:
100 				col1 = background;
101 				col2 = light;
102 				break;
103 			case 3:
104 				col1 = background;
105 				col2 = background;
106 				num = 0;
107 				break;
108 		}
109 		SetHighColor(col1);
110 		StrokeLine(dot, dot, B_SOLID_HIGH);
111 		SetHighColor(col2);
112 		dot += offset;
113 		StrokeLine(dot, dot, B_SOLID_HIGH);
114 		dot += offset;
115 		StrokeLine(dot, dot, B_SOLID_LOW);
116 		dot += offset;
117 		SetHighColor(col1);
118 		StrokeLine(dot, dot, B_SOLID_HIGH);
119 		dot += offset;
120 		SetHighColor(col2);
121 		StrokeLine(dot, dot, B_SOLID_HIGH);
122 		// next pixel
123 		num++;
124 		dot += next;
125 		current++;
126 	}
127 	FillRect(r, B_SOLID_LOW);
128 }
129 
130 
131 void
132 PadView::MessageReceived(BMessage* message)
133 {
134 	switch (message->what) {
135 		case MSG_TOGGLE_LAYOUT:
136 			if (fButtonLayout->Orientation() == B_HORIZONTAL) {
137 				fButtonLayout->SetInsets(2, 7, 2, 2);
138 				fButtonLayout->SetOrientation(B_VERTICAL);
139 			} else {
140 				fButtonLayout->SetInsets(7, 2, 2, 2);
141 				fButtonLayout->SetOrientation(B_HORIZONTAL);
142 			}
143 			break;
144 
145 		case MSG_SET_ICON_SIZE:
146 			uint32 size;
147 			if (message->FindInt32("size", (int32*)&size) == B_OK)
148 				SetIconSize(size);
149 			break;
150 
151 		case MSG_SET_IGNORE_DOUBLECLICK:
152 			SetIgnoreDoubleClick(!IgnoreDoubleClick());
153 			break;
154 
155 		default:
156 			BView::MessageReceived(message);
157 			break;
158 	}
159 }
160 
161 
162 void
163 PadView::MouseDown(BPoint where)
164 {
165 	BWindow* window = Window();
166 	if (window == NULL)
167 		return;
168 
169 	BRegion region;
170 	GetClippingRegion(&region);
171 	if (!region.Contains(where))
172 		return;
173 
174 	bool handle = true;
175 	for (int32 i = 0; BView* child = ChildAt(i); i++) {
176 		if (child->Frame().Contains(where)) {
177 			handle = false;
178 			break;
179 		}
180 	}
181 	if (!handle)
182 		return;
183 
184 	BMessage* message = window->CurrentMessage();
185 	if (message == NULL)
186 		return;
187 
188 	uint32 buttons;
189 	message->FindInt32("buttons", (int32*)&buttons);
190 	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
191 		BRect r = Bounds();
192 		r.InsetBy(2.0, 2.0);
193 		r.top += 6.0;
194 		if (r.Contains(where)) {
195 			DisplayMenu(where);
196 		} else {
197 			// sends the window to the back
198 			window->Activate(false);
199 		}
200 	} else {
201 		if (system_time() - fClickTime < sActivationDelay) {
202 			window->Minimize(true);
203 			fClickTime = 0;
204 		} else {
205 			window->Activate();
206 			fDragOffset = ConvertToScreen(where) - window->Frame().LeftTop();
207 			fDragging = true;
208 			SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
209 			fClickTime = system_time();
210 		}
211 	}
212 }
213 
214 
215 void
216 PadView::MouseUp(BPoint where)
217 {
218 	if (BWindow* window = Window()) {
219 		uint32 buttons;
220 		window->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
221 		if (buttons & B_PRIMARY_MOUSE_BUTTON
222 			&& system_time() - fClickTime < sActivationDelay
223 			&& window->IsActive())
224 			window->Activate();
225 	}
226 	fDragging = false;
227 }
228 
229 
230 void
231 PadView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
232 {
233 	MainWindow* window = dynamic_cast<MainWindow*>(Window());
234 	if (window == NULL)
235 		return;
236 
237 	if (fDragging) {
238 		window->MoveTo(ConvertToScreen(where) - fDragOffset);
239 	} else if (window->AutoRaise()) {
240 		where = ConvertToScreen(where);
241 		BScreen screen(window);
242 		BRect frame = screen.Frame();
243 		BRect windowFrame = window->Frame();
244 		if (where.x == frame.left || where.x == frame.right
245 			|| where.y == frame.top || where.y == frame.bottom) {
246 			BPoint position = window->ScreenPosition();
247 			bool raise = false;
248 			if (fabs(0.5 - position.x) > fabs(0.5 - position.y)) {
249 				// left or right border
250 				if (where.y >= windowFrame.top
251 					&& where.y <= windowFrame.bottom) {
252 					if (position.x < 0.5 && where.x == frame.left)
253 						raise = true;
254 					else if (position.x > 0.5 && where.x == frame.right)
255 						raise = true;
256 				}
257 			} else {
258 				// top or bottom border
259 				if (where.x >= windowFrame.left && where.x <= windowFrame.right) {
260 					if (position.y < 0.5 && where.y == frame.top)
261 						raise = true;
262 					else if (position.y > 0.5 && where.y == frame.bottom)
263 						raise = true;
264 				}
265 			}
266 			if (raise)
267 				window->Activate();
268 		}
269 	}
270 }
271 
272 
273 void
274 PadView::AddButton(LaunchButton* button, LaunchButton* beforeButton)
275 {
276 	button->SetIconSize(fIconSize);
277 
278 	if (beforeButton)
279 		fButtonLayout->AddView(fButtonLayout->IndexOfView(beforeButton), button);
280 	else
281 		fButtonLayout->AddView(button);
282 
283 	_NotifySettingsChanged();
284 }
285 
286 
287 bool
288 PadView::RemoveButton(LaunchButton* button)
289 {
290 	bool result = fButtonLayout->RemoveView(button);
291 	if (result)
292 		_NotifySettingsChanged();
293 	return result;
294 }
295 
296 
297 LaunchButton*
298 PadView::ButtonAt(int32 index) const
299 {
300 	BLayoutItem* item = fButtonLayout->ItemAt(index);
301 	if (item == NULL)
302 		return NULL;
303 	return dynamic_cast<LaunchButton*>(item->View());
304 }
305 
306 
307 void
308 PadView::DisplayMenu(BPoint where, LaunchButton* button) const
309 {
310 	MainWindow* window = dynamic_cast<MainWindow*>(Window());
311 	if (window == NULL)
312 		return;
313 
314 	LaunchButton* nearestButton = button;
315 	if (!nearestButton) {
316 		// find the nearest button
317 		for (int32 i = 0; (nearestButton = ButtonAt(i)); i++) {
318 			if (nearestButton->Frame().top > where.y)
319 				break;
320 		}
321 	}
322 	BPopUpMenu* menu = new BPopUpMenu("launch popup", false, false);
323 	// add button
324 	BMessage* message = new BMessage(MSG_ADD_SLOT);
325 	message->AddPointer("be:source", (void*)nearestButton);
326 	BMenuItem* item = new BMenuItem("Add button here", message);
327 	item->SetTarget(window);
328 	menu->AddItem(item);
329 	// button options
330 	if (button) {
331 		// clear button
332 		message = new BMessage(MSG_CLEAR_SLOT);
333 		message->AddPointer("be:source", (void*)button);
334 		item = new BMenuItem("Clear button", message);
335 		item->SetTarget(window);
336 		menu->AddItem(item);
337 		// remove button
338 		message = new BMessage(MSG_REMOVE_SLOT);
339 		message->AddPointer("be:source", (void*)button);
340 		item = new BMenuItem("Remove button", message);
341 		item->SetTarget(window);
342 		menu->AddItem(item);
343 		// set button description
344 		if (button->Ref()) {
345 			message = new BMessage(MSG_SET_DESCRIPTION);
346 			message->AddPointer("be:source", (void*)button);
347 			item = new BMenuItem("Set description"B_UTF8_ELLIPSIS, message);
348 			item->SetTarget(window);
349 			menu->AddItem(item);
350 		}
351 	}
352 	menu->AddSeparatorItem();
353 	// window settings
354 	BMenu* settingsM = new BMenu("Settings");
355 	settingsM->SetFont(be_plain_font);
356 
357 	const char* toggleLayoutLabel;
358 	if (fButtonLayout->Orientation() == B_HORIZONTAL)
359 		toggleLayoutLabel = "Vertical layout";
360 	else
361 		toggleLayoutLabel = "Horizontal layout";
362 	item = new BMenuItem(toggleLayoutLabel, new BMessage(MSG_TOGGLE_LAYOUT));
363 	item->SetTarget(this);
364 	settingsM->AddItem(item);
365 
366 	BMenu* iconSizeM = new BMenu("Icon size");
367 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); i++) {
368 		uint32 iconSize = kIconSizes[i];
369 		message = new BMessage(MSG_SET_ICON_SIZE);
370 		message->AddInt32("size", iconSize);
371 		BString label;
372 		label << iconSize << " x " << iconSize;
373 		item = new BMenuItem(label.String(), message);
374 		item->SetTarget(this);
375 		item->SetMarked(IconSize() == iconSize);
376 		iconSizeM->AddItem(item);
377 	}
378 	settingsM->AddItem(iconSizeM);
379 
380 	item = new BMenuItem("Ignore double-click",
381 		new BMessage(MSG_SET_IGNORE_DOUBLECLICK));
382 	item->SetTarget(this);
383 	item->SetMarked(IgnoreDoubleClick());
384 	settingsM->AddItem(item);
385 
386 	uint32 what = window->Look() == B_BORDERED_WINDOW_LOOK ? MSG_SHOW_BORDER : MSG_HIDE_BORDER;
387 	item = new BMenuItem("Show window border", new BMessage(what));
388 	item->SetTarget(window);
389 	item->SetMarked(what == MSG_HIDE_BORDER);
390 	settingsM->AddItem(item);
391 
392 	item = new BMenuItem("Auto-raise", new BMessage(MSG_TOGGLE_AUTORAISE));
393 	item->SetTarget(window);
394 	item->SetMarked(window->AutoRaise());
395 	settingsM->AddItem(item);
396 
397 	item = new BMenuItem("Show on all workspaces", new BMessage(MSG_SHOW_ON_ALL_WORKSPACES));
398 	item->SetTarget(window);
399 	item->SetMarked(window->ShowOnAllWorkspaces());
400 	settingsM->AddItem(item);
401 
402 	menu->AddItem(settingsM);
403 
404 	menu->AddSeparatorItem();
405 
406 	// pad commands
407 	BMenu* padM = new BMenu("Pad");
408 	padM->SetFont(be_plain_font);
409 	// new pad
410 	item = new BMenuItem("New", new BMessage(MSG_ADD_WINDOW));
411 	item->SetTarget(be_app);
412 	padM->AddItem(item);
413 	// new pad
414 	item = new BMenuItem("Clone", new BMessage(MSG_ADD_WINDOW));
415 	item->SetTarget(window);
416 	padM->AddItem(item);
417 	padM->AddSeparatorItem();
418 	// close
419 	item = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED));
420 	item->SetTarget(window);
421 	padM->AddItem(item);
422 	menu->AddItem(padM);
423 	// app commands
424 	BMenu* appM = new BMenu("LaunchBox");
425 	appM->SetFont(be_plain_font);
426 	// about
427 	item = new BMenuItem("About", new BMessage(B_ABOUT_REQUESTED));
428 	item->SetTarget(be_app);
429 	appM->AddItem(item);
430 	// quit
431 	item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED));
432 	item->SetTarget(be_app);
433 	appM->AddItem(item);
434 	menu->AddItem(appM);
435 	// finish popup
436 	menu->SetAsyncAutoDestruct(true);
437 	menu->SetFont(be_plain_font);
438 	where = ConvertToScreen(where);
439 	BRect mouseRect(where, where);
440 	mouseRect.InsetBy(-4.0, -4.0);
441 	menu->Go(where, true, false, mouseRect, true);
442 }
443 
444 
445 void
446 PadView::SetOrientation(enum orientation orientation)
447 {
448 	if (orientation == B_VERTICAL) {
449 		fButtonLayout->SetInsets(2, 7, 2, 2);
450 		fButtonLayout->SetOrientation(B_VERTICAL);
451 	} else {
452 		fButtonLayout->SetInsets(7, 2, 2, 2);
453 		fButtonLayout->SetOrientation(B_HORIZONTAL);
454 	}
455 	_NotifySettingsChanged();
456 }
457 
458 
459 enum orientation
460 PadView::Orientation() const
461 {
462 	return fButtonLayout->Orientation();
463 }
464 
465 
466 void
467 PadView::SetIconSize(uint32 size)
468 {
469 	if (size == fIconSize)
470 		return;
471 
472 	fIconSize = size;
473 
474 	for (int32 i = 0; LaunchButton* button = ButtonAt(i); i++)
475 		button->SetIconSize(fIconSize);
476 
477 	_NotifySettingsChanged();
478 }
479 
480 
481 uint32
482 PadView::IconSize() const
483 {
484 	return fIconSize;
485 }
486 
487 
488 void
489 PadView::SetIgnoreDoubleClick(bool refuse)
490 {
491 	LaunchButton::SetIgnoreDoubleClick(refuse);
492 
493 	_NotifySettingsChanged();
494 }
495 
496 
497 bool
498 PadView::IgnoreDoubleClick() const
499 {
500 	return LaunchButton::IgnoreDoubleClick();
501 }
502 
503 
504 void
505 PadView::_NotifySettingsChanged()
506 {
507 	be_app->PostMessage(MSG_SETTINGS_CHANGED);
508 }
509 
510