xref: /haiku/src/apps/launchbox/PadView.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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 	return dynamic_cast<LaunchButton*>(ChildAt(index));
301 }
302 
303 
304 void
305 PadView::DisplayMenu(BPoint where, LaunchButton* button) const
306 {
307 	MainWindow* window = dynamic_cast<MainWindow*>(Window());
308 	if (window == NULL)
309 		return;
310 
311 	LaunchButton* nearestButton = button;
312 	if (!nearestButton) {
313 		// find the nearest button
314 		for (int32 i = 0; (nearestButton = ButtonAt(i)); i++) {
315 			if (nearestButton->Frame().top > where.y)
316 				break;
317 		}
318 	}
319 	BPopUpMenu* menu = new BPopUpMenu("launch popup", false, false);
320 	// add button
321 	BMessage* message = new BMessage(MSG_ADD_SLOT);
322 	message->AddPointer("be:source", (void*)nearestButton);
323 	BMenuItem* item = new BMenuItem("Add Button Here", message);
324 	item->SetTarget(window);
325 	menu->AddItem(item);
326 	// button options
327 	if (button) {
328 		// clear button
329 		message = new BMessage(MSG_CLEAR_SLOT);
330 		message->AddPointer("be:source", (void*)button);
331 		item = new BMenuItem("Clear Button", message);
332 		item->SetTarget(window);
333 		menu->AddItem(item);
334 		// remove button
335 		message = new BMessage(MSG_REMOVE_SLOT);
336 		message->AddPointer("be:source", (void*)button);
337 		item = new BMenuItem("Remove Button", message);
338 		item->SetTarget(window);
339 		menu->AddItem(item);
340 		// set button description
341 		if (button->Ref()) {
342 			message = new BMessage(MSG_SET_DESCRIPTION);
343 			message->AddPointer("be:source", (void*)button);
344 			item = new BMenuItem("Set Description"B_UTF8_ELLIPSIS, message);
345 			item->SetTarget(window);
346 			menu->AddItem(item);
347 		}
348 	}
349 	menu->AddSeparatorItem();
350 	// window settings
351 	BMenu* settingsM = new BMenu("Settings");
352 	settingsM->SetFont(be_plain_font);
353 
354 	const char* toggleLayoutLabel;
355 	if (fButtonLayout->Orientation() == B_HORIZONTAL)
356 		toggleLayoutLabel = "Vertical Layout";
357 	else
358 		toggleLayoutLabel = "Horizontal Layout";
359 	item = new BMenuItem(toggleLayoutLabel, new BMessage(MSG_TOGGLE_LAYOUT));
360 	item->SetTarget(this);
361 	settingsM->AddItem(item);
362 
363 	BMenu* iconSizeM = new BMenu("Icon size");
364 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); i++) {
365 		uint32 iconSize = kIconSizes[i];
366 		message = new BMessage(MSG_SET_ICON_SIZE);
367 		message->AddInt32("size", iconSize);
368 		BString label;
369 		label << iconSize << " x " << iconSize;
370 		item = new BMenuItem(label.String(), message);
371 		item->SetTarget(this);
372 		item->SetMarked(IconSize() == iconSize);
373 		iconSizeM->AddItem(item);
374 	}
375 	settingsM->AddItem(iconSizeM);
376 
377 	item = new BMenuItem("Ignore Double-click",
378 		new BMessage(MSG_SET_IGNORE_DOUBLECLICK));
379 	item->SetTarget(this);
380 	item->SetMarked(IgnoreDoubleClick());
381 	settingsM->AddItem(item);
382 
383 	uint32 what = window->Look() == B_BORDERED_WINDOW_LOOK ? MSG_SHOW_BORDER : MSG_HIDE_BORDER;
384 	item = new BMenuItem("Show Window Border", new BMessage(what));
385 	item->SetTarget(window);
386 	item->SetMarked(what == MSG_HIDE_BORDER);
387 	settingsM->AddItem(item);
388 
389 	item = new BMenuItem("Auto Raise", new BMessage(MSG_TOGGLE_AUTORAISE));
390 	item->SetTarget(window);
391 	item->SetMarked(window->AutoRaise());
392 	settingsM->AddItem(item);
393 
394 	item = new BMenuItem("Show On All Workspaces", new BMessage(MSG_SHOW_ON_ALL_WORKSPACES));
395 	item->SetTarget(window);
396 	item->SetMarked(window->ShowOnAllWorkspaces());
397 	settingsM->AddItem(item);
398 
399 	menu->AddItem(settingsM);
400 
401 	menu->AddSeparatorItem();
402 
403 	// pad commands
404 	BMenu* padM = new BMenu("Pad");
405 	padM->SetFont(be_plain_font);
406 	// new pad
407 	item = new BMenuItem("New", new BMessage(MSG_ADD_WINDOW));
408 	item->SetTarget(be_app);
409 	padM->AddItem(item);
410 	// new pad
411 	item = new BMenuItem("Clone", new BMessage(MSG_ADD_WINDOW));
412 	item->SetTarget(window);
413 	padM->AddItem(item);
414 	padM->AddSeparatorItem();
415 	// close
416 	item = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED));
417 	item->SetTarget(window);
418 	padM->AddItem(item);
419 	menu->AddItem(padM);
420 	// app commands
421 	BMenu* appM = new BMenu("LaunchBox");
422 	appM->SetFont(be_plain_font);
423 	// about
424 	item = new BMenuItem("About", new BMessage(B_ABOUT_REQUESTED));
425 	item->SetTarget(be_app);
426 	appM->AddItem(item);
427 	// quit
428 	item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED));
429 	item->SetTarget(be_app);
430 	appM->AddItem(item);
431 	menu->AddItem(appM);
432 	// finish popup
433 	menu->SetAsyncAutoDestruct(true);
434 	menu->SetFont(be_plain_font);
435 	where = ConvertToScreen(where);
436 	BRect mouseRect(where, where);
437 	mouseRect.InsetBy(-4.0, -4.0);
438 	menu->Go(where, true, false, mouseRect, true);
439 }
440 
441 
442 void
443 PadView::SetOrientation(enum orientation orientation)
444 {
445 	if (orientation == B_VERTICAL) {
446 		fButtonLayout->SetInsets(2, 7, 2, 2);
447 		fButtonLayout->SetOrientation(B_VERTICAL);
448 	} else {
449 		fButtonLayout->SetInsets(7, 2, 2, 2);
450 		fButtonLayout->SetOrientation(B_HORIZONTAL);
451 	}
452 	_NotifySettingsChanged();
453 }
454 
455 
456 enum orientation
457 PadView::Orientation() const
458 {
459 	return fButtonLayout->Orientation();
460 }
461 
462 
463 void
464 PadView::SetIconSize(uint32 size)
465 {
466 	if (size == fIconSize)
467 		return;
468 
469 	fIconSize = size;
470 
471 	for (int32 i = 0; LaunchButton* button = ButtonAt(i); i++)
472 		button->SetIconSize(fIconSize);
473 
474 	_NotifySettingsChanged();
475 }
476 
477 
478 uint32
479 PadView::IconSize() const
480 {
481 	return fIconSize;
482 }
483 
484 
485 void
486 PadView::SetIgnoreDoubleClick(bool refuse)
487 {
488 	LaunchButton::SetIgnoreDoubleClick(refuse);
489 
490 	_NotifySettingsChanged();
491 }
492 
493 
494 bool
495 PadView::IgnoreDoubleClick() const
496 {
497 	return LaunchButton::IgnoreDoubleClick();
498 }
499 
500 
501 void
502 PadView::_NotifySettingsChanged()
503 {
504 	be_app->PostMessage(MSG_SETTINGS_CHANGED);
505 }
506 
507