xref: /haiku/src/kits/tracker/DeskWindow.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
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 		return;
268 
269 	if (update) {
270 		for (int i = fAddOnsList->CountItems() - 1; i >= 0; i--) {
271 			AddOnShortcut* item = fAddOnsList->ItemAt(i);
272 			RemoveShortcut(item->key, B_OPTION_KEY | B_COMMAND_KEY);
273 		}
274 		fAddOnsList->MakeEmpty(true);
275 	}
276 
277 	BStringList addOnPaths;
278 	BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker",
279 		addOnPaths);
280 	int32 count = addOnPaths.CountStrings();
281 	for (int32 i = 0; i < count; i++)
282 		LoadAddOnDir(BDirectory(addOnPaths.StringAt(i)), this, fAddOnsList);
283 }
284 
285 
286 void
287 BDeskWindow::ApplyShortcutPreferences(bool update)
288 {
289 	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
290 	if (!lock.IsLocked())
291 		return;
292 
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 			continue;
321 
322 		// only handle shortcuts referring add-ons
323 		BString command;
324 		if (message.FindString("command", &command) != B_OK)
325 			continue;
326 
327 		bool isInAddOns = false;
328 
329 		BStringList addOnPaths;
330 		BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY,
331 			"Tracker/", addOnPaths);
332 		for (int32 i = 0; i < addOnPaths.CountStrings(); i++) {
333 			if (command.StartsWith(addOnPaths.StringAt(i))) {
334 				isInAddOns = true;
335 				break;
336 			}
337 		}
338 
339 		if (!isInAddOns)
340 			continue;
341 
342 		BEntry entry(command);
343 		if (entry.InitCheck() != B_OK)
344 			continue;
345 
346 		const char* shortcut = GetKeyName(key);
347 		if (strlen(shortcut) != 1)
348 			continue;
349 
350 		uint32 modifiers = B_COMMAND_KEY;
351 			// it's required by interface kit to at least
352 			// have B_COMMAND_KEY
353 		int32 value;
354 		if (message.FindInt32("mcidx", 0, &value) == B_OK)
355 			modifiers |= (value != 0 ? B_SHIFT_KEY : 0);
356 
357 		if (message.FindInt32("mcidx", 1, &value) == B_OK)
358 			modifiers |= (value != 0 ? B_CONTROL_KEY : 0);
359 
360 		if (message.FindInt32("mcidx", 3, &value) == B_OK)
361 			modifiers |= (value != 0 ? B_OPTION_KEY : 0);
362 
363 		Model model(&entry);
364 		AddOnShortcut* item = fAddOnsList->EachElement(FindElement, &model);
365 		if (item != NULL) {
366 			if (item->key != '\0')
367 				RemoveShortcut(item->key, item->modifiers);
368 
369 			item->key = shortcut[0];
370 			item->modifiers = modifiers;
371 			AddOneShortcut(&model, item->key, item->modifiers, this);
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