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
103
104static struct AddonShortcut*
105RevertToDefault(struct AddonShortcut* item, void* castToWindow)
106{
107	if (item->key != item->defaultKey || item->modifiers != kDefaultModifiers) {
108		BDeskWindow* window = static_cast<BDeskWindow*>(castToWindow);
109		if (window != NULL) {
110			window->RemoveShortcut(item->key, item->modifiers);
111			item->key = item->defaultKey;
112			item->modifiers = kDefaultModifiers;
113			AddOneShortcut(item->model, item->key, item->modifiers, window);
114		}
115	}
116
117	return 0;
118}
119
120
121static struct AddonShortcut*
122FindElement(struct AddonShortcut* item, void* castToOther)
123{
124	Model* other = static_cast<Model*>(castToOther);
125	if (*item->model->EntryRef() == *other->EntryRef())
126		return item;
127
128	return 0;
129}
130
131
132static void
133LoadAddOnDir(BDirectory directory, BDeskWindow* window,
134	LockingList<AddonShortcut>* list)
135{
136	BEntry entry;
137	while (directory.GetNextEntry(&entry) == B_OK) {
138		Model* model = new Model(&entry);
139		if (model->InitCheck() == B_OK && model->IsSymLink()) {
140			// resolve symlinks
141			Model* resolved = new Model(model->EntryRef(), true, true);
142			if (resolved->InitCheck() == B_OK)
143				model->SetLinkTo(resolved);
144			else
145				delete resolved;
146		}
147		if (model->InitCheck() != B_OK
148			|| !model->ResolveIfLink()->IsExecutable()) {
149			delete model;
150			continue;
151		}
152
153		char* name = strdup(model->Name());
154		if (!list->EachElement(MatchOne, name)) {
155			struct AddonShortcut* item = new struct AddonShortcut;
156			item->model = model;
157
158			BResources resources(model->ResolveIfLink()->EntryRef());
159			size_t size;
160			char* shortcut = (char*)resources.LoadResource(B_STRING_TYPE,
161				kDefaultShortcut, &size);
162			if (shortcut == NULL || strlen(shortcut) > 1)
163				item->key = '\0';
164			else
165				item->key = shortcut[0];
166			AddOneShortcut(model, item->key, kDefaultModifiers, window);
167			item->defaultKey = item->key;
168			item->modifiers = kDefaultModifiers;
169			list->AddItem(item);
170		}
171		free(name);
172	}
173
174	node_ref nodeRef;
175	directory.GetNodeRef(&nodeRef);
176
177	TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window);
178}
179
180
181// #pragma mark - BDeskWindow
182
183
184#undef B_TRANSLATION_CONTEXT
185#define B_TRANSLATION_CONTEXT "DeskWindow"
186
187
188BDeskWindow::BDeskWindow(LockingList<BWindow>* windowList)
189	:
190	BContainerWindow(windowList, 0, kDesktopWindowLook,
191		kDesktopWindowFeel, 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, B_ALL_WORKSPACES,
194			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	PoseView()->SetShowHideSelection(false);
240	ResizeTo(fOldFrame.Width(), fOldFrame.Height());
241
242	InitKeyIndices();
243	InitAddonsList(false);
244	ApplyShortcutPreferences(false);
245
246	_inherited::Init();
247
248	entry_ref ref;
249	BPath path;
250	if (!BootedInSafeMode() && FSFindTrackerSettingsDir(&path) == B_OK) {
251		path.Append(kShelfPath);
252		close(open(path.Path(), O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR
253			| S_IRGRP | S_IROTH));
254		if (get_ref_for_path(path.Path(), &ref) == B_OK)
255			fDeskShelf = new BShelf(&ref, fPoseView);
256
257		if (fDeskShelf != NULL)
258			fDeskShelf->SetDisplaysZombies(true);
259	}
260}
261
262
263void
264BDeskWindow::InitAddonsList(bool update)
265{
266	AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
267	if (lock.IsLocked()) {
268		if (update) {
269			for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) {
270				AddonShortcut* item = fAddonsList->ItemAt(i);
271				RemoveShortcut(item->key, B_OPTION_KEY | B_COMMAND_KEY);
272			}
273			fAddonsList->MakeEmpty(true);
274		}
275
276		BStringList addOnPaths;
277		BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker",
278			addOnPaths);
279		int32 count = addOnPaths.CountStrings();
280		for (int32 i = 0; i < count; i++) {
281			LoadAddOnDir(BDirectory(addOnPaths.StringAt(i)), this,
282				fAddonsList);
283		}
284	}
285}
286
287
288void
289BDeskWindow::ApplyShortcutPreferences(bool update)
290{
291	AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
292	if (lock.IsLocked()) {
293		if (!update) {
294			BPath path;
295			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
296				BPathMonitor::StartWatching(path.Path(),
297					B_WATCH_STAT | B_WATCH_FILES_ONLY, this);
298				path.Append(kShortcutsSettings);
299				fShortcutsSettings = new char[strlen(path.Path()) + 1];
300				strcpy(fShortcutsSettings, path.Path());
301			}
302		}
303
304		fAddonsList->EachElement(RevertToDefault, this);
305
306		BFile shortcutSettings(fShortcutsSettings, B_READ_ONLY);
307		BMessage fileMsg;
308		if (shortcutSettings.InitCheck() != B_OK
309			|| fileMsg.Unflatten(&shortcutSettings) != B_OK) {
310			fNodeRef = NULL;
311			return;
312		}
313		shortcutSettings.GetNodeRef(fNodeRef);
314
315		int32 i = 0;
316		BMessage message;
317		while (fileMsg.FindMessage("spec", i++, &message) == B_OK) {
318			int32 key;
319			if (message.FindInt32("key", &key) == B_OK) {
320				// only handle shortcuts referring add-ons
321				BString command;
322				if (message.FindString("command", &command) != B_OK)
323					continue;
324
325				bool isInAddons = false;
326
327				BStringList addOnPaths;
328				BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY,
329					"Tracker/", addOnPaths);
330				for (int32 i = 0; i < addOnPaths.CountStrings(); i++) {
331					if (command.StartsWith(addOnPaths.StringAt(i))) {
332						isInAddons = true;
333						break;
334					}
335				}
336
337				if (!isInAddons)
338					continue;
339
340				BEntry entry(command);
341				if (entry.InitCheck() != B_OK)
342					continue;
343
344				const char* shortcut = GetKeyName(key);
345				if (strlen(shortcut) != 1)
346					continue;
347
348				uint32 modifiers = B_COMMAND_KEY;
349					// it's required by interface kit to at least
350					// have B_COMMAND_KEY
351				int32 value;
352				if (message.FindInt32("mcidx", 0, &value) == B_OK)
353					modifiers |= (value != 0 ? B_SHIFT_KEY : 0);
354
355				if (message.FindInt32("mcidx", 1, &value) == B_OK)
356					modifiers |= (value != 0 ? B_CONTROL_KEY : 0);
357
358				if (message.FindInt32("mcidx", 3, &value) == B_OK)
359					modifiers |= (value != 0 ? B_OPTION_KEY : 0);
360
361				Model model(&entry);
362				AddonShortcut* item = fAddonsList->EachElement(FindElement,
363					&model);
364				if (item != NULL) {
365					if (item->key != '\0')
366						RemoveShortcut(item->key, item->modifiers);
367
368					item->key = shortcut[0];
369					item->modifiers = modifiers;
370					AddOneShortcut(&model, item->key, item->modifiers, this);
371				}
372			}
373		}
374	}
375}
376
377
378void
379BDeskWindow::Quit()
380{
381	if (fNavigationItem != NULL) {
382		// this duplicates BContainerWindow::Quit because
383		// fNavigationItem can be part of fTrashContextMenu
384		// and would get deleted with it
385		BMenu* menu = fNavigationItem->Menu();
386		if (menu != NULL)
387			menu->RemoveItem(fNavigationItem);
388
389		delete fNavigationItem;
390		fNavigationItem = 0;
391	}
392
393	fAddonsList->MakeEmpty(true);
394	delete fAddonsList;
395
396	delete fDeskShelf;
397	_inherited::Quit();
398}
399
400
401BPoseView*
402BDeskWindow::NewPoseView(Model* model, uint32 viewMode)
403{
404	return new DesktopPoseView(model, viewMode);
405}
406
407
408void
409BDeskWindow::CreatePoseView(Model* model)
410{
411	fPoseView = NewPoseView(model, kIconMode);
412	fPoseView->SetIconMapping(false);
413	fPoseView->SetEnsurePosesVisible(true);
414	fPoseView->SetAutoScroll(false);
415
416	BScreen screen(this);
417	rgb_color desktopColor = screen.DesktopColor();
418	if (desktopColor.alpha != 255) {
419		desktopColor.alpha = 255;
420#if B_BEOS_VERSION > B_BEOS_VERSION_5
421		// This call seems to have the power to cause R5 to freeze!
422		// Please report if commenting this out helped or helped not
423		// on your system
424		screen.SetDesktopColor(desktopColor);
425#endif
426	}
427
428	fPoseView->SetViewColor(desktopColor);
429	fPoseView->SetLowColor(desktopColor);
430
431	fPoseView->SetResizingMode(B_FOLLOW_ALL);
432	fPoseView->ResizeTo(Bounds().Size());
433	AddChild(fPoseView);
434
435	PoseView()->StartSettingsWatch();
436}
437
438
439void
440BDeskWindow::AddWindowContextMenus(BMenu* menu)
441{
442	TemplatesMenu* tempateMenu = new TemplatesMenu(PoseView(),
443		B_TRANSLATE("New"));
444
445	menu->AddItem(tempateMenu);
446	tempateMenu->SetTargetForItems(PoseView());
447	tempateMenu->SetFont(be_plain_font);
448
449	menu->AddSeparatorItem();
450
451	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
452
453	BMessage* message = new BMessage(kIconMode);
454	message->AddInt32("size", 32);
455	BMenuItem* item = new BMenuItem(B_TRANSLATE("32 x 32"), message);
456	item->SetMarked(PoseView()->IconSizeInt() == 32);
457	item->SetTarget(PoseView());
458	iconSizeMenu->AddItem(item);
459
460	message = new BMessage(kIconMode);
461	message->AddInt32("size", 40);
462	item = new BMenuItem(B_TRANSLATE("40 x 40"), message);
463	item->SetMarked(PoseView()->IconSizeInt() == 40);
464	item->SetTarget(PoseView());
465	iconSizeMenu->AddItem(item);
466
467	message = new BMessage(kIconMode);
468	message->AddInt32("size", 48);
469	item = new BMenuItem(B_TRANSLATE("48 x 48"), message);
470	item->SetMarked(PoseView()->IconSizeInt() == 48);
471	item->SetTarget(PoseView());
472	iconSizeMenu->AddItem(item);
473
474	message = new BMessage(kIconMode);
475	message->AddInt32("size", 64);
476	item = new BMenuItem(B_TRANSLATE("64 x 64"), message);
477	item->SetMarked(PoseView()->IconSizeInt() == 64);
478	item->SetTarget(PoseView());
479	iconSizeMenu->AddItem(item);
480
481	message = new BMessage(kIconMode);
482	message->AddInt32("size", 96);
483	item = new BMenuItem(B_TRANSLATE("96 x 96"), message);
484	item->SetMarked(PoseView()->IconSizeInt() == 96);
485	item->SetTarget(PoseView());
486	iconSizeMenu->AddItem(item);
487
488	message = new BMessage(kIconMode);
489	message->AddInt32("size", 128);
490	item = new BMenuItem(B_TRANSLATE("128 x 128"), message);
491	item->SetMarked(PoseView()->IconSizeInt() == 128);
492	item->SetTarget(PoseView());
493	iconSizeMenu->AddItem(item);
494
495	iconSizeMenu->AddSeparatorItem();
496
497	message = new BMessage(kIconMode);
498	message->AddInt32("scale", 0);
499	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
500	item->SetTarget(PoseView());
501	iconSizeMenu->AddItem(item);
502
503	message = new BMessage(kIconMode);
504	message->AddInt32("scale", 1);
505	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
506	item->SetTarget(PoseView());
507	iconSizeMenu->AddItem(item);
508
509	// A sub menu where the super item can be invoked.
510	menu->AddItem(iconSizeMenu);
511	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
512	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
513	iconSizeMenu->Superitem()->SetTarget(PoseView());
514	iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode);
515
516	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
517		new BMessage(kMiniIconMode), '2');
518	item->SetMarked(PoseView()->ViewMode() == kMiniIconMode);
519	menu->AddItem(item);
520
521	menu->AddSeparatorItem();
522
523#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
524	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
525		new BMessage(B_PASTE), 'V');
526	menu->AddItem(pasteItem);
527	menu->AddSeparatorItem();
528#endif
529	menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"),
530		new BMessage(kCleanup), 'K'));
531	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
532		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
533	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
534		new BMessage(B_SELECT_ALL), 'A'));
535
536	menu->AddSeparatorItem();
537	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
538
539	menu->AddSeparatorItem();
540	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
541
542	// target items as needed
543	menu->SetTargetForItems(PoseView());
544#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
545	pasteItem->SetTarget(this);
546#endif
547}
548
549
550void
551BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
552{
553	if (fBackgroundImage)
554		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
555}
556
557
558void
559BDeskWindow::SaveDesktopPoseLocations()
560{
561	PoseView()->SavePoseLocations(&fOldFrame);
562}
563
564
565void
566BDeskWindow::ScreenChanged(BRect frame, color_space space)
567{
568	bool frameChanged = (frame != fOldFrame);
569
570	SaveDesktopPoseLocations();
571	fOldFrame = frame;
572	ResizeTo(frame.Width(), frame.Height());
573
574	if (fBackgroundImage)
575		fBackgroundImage->ScreenChanged(frame, space);
576
577	PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0);
578		// if frame changed, pass new frame so that icons can
579		// get rearranged based on old pose info for the frame
580}
581
582
583void
584BDeskWindow::UpdateDesktopBackgroundImages()
585{
586	WindowStateNodeOpener opener(this, false);
587	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
588		opener.Node(), true, PoseView());
589}
590
591
592void
593BDeskWindow::Show()
594{
595	if (fBackgroundImage)
596		fBackgroundImage->Show(PoseView(), current_workspace());
597
598	PoseView()->CheckPoseVisibility();
599
600	_inherited::Show();
601}
602
603
604bool
605BDeskWindow::ShouldAddScrollBars() const
606{
607	return false;
608}
609
610
611bool
612BDeskWindow::ShouldAddMenus() const
613{
614	return false;
615}
616
617
618bool
619BDeskWindow::ShouldAddContainerView() const
620{
621	return false;
622}
623
624
625void
626BDeskWindow::MessageReceived(BMessage* message)
627{
628	if (message->WasDropped()) {
629		const rgb_color* color;
630		ssize_t size;
631		// handle "roColour"-style color drops
632		if (message->FindData("RGBColor", 'RGBC',
633			(const void**)&color, &size) == B_OK) {
634			BScreen(this).SetDesktopColor(*color);
635			fPoseView->SetViewColor(*color);
636			fPoseView->SetLowColor(*color);
637
638			// Notify the backgrounds app that the background changed
639			status_t initStatus;
640			BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1,
641				&initStatus);
642			if (initStatus == B_OK)
643				messenger.SendMessage(message);
644
645			return;
646		}
647	}
648
649	switch (message->what) {
650		case B_PATH_MONITOR:
651		{
652			const char* path = "";
653			if (!(message->FindString("path", &path) == B_OK
654					&& strcmp(path, fShortcutsSettings) == 0)) {
655
656				dev_t device;
657				ino_t node;
658				if (fNodeRef == NULL
659					|| message->FindInt32("device", &device) != B_OK
660					|| message->FindInt64("node", &node) != B_OK
661					|| device != fNodeRef->device
662					|| node != fNodeRef->node)
663					break;
664			}
665			ApplyShortcutPreferences(true);
666			break;
667		}
668		case B_NODE_MONITOR:
669			PRINT(("will update addon shortcuts\n"));
670			InitAddonsList(true);
671			ApplyShortcutPreferences(true);
672			break;
673
674		default:
675			_inherited::MessageReceived(message);
676			break;
677	}
678}
679