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