xref: /haiku/src/kits/tracker/DeskWindow.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 
104 static struct AddonShortcut*
105 RevertToDefault(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 
121 static struct AddonShortcut*
122 FindElement(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 
132 static void
133 LoadAddOnDir(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 
188 BDeskWindow::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 
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, fPoseView);
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 = 0;
390 	}
391 
392 	fAddonsList->MakeEmpty(true);
393 	delete fAddonsList;
394 
395 	delete fDeskShelf;
396 	_inherited::Quit();
397 }
398 
399 
400 BPoseView*
401 BDeskWindow::NewPoseView(Model* model, uint32 viewMode)
402 {
403 	return new DesktopPoseView(model, viewMode);
404 }
405 
406 
407 void
408 BDeskWindow::CreatePoseView(Model* model)
409 {
410 	fPoseView = NewPoseView(model, kIconMode);
411 	fPoseView->SetIconMapping(false);
412 	fPoseView->SetEnsurePosesVisible(true);
413 	fPoseView->SetAutoScroll(false);
414 
415 	BScreen screen(this);
416 	rgb_color desktopColor = screen.DesktopColor();
417 	if (desktopColor.alpha != 255) {
418 		desktopColor.alpha = 255;
419 #if B_BEOS_VERSION > B_BEOS_VERSION_5
420 		// This call seems to have the power to cause R5 to freeze!
421 		// Please report if commenting this out helped or helped not
422 		// on your system
423 		screen.SetDesktopColor(desktopColor);
424 #endif
425 	}
426 
427 	fPoseView->SetViewColor(desktopColor);
428 	fPoseView->SetLowColor(desktopColor);
429 
430 	fPoseView->SetResizingMode(B_FOLLOW_ALL);
431 	fPoseView->ResizeTo(Bounds().Size());
432 	AddChild(fPoseView);
433 
434 	PoseView()->StartSettingsWatch();
435 }
436 
437 
438 void
439 BDeskWindow::AddWindowContextMenus(BMenu* menu)
440 {
441 	TemplatesMenu* tempateMenu = new TemplatesMenu(PoseView(),
442 		B_TRANSLATE("New"));
443 
444 	menu->AddItem(tempateMenu);
445 	tempateMenu->SetTargetForItems(PoseView());
446 	tempateMenu->SetFont(be_plain_font);
447 
448 	menu->AddSeparatorItem();
449 
450 	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
451 	BMenuItem* item;
452 
453 	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
454 	BMessage* message;
455 
456 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
457 		uint32 iconSize = kIconSizes[i];
458 		message = new BMessage(kIconMode);
459 		message->AddInt32("size", iconSize);
460 		BString label;
461 		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
462 			"The '×' is the Unicode multiplication sign U+00D7"),
463 			iconSize, iconSize);
464 		item = new BMenuItem(label, message);
465 		item->SetMarked(PoseView()->IconSizeInt() == iconSize);
466 		item->SetTarget(PoseView());
467 		iconSizeMenu->AddItem(item);
468 	}
469 
470 	iconSizeMenu->AddSeparatorItem();
471 
472 	message = new BMessage(kIconMode);
473 	message->AddInt32("scale", 0);
474 	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
475 	item->SetTarget(PoseView());
476 	iconSizeMenu->AddItem(item);
477 
478 	message = new BMessage(kIconMode);
479 	message->AddInt32("scale", 1);
480 	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
481 	item->SetTarget(PoseView());
482 	iconSizeMenu->AddItem(item);
483 
484 	// A sub menu where the super item can be invoked.
485 	menu->AddItem(iconSizeMenu);
486 	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
487 	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
488 	iconSizeMenu->Superitem()->SetTarget(PoseView());
489 	iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode);
490 
491 	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
492 		new BMessage(kMiniIconMode), '2');
493 	item->SetMarked(PoseView()->ViewMode() == kMiniIconMode);
494 	menu->AddItem(item);
495 
496 	menu->AddSeparatorItem();
497 
498 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
499 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
500 		new BMessage(B_PASTE), 'V');
501 	menu->AddItem(pasteItem);
502 	menu->AddSeparatorItem();
503 #endif
504 	menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"),
505 		new BMessage(kCleanup), 'K'));
506 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
507 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
508 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
509 		new BMessage(B_SELECT_ALL), 'A'));
510 
511 	menu->AddSeparatorItem();
512 	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
513 
514 	menu->AddSeparatorItem();
515 	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
516 
517 	// target items as needed
518 	menu->SetTargetForItems(PoseView());
519 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
520 	pasteItem->SetTarget(this);
521 #endif
522 }
523 
524 
525 void
526 BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
527 {
528 	if (fBackgroundImage)
529 		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
530 }
531 
532 
533 void
534 BDeskWindow::SaveDesktopPoseLocations()
535 {
536 	PoseView()->SavePoseLocations(&fOldFrame);
537 }
538 
539 
540 void
541 BDeskWindow::ScreenChanged(BRect frame, color_space space)
542 {
543 	bool frameChanged = (frame != fOldFrame);
544 
545 	SaveDesktopPoseLocations();
546 	fOldFrame = frame;
547 	ResizeTo(frame.Width(), frame.Height());
548 
549 	if (fBackgroundImage)
550 		fBackgroundImage->ScreenChanged(frame, space);
551 
552 	PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0);
553 		// if frame changed, pass new frame so that icons can
554 		// get rearranged based on old pose info for the frame
555 }
556 
557 
558 void
559 BDeskWindow::UpdateDesktopBackgroundImages()
560 {
561 	WindowStateNodeOpener opener(this, false);
562 	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
563 		opener.Node(), true, PoseView());
564 }
565 
566 
567 void
568 BDeskWindow::Show()
569 {
570 	if (fBackgroundImage)
571 		fBackgroundImage->Show(PoseView(), current_workspace());
572 
573 	PoseView()->CheckPoseVisibility();
574 
575 	_inherited::Show();
576 }
577 
578 
579 bool
580 BDeskWindow::ShouldAddScrollBars() const
581 {
582 	return false;
583 }
584 
585 
586 bool
587 BDeskWindow::ShouldAddMenus() const
588 {
589 	return false;
590 }
591 
592 
593 bool
594 BDeskWindow::ShouldAddContainerView() const
595 {
596 	return false;
597 }
598 
599 
600 void
601 BDeskWindow::MessageReceived(BMessage* message)
602 {
603 	if (message->WasDropped()) {
604 		const rgb_color* color;
605 		ssize_t size;
606 		// handle "roColour"-style color drops
607 		if (message->FindData("RGBColor", 'RGBC',
608 			(const void**)&color, &size) == B_OK) {
609 			BScreen(this).SetDesktopColor(*color);
610 			fPoseView->SetViewColor(*color);
611 			fPoseView->SetLowColor(*color);
612 
613 			// Notify the backgrounds app that the background changed
614 			status_t initStatus;
615 			BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1,
616 				&initStatus);
617 			if (initStatus == B_OK)
618 				messenger.SendMessage(message);
619 
620 			return;
621 		}
622 	}
623 
624 	switch (message->what) {
625 		case B_PATH_MONITOR:
626 		{
627 			const char* path = "";
628 			if (!(message->FindString("path", &path) == B_OK
629 					&& strcmp(path, fShortcutsSettings) == 0)) {
630 
631 				dev_t device;
632 				ino_t node;
633 				if (fNodeRef == NULL
634 					|| message->FindInt32("device", &device) != B_OK
635 					|| message->FindInt64("node", &node) != B_OK
636 					|| device != fNodeRef->device
637 					|| node != fNodeRef->node)
638 					break;
639 			}
640 			ApplyShortcutPreferences(true);
641 			break;
642 		}
643 		case B_NODE_MONITOR:
644 			PRINT(("will update addon shortcuts\n"));
645 			InitAddonsList(true);
646 			ApplyShortcutPreferences(true);
647 			break;
648 
649 		default:
650 			_inherited::MessageReceived(message);
651 			break;
652 	}
653 }
654