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
31static bigtime_t sActivationDelay = 40000;
32static const uint32 kIconSizes[] = { 16, 20, 24, 32, 40, 48, 64, 96, 128 };
33
34
35enum {
36	MSG_TOGGLE_LAYOUT			= 'tgll',
37	MSG_SET_ICON_SIZE			= 'stis',
38	MSG_SET_IGNORE_DOUBLECLICK	= 'strd'
39};
40
41
42PadView::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
58PadView::~PadView()
59{
60}
61
62
63void
64PadView::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
135void
136PadView::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
166void
167PadView::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
219void
220PadView::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
234void
235PadView::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
277void
278PadView::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
291bool
292PadView::RemoveButton(LaunchButton* button)
293{
294	bool result = fButtonLayout->RemoveView(button);
295	if (result)
296		_NotifySettingsChanged();
297	return result;
298}
299
300
301LaunchButton*
302PadView::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
311void
312PadView::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
461void
462PadView::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
475enum orientation
476PadView::Orientation() const
477{
478	return fButtonLayout->Orientation();
479}
480
481
482void
483PadView::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
497uint32
498PadView::IconSize() const
499{
500	return fIconSize;
501}
502
503
504void
505PadView::SetIgnoreDoubleClick(bool refuse)
506{
507	LaunchButton::SetIgnoreDoubleClick(refuse);
508
509	_NotifySettingsChanged();
510}
511
512
513bool
514PadView::IgnoreDoubleClick() const
515{
516	return LaunchButton::IgnoreDoubleClick();
517}
518
519
520void
521PadView::_NotifySettingsChanged()
522{
523	be_app->PostMessage(MSG_SETTINGS_CHANGED);
524}
525