xref: /haiku/src/kits/tracker/DeskWindow.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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 
449 	menu->AddSeparatorItem();
450 
451 	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
452 	BMenuItem* item;
453 
454 	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
455 	BMessage* message;
456 
457 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
458 		uint32 iconSize = kIconSizes[i];
459 		message = new BMessage(kIconMode);
460 		message->AddInt32("size", iconSize);
461 		BString label;
462 		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
463 			"The '×' is the Unicode multiplication sign U+00D7"),
464 			iconSize, iconSize);
465 		item = new BMenuItem(label, message);
466 		item->SetMarked(PoseView()->IconSizeInt() == iconSize);
467 		item->SetTarget(PoseView());
468 		iconSizeMenu->AddItem(item);
469 	}
470 
471 	iconSizeMenu->AddSeparatorItem();
472 
473 	message = new BMessage(kIconMode);
474 	message->AddInt32("scale", 0);
475 	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
476 	item->SetTarget(PoseView());
477 	iconSizeMenu->AddItem(item);
478 
479 	message = new BMessage(kIconMode);
480 	message->AddInt32("scale", 1);
481 	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
482 	item->SetTarget(PoseView());
483 	iconSizeMenu->AddItem(item);
484 
485 	// A sub menu where the super item can be invoked.
486 	menu->AddItem(iconSizeMenu);
487 	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
488 	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
489 	iconSizeMenu->Superitem()->SetTarget(PoseView());
490 	iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode);
491 
492 	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
493 		new BMessage(kMiniIconMode), '2');
494 	item->SetMarked(PoseView()->ViewMode() == kMiniIconMode);
495 	menu->AddItem(item);
496 
497 	menu->AddSeparatorItem();
498 
499 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
500 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
501 		new BMessage(B_PASTE), 'V');
502 	menu->AddItem(pasteItem);
503 	menu->AddSeparatorItem();
504 #endif
505 	menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"),
506 		new BMessage(kCleanup), 'K'));
507 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
508 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
509 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
510 		new BMessage(B_SELECT_ALL), 'A'));
511 
512 	menu->AddSeparatorItem();
513 	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
514 
515 	menu->AddSeparatorItem();
516 	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
517 
518 	// target items as needed
519 	menu->SetTargetForItems(PoseView());
520 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
521 	pasteItem->SetTarget(this);
522 #endif
523 }
524 
525 
526 void
527 BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
528 {
529 	if (fBackgroundImage)
530 		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
531 }
532 
533 
534 void
535 BDeskWindow::SaveDesktopPoseLocations()
536 {
537 	PoseView()->SavePoseLocations(&fOldFrame);
538 }
539 
540 
541 void
542 BDeskWindow::ScreenChanged(BRect frame, color_space space)
543 {
544 	bool frameChanged = (frame != fOldFrame);
545 
546 	SaveDesktopPoseLocations();
547 	fOldFrame = frame;
548 	ResizeTo(frame.Width(), frame.Height());
549 
550 	if (fBackgroundImage)
551 		fBackgroundImage->ScreenChanged(frame, space);
552 
553 	PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0);
554 		// if frame changed, pass new frame so that icons can
555 		// get rearranged based on old pose info for the frame
556 }
557 
558 
559 void
560 BDeskWindow::UpdateDesktopBackgroundImages()
561 {
562 	WindowStateNodeOpener opener(this, false);
563 	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
564 		opener.Node(), true, PoseView());
565 }
566 
567 
568 void
569 BDeskWindow::Show()
570 {
571 	if (fBackgroundImage)
572 		fBackgroundImage->Show(PoseView(), current_workspace());
573 
574 	PoseView()->CheckPoseVisibility();
575 
576 	_inherited::Show();
577 }
578 
579 
580 bool
581 BDeskWindow::ShouldAddScrollBars() const
582 {
583 	return false;
584 }
585 
586 
587 bool
588 BDeskWindow::ShouldAddMenus() const
589 {
590 	return false;
591 }
592 
593 
594 bool
595 BDeskWindow::ShouldAddContainerView() const
596 {
597 	return false;
598 }
599 
600 
601 void
602 BDeskWindow::MessageReceived(BMessage* message)
603 {
604 	if (message->WasDropped()) {
605 		const rgb_color* color;
606 		ssize_t size;
607 		// handle "roColour"-style color drops
608 		if (message->FindData("RGBColor", 'RGBC',
609 			(const void**)&color, &size) == B_OK) {
610 			BScreen(this).SetDesktopColor(*color);
611 			PoseView()->SetViewColor(*color);
612 			PoseView()->SetLowColor(*color);
613 
614 			// Notify the backgrounds app that the background changed
615 			status_t initStatus;
616 			BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1,
617 				&initStatus);
618 			if (initStatus == B_OK)
619 				messenger.SendMessage(message);
620 
621 			return;
622 		}
623 	}
624 
625 	switch (message->what) {
626 		case B_PATH_MONITOR:
627 		{
628 			const char* path = "";
629 			if (!(message->FindString("path", &path) == B_OK
630 					&& strcmp(path, fShortcutsSettings) == 0)) {
631 
632 				dev_t device;
633 				ino_t node;
634 				if (fNodeRef == NULL
635 					|| message->FindInt32("device", &device) != B_OK
636 					|| message->FindInt64("node", &node) != B_OK
637 					|| device != fNodeRef->device
638 					|| node != fNodeRef->node)
639 					break;
640 			}
641 			ApplyShortcutPreferences(true);
642 			break;
643 		}
644 		case B_NODE_MONITOR:
645 			PRINT(("will update addon shortcuts\n"));
646 			InitAddOnsList(true);
647 			ApplyShortcutPreferences(true);
648 			break;
649 
650 		default:
651 			_inherited::MessageReceived(message);
652 			break;
653 	}
654 }
655