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