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