xref: /haiku/src/kits/tracker/DeskWindow.cpp (revision 1978089f7cec856677e46204e992c7273d70b9af)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All 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 
71 const char* kShelfPath = "tracker_shelf";
72 	// replicant support
73 
74 const char* kShortcutsSettings = "shortcuts_settings";
75 const char* kDefaultShortcut = "BEOS:default_shortcut";
76 const uint32 kDefaultModifiers = B_OPTION_KEY | B_COMMAND_KEY;
77 
78 
79 static struct AddOnShortcut*
80 MatchOne(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 
91 static void
92 AddOneShortcut(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 static struct AddOnShortcut*
104 RevertToDefault(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 
120 static struct AddOnShortcut*
121 FindElement(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 
131 static void
132 LoadAddOnDir(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 
187 BDeskWindow::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 
217 BDeskWindow::~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 
229 void
230 BDeskWindow::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 
262 void
263 BDeskWindow::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 
287 void
288 BDeskWindow::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 
377 void
378 BDeskWindow::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 
402 BPoseView*
403 BDeskWindow::NewPoseView(Model* model, uint32 viewMode)
404 {
405 	return new DesktopPoseView(model, viewMode);
406 }
407 
408 
409 void
410 BDeskWindow::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 
440 void
441 BDeskWindow::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 
527 void
528 BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
529 {
530 	if (fBackgroundImage)
531 		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
532 }
533 
534 
535 void
536 BDeskWindow::SaveDesktopPoseLocations()
537 {
538 	PoseView()->SavePoseLocations(&fOldFrame);
539 }
540 
541 
542 void
543 BDeskWindow::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 
560 void
561 BDeskWindow::UpdateDesktopBackgroundImages()
562 {
563 	WindowStateNodeOpener opener(this, false);
564 	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
565 		opener.Node(), true, PoseView());
566 }
567 
568 
569 void
570 BDeskWindow::Show()
571 {
572 	if (fBackgroundImage)
573 		fBackgroundImage->Show(PoseView(), current_workspace());
574 
575 	PoseView()->CheckPoseVisibility();
576 
577 	_inherited::Show();
578 }
579 
580 
581 bool
582 BDeskWindow::ShouldAddScrollBars() const
583 {
584 	return false;
585 }
586 
587 
588 bool
589 BDeskWindow::ShouldAddMenus() const
590 {
591 	return false;
592 }
593 
594 
595 bool
596 BDeskWindow::ShouldAddContainerView() const
597 {
598 	return false;
599 }
600 
601 
602 void
603 BDeskWindow::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