1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35
36#include "DeskWindow.h"
37
38#include <Catalog.h>
39#include <Debug.h>
40#include <FindDirectory.h>
41#include <Locale.h>
42#include <Messenger.h>
43#include <NodeMonitor.h>
44#include <Path.h>
45#include <PathFinder.h>
46#include <PathMonitor.h>
47#include <PopUpMenu.h>
48#include <Resources.h>
49#include <Screen.h>
50#include <String.h>
51#include <StringList.h>
52#include <Volume.h>
53#include <WindowPrivate.h>
54
55#include <fcntl.h>
56#include <unistd.h>
57
58#include "Attributes.h"
59#include "AutoLock.h"
60#include "BackgroundImage.h"
61#include "Commands.h"
62#include "FSUtils.h"
63#include "IconMenuItem.h"
64#include "KeyInfos.h"
65#include "MountMenu.h"
66#include "PoseView.h"
67#include "Tracker.h"
68#include "TemplatesMenu.h"
69
70
71const char* kShelfPath = "tracker_shelf";
72	// replicant support
73
74const char* kShortcutsSettings = "shortcuts_settings";
75const char* kDefaultShortcut = "BEOS:default_shortcut";
76const uint32 kDefaultModifiers = B_OPTION_KEY | B_COMMAND_KEY;
77
78
79static struct AddOnShortcut*
80MatchOne(struct AddOnShortcut* item, void* castToName)
81{
82	if (strcmp(item->model->Name(), (const char*)castToName) == 0) {
83		// found match, bail out
84		return item;
85	}
86
87	return 0;
88}
89
90
91static void
92AddOneShortcut(Model* model, char key, uint32 modifiers, BDeskWindow* window)
93{
94	if (key == '\0')
95		return;
96
97	BMessage* runAddOn = new BMessage(kLoadAddOn);
98	runAddOn->AddRef("refs", model->EntryRef());
99	window->AddShortcut(key, modifiers, runAddOn);
100}
101
102
103static struct AddOnShortcut*
104RevertToDefault(struct AddOnShortcut* item, void* castToWindow)
105{
106	if (item->key != item->defaultKey || item->modifiers != kDefaultModifiers) {
107		BDeskWindow* window = static_cast<BDeskWindow*>(castToWindow);
108		if (window != NULL) {
109			window->RemoveShortcut(item->key, item->modifiers);
110			item->key = item->defaultKey;
111			item->modifiers = kDefaultModifiers;
112			AddOneShortcut(item->model, item->key, item->modifiers, window);
113		}
114	}
115
116	return 0;
117}
118
119
120static struct AddOnShortcut*
121FindElement(struct AddOnShortcut* item, void* castToOther)
122{
123	Model* other = static_cast<Model*>(castToOther);
124	if (*item->model->EntryRef() == *other->EntryRef())
125		return item;
126
127	return 0;
128}
129
130
131static void
132LoadAddOnDir(BDirectory directory, BDeskWindow* window,
133	LockingList<AddOnShortcut>* list)
134{
135	BEntry entry;
136	while (directory.GetNextEntry(&entry) == B_OK) {
137		Model* model = new Model(&entry);
138		if (model->InitCheck() == B_OK && model->IsSymLink()) {
139			// resolve symlinks
140			Model* resolved = new Model(model->EntryRef(), true, true);
141			if (resolved->InitCheck() == B_OK)
142				model->SetLinkTo(resolved);
143			else
144				delete resolved;
145		}
146		if (model->InitCheck() != B_OK
147			|| !model->ResolveIfLink()->IsExecutable()) {
148			delete model;
149			continue;
150		}
151
152		char* name = strdup(model->Name());
153		if (!list->EachElement(MatchOne, name)) {
154			struct AddOnShortcut* item = new struct AddOnShortcut;
155			item->model = model;
156
157			BResources resources(model->ResolveIfLink()->EntryRef());
158			size_t size;
159			char* shortcut = (char*)resources.LoadResource(B_STRING_TYPE,
160				kDefaultShortcut, &size);
161			if (shortcut == NULL || strlen(shortcut) > 1)
162				item->key = '\0';
163			else
164				item->key = shortcut[0];
165			AddOneShortcut(model, item->key, kDefaultModifiers, window);
166			item->defaultKey = item->key;
167			item->modifiers = kDefaultModifiers;
168			list->AddItem(item);
169		}
170		free(name);
171	}
172
173	node_ref nodeRef;
174	directory.GetNodeRef(&nodeRef);
175
176	TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window);
177}
178
179
180// #pragma mark - BDeskWindow
181
182
183#undef B_TRANSLATION_CONTEXT
184#define B_TRANSLATION_CONTEXT "DeskWindow"
185
186
187BDeskWindow::BDeskWindow(LockingList<BWindow>* windowList, uint32 openFlags)
188	:
189	BContainerWindow(windowList, openFlags,
190		kDesktopWindowLook, kDesktopWindowFeel,
191		B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK
192			| B_NOT_ZOOMABLE | B_NOT_CLOSABLE | B_NOT_MINIMIZABLE
193			| B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS,
194		B_ALL_WORKSPACES, false, true),
195	fDeskShelf(NULL),
196	fNodeRef(NULL),
197	fShortcutsSettings(NULL)
198{
199	// Add icon view switching shortcuts. These are displayed in the context
200	// menu, although they obviously don't work from those menu items.
201	BMessage* message = new BMessage(kIconMode);
202	AddShortcut('1', B_COMMAND_KEY, message, PoseView());
203
204	message = new BMessage(kMiniIconMode);
205	AddShortcut('2', B_COMMAND_KEY, message, PoseView());
206
207	message = new BMessage(kIconMode);
208	message->AddInt32("scale", 1);
209	AddShortcut('+', B_COMMAND_KEY, message, PoseView());
210
211	message = new BMessage(kIconMode);
212	message->AddInt32("scale", 0);
213	AddShortcut('-', B_COMMAND_KEY, message, PoseView());
214}
215
216
217BDeskWindow::~BDeskWindow()
218{
219	SaveDesktopPoseLocations();
220		// explicit call to SavePoseLocations so that extended pose info
221		// gets committed properly
222	PoseView()->DisableSaveLocation();
223		// prevent double-saving, this would slow down quitting
224	PoseView()->StopSettingsWatch();
225	stop_watching(this);
226}
227
228
229void
230BDeskWindow::Init(const BMessage*)
231{
232	// Set the size of the screen before calling the container window's
233	// Init() because it will add volume poses to this window and
234	// they will be clipped otherwise
235
236	BScreen screen(this);
237	fOldFrame = screen.Frame();
238
239	ResizeTo(fOldFrame.Width(), fOldFrame.Height());
240
241	InitKeyIndices();
242	InitAddOnsList(false);
243	ApplyShortcutPreferences(false);
244
245	_inherited::Init();
246
247	entry_ref ref;
248	BPath path;
249	if (!BootedInSafeMode() && FSFindTrackerSettingsDir(&path) == B_OK) {
250		path.Append(kShelfPath);
251		close(open(path.Path(), O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR
252			| S_IRGRP | S_IROTH));
253		if (get_ref_for_path(path.Path(), &ref) == B_OK)
254			fDeskShelf = new BShelf(&ref, PoseView());
255
256		if (fDeskShelf != NULL)
257			fDeskShelf->SetDisplaysZombies(true);
258	}
259}
260
261
262void
263BDeskWindow::InitAddOnsList(bool update)
264{
265	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
266	if (lock.IsLocked()) {
267		if (update) {
268			for (int i = fAddOnsList->CountItems() - 1; i >= 0; i--) {
269				AddOnShortcut* item = fAddOnsList->ItemAt(i);
270				RemoveShortcut(item->key, B_OPTION_KEY | B_COMMAND_KEY);
271			}
272			fAddOnsList->MakeEmpty(true);
273		}
274
275		BStringList addOnPaths;
276		BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker",
277			addOnPaths);
278		int32 count = addOnPaths.CountStrings();
279		for (int32 i = 0; i < count; i++) {
280			LoadAddOnDir(BDirectory(addOnPaths.StringAt(i)), this,
281				fAddOnsList);
282		}
283	}
284}
285
286
287void
288BDeskWindow::ApplyShortcutPreferences(bool update)
289{
290	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
291	if (lock.IsLocked()) {
292		if (!update) {
293			BPath path;
294			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
295				BPathMonitor::StartWatching(path.Path(),
296					B_WATCH_STAT | B_WATCH_FILES_ONLY, this);
297				path.Append(kShortcutsSettings);
298				fShortcutsSettings = new char[strlen(path.Path()) + 1];
299				strcpy(fShortcutsSettings, path.Path());
300			}
301		}
302
303		fAddOnsList->EachElement(RevertToDefault, this);
304
305		BFile shortcutSettings(fShortcutsSettings, B_READ_ONLY);
306		BMessage fileMsg;
307		if (shortcutSettings.InitCheck() != B_OK
308			|| fileMsg.Unflatten(&shortcutSettings) != B_OK) {
309			fNodeRef = NULL;
310			return;
311		}
312		shortcutSettings.GetNodeRef(fNodeRef);
313
314		int32 i = 0;
315		BMessage message;
316		while (fileMsg.FindMessage("spec", i++, &message) == B_OK) {
317			int32 key;
318			if (message.FindInt32("key", &key) == B_OK) {
319				// only handle shortcuts referring add-ons
320				BString command;
321				if (message.FindString("command", &command) != B_OK)
322					continue;
323
324				bool isInAddOns = false;
325
326				BStringList addOnPaths;
327				BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY,
328					"Tracker/", addOnPaths);
329				for (int32 i = 0; i < addOnPaths.CountStrings(); i++) {
330					if (command.StartsWith(addOnPaths.StringAt(i))) {
331						isInAddOns = true;
332						break;
333					}
334				}
335
336				if (!isInAddOns)
337					continue;
338
339				BEntry entry(command);
340				if (entry.InitCheck() != B_OK)
341					continue;
342
343				const char* shortcut = GetKeyName(key);
344				if (strlen(shortcut) != 1)
345					continue;
346
347				uint32 modifiers = B_COMMAND_KEY;
348					// it's required by interface kit to at least
349					// have B_COMMAND_KEY
350				int32 value;
351				if (message.FindInt32("mcidx", 0, &value) == B_OK)
352					modifiers |= (value != 0 ? B_SHIFT_KEY : 0);
353
354				if (message.FindInt32("mcidx", 1, &value) == B_OK)
355					modifiers |= (value != 0 ? B_CONTROL_KEY : 0);
356
357				if (message.FindInt32("mcidx", 3, &value) == B_OK)
358					modifiers |= (value != 0 ? B_OPTION_KEY : 0);
359
360				Model model(&entry);
361				AddOnShortcut* item = fAddOnsList->EachElement(FindElement,
362					&model);
363				if (item != NULL) {
364					if (item->key != '\0')
365						RemoveShortcut(item->key, item->modifiers);
366
367					item->key = shortcut[0];
368					item->modifiers = modifiers;
369					AddOneShortcut(&model, item->key, item->modifiers, this);
370				}
371			}
372		}
373	}
374}
375
376
377void
378BDeskWindow::Quit()
379{
380	if (fNavigationItem != NULL) {
381		// this duplicates BContainerWindow::Quit because
382		// fNavigationItem can be part of fTrashContextMenu
383		// and would get deleted with it
384		BMenu* menu = fNavigationItem->Menu();
385		if (menu != NULL)
386			menu->RemoveItem(fNavigationItem);
387
388		delete fNavigationItem;
389		fNavigationItem = NULL;
390	}
391
392	fAddOnsList->MakeEmpty(true);
393	delete fAddOnsList;
394
395	delete fDeskShelf;
396
397	// inherited will clean up the rest
398	_inherited::Quit();
399}
400
401
402BPoseView*
403BDeskWindow::NewPoseView(Model* model, uint32 viewMode)
404{
405	return new DesktopPoseView(model, viewMode);
406}
407
408
409void
410BDeskWindow::CreatePoseView(Model* model)
411{
412	fPoseView = NewPoseView(model, kIconMode);
413	fPoseView->SetIconMapping(false);
414	fPoseView->SetEnsurePosesVisible(true);
415	fPoseView->SetAutoScroll(false);
416
417	BScreen screen(this);
418	rgb_color desktopColor = screen.DesktopColor();
419	if (desktopColor.alpha != 255) {
420		desktopColor.alpha = 255;
421#if B_BEOS_VERSION > B_BEOS_VERSION_5
422		// This call seems to have the power to cause R5 to freeze!
423		// Please report if commenting this out helped or helped not
424		// on your system
425		screen.SetDesktopColor(desktopColor);
426#endif
427	}
428
429	fPoseView->SetViewColor(desktopColor);
430	fPoseView->SetLowColor(desktopColor);
431
432	fPoseView->SetResizingMode(B_FOLLOW_ALL);
433	fPoseView->ResizeTo(Bounds().Size());
434	AddChild(fPoseView);
435
436	PoseView()->StartSettingsWatch();
437}
438
439
440void
441BDeskWindow::AddWindowContextMenus(BMenu* menu)
442{
443	TemplatesMenu* tempateMenu = new TemplatesMenu(PoseView(),
444		B_TRANSLATE("New"));
445
446	menu->AddItem(tempateMenu);
447	tempateMenu->SetTargetForItems(PoseView());
448	tempateMenu->SetFont(be_plain_font);
449
450	menu->AddSeparatorItem();
451
452	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
453	BMenuItem* item;
454
455	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
456	BMessage* message;
457
458	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
459		uint32 iconSize = kIconSizes[i];
460		message = new BMessage(kIconMode);
461		message->AddInt32("size", iconSize);
462		BString label;
463		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" �� %" B_PRId32,
464			"The '��' is the Unicode multiplication sign U+00D7"),
465			iconSize, iconSize);
466		item = new BMenuItem(label, message);
467		item->SetMarked(PoseView()->IconSizeInt() == iconSize);
468		item->SetTarget(PoseView());
469		iconSizeMenu->AddItem(item);
470	}
471
472	iconSizeMenu->AddSeparatorItem();
473
474	message = new BMessage(kIconMode);
475	message->AddInt32("scale", 0);
476	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
477	item->SetTarget(PoseView());
478	iconSizeMenu->AddItem(item);
479
480	message = new BMessage(kIconMode);
481	message->AddInt32("scale", 1);
482	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
483	item->SetTarget(PoseView());
484	iconSizeMenu->AddItem(item);
485
486	// A sub menu where the super item can be invoked.
487	menu->AddItem(iconSizeMenu);
488	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
489	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
490	iconSizeMenu->Superitem()->SetTarget(PoseView());
491	iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode);
492
493	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
494		new BMessage(kMiniIconMode), '2');
495	item->SetMarked(PoseView()->ViewMode() == kMiniIconMode);
496	menu->AddItem(item);
497
498	menu->AddSeparatorItem();
499
500#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
501	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
502		new BMessage(B_PASTE), 'V');
503	menu->AddItem(pasteItem);
504	menu->AddSeparatorItem();
505#endif
506	menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"),
507		new BMessage(kCleanup), 'K'));
508	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
509		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
510	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
511		new BMessage(B_SELECT_ALL), 'A'));
512
513	menu->AddSeparatorItem();
514	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
515
516	menu->AddSeparatorItem();
517	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
518
519	// target items as needed
520	menu->SetTargetForItems(PoseView());
521#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
522	pasteItem->SetTarget(this);
523#endif
524}
525
526
527void
528BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
529{
530	if (fBackgroundImage)
531		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
532}
533
534
535void
536BDeskWindow::SaveDesktopPoseLocations()
537{
538	PoseView()->SavePoseLocations(&fOldFrame);
539}
540
541
542void
543BDeskWindow::ScreenChanged(BRect frame, color_space space)
544{
545	bool frameChanged = (frame != fOldFrame);
546
547	SaveDesktopPoseLocations();
548	fOldFrame = frame;
549	ResizeTo(frame.Width(), frame.Height());
550
551	if (fBackgroundImage)
552		fBackgroundImage->ScreenChanged(frame, space);
553
554	PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0);
555		// if frame changed, pass new frame so that icons can
556		// get rearranged based on old pose info for the frame
557}
558
559
560void
561BDeskWindow::UpdateDesktopBackgroundImages()
562{
563	WindowStateNodeOpener opener(this, false);
564	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
565		opener.Node(), true, PoseView());
566}
567
568
569void
570BDeskWindow::Show()
571{
572	if (fBackgroundImage)
573		fBackgroundImage->Show(PoseView(), current_workspace());
574
575	PoseView()->CheckPoseVisibility();
576
577	_inherited::Show();
578}
579
580
581bool
582BDeskWindow::ShouldAddScrollBars() const
583{
584	return false;
585}
586
587
588bool
589BDeskWindow::ShouldAddMenus() const
590{
591	return false;
592}
593
594
595bool
596BDeskWindow::ShouldAddContainerView() const
597{
598	return false;
599}
600
601
602void
603BDeskWindow::MessageReceived(BMessage* message)
604{
605	if (message->WasDropped()) {
606		const rgb_color* color;
607		ssize_t size;
608		// handle "roColour"-style color drops
609		if (message->FindData("RGBColor", 'RGBC',
610			(const void**)&color, &size) == B_OK) {
611			BScreen(this).SetDesktopColor(*color);
612			PoseView()->SetViewColor(*color);
613			PoseView()->SetLowColor(*color);
614
615			// Notify the backgrounds app that the background changed
616			status_t initStatus;
617			BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1,
618				&initStatus);
619			if (initStatus == B_OK)
620				messenger.SendMessage(message);
621
622			return;
623		}
624	}
625
626	switch (message->what) {
627		case B_PATH_MONITOR:
628		{
629			const char* path = "";
630			if (!(message->FindString("path", &path) == B_OK
631					&& strcmp(path, fShortcutsSettings) == 0)) {
632
633				dev_t device;
634				ino_t node;
635				if (fNodeRef == NULL
636					|| message->FindInt32("device", &device) != B_OK
637					|| message->FindInt64("node", &node) != B_OK
638					|| device != fNodeRef->device
639					|| node != fNodeRef->node)
640					break;
641			}
642			ApplyShortcutPreferences(true);
643			break;
644		}
645		case B_NODE_MONITOR:
646			PRINT(("will update addon shortcuts\n"));
647			InitAddOnsList(true);
648			ApplyShortcutPreferences(true);
649			break;
650
651		default:
652			_inherited::MessageReceived(message);
653			break;
654	}
655}
656