xref: /haiku/src/kits/tracker/DeskWindow.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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 "TemplatesMenu.h"
68 #include "Tracker.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, kDesktopWindowLook, kDesktopWindowFeel,
190 		B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK | B_NOT_ZOOMABLE | B_NOT_CLOSABLE
191 			| B_NOT_MINIMIZABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS,
192 		B_ALL_WORKSPACES, false),
193 	fDeskShelf(NULL),
194 	fNodeRef(NULL),
195 	fShortcutsSettings(NULL)
196 {
197 	// Add icon view switching shortcuts. These are displayed in the context
198 	// menu, although they obviously don't work from those menu items.
199 	BMessage* message = new BMessage(kIconMode);
200 	AddShortcut('1', B_COMMAND_KEY, message, PoseView());
201 
202 	message = new BMessage(kMiniIconMode);
203 	AddShortcut('2', B_COMMAND_KEY, message, PoseView());
204 
205 	message = new BMessage(kIconMode);
206 	message->AddInt32("scale", 1);
207 	AddShortcut('+', B_COMMAND_KEY, message, PoseView());
208 
209 	message = new BMessage(kIconMode);
210 	message->AddInt32("scale", 0);
211 	AddShortcut('-', B_COMMAND_KEY, message, PoseView());
212 }
213 
214 
215 BDeskWindow::~BDeskWindow()
216 {
217 	SaveDesktopPoseLocations();
218 		// explicit call to SavePoseLocations so that extended pose info
219 		// gets committed properly
220 	PoseView()->DisableSaveLocation();
221 		// prevent double-saving, this would slow down quitting
222 	PoseView()->StopSettingsWatch();
223 	stop_watching(this);
224 }
225 
226 
227 void
228 BDeskWindow::Init(const BMessage*)
229 {
230 	// Set the size of the screen before calling the container window's
231 	// Init() because it will add volume poses to this window and
232 	// they will be clipped otherwise
233 
234 	BScreen screen(this);
235 	fOldFrame = screen.Frame();
236 
237 	ResizeTo(fOldFrame.Width(), fOldFrame.Height());
238 
239 	InitKeyIndices();
240 	InitAddOnsList(false);
241 	ApplyShortcutPreferences(false);
242 
243 	_inherited::Init();
244 
245 	entry_ref ref;
246 	BPath path;
247 	if (!BootedInSafeMode() && FSFindTrackerSettingsDir(&path) == B_OK) {
248 		path.Append(kShelfPath);
249 		close(open(path.Path(), O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR
250 			| S_IRGRP | S_IROTH));
251 		if (get_ref_for_path(path.Path(), &ref) == B_OK)
252 			fDeskShelf = new BShelf(&ref, PoseView());
253 
254 		if (fDeskShelf != NULL)
255 			fDeskShelf->SetDisplaysZombies(true);
256 	}
257 }
258 
259 
260 void
261 BDeskWindow::InitAddOnsList(bool update)
262 {
263 	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
264 	if (!lock.IsLocked())
265 		return;
266 
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, fAddOnsList);
281 }
282 
283 
284 void
285 BDeskWindow::ApplyShortcutPreferences(bool update)
286 {
287 	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
288 	if (!lock.IsLocked())
289 		return;
290 
291 	if (!update) {
292 		BPath path;
293 		if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
294 			BPathMonitor::StartWatching(path.Path(),
295 				B_WATCH_STAT | B_WATCH_FILES_ONLY, this);
296 			path.Append(kShortcutsSettings);
297 			fShortcutsSettings = new char[strlen(path.Path()) + 1];
298 			strcpy(fShortcutsSettings, path.Path());
299 		}
300 	}
301 
302 	fAddOnsList->EachElement(RevertToDefault, this);
303 
304 	BFile shortcutSettings(fShortcutsSettings, B_READ_ONLY);
305 	BMessage fileMsg;
306 	if (shortcutSettings.InitCheck() != B_OK
307 		|| fileMsg.Unflatten(&shortcutSettings) != B_OK) {
308 		fNodeRef = NULL;
309 		return;
310 	}
311 	shortcutSettings.GetNodeRef(fNodeRef);
312 
313 	int32 i = 0;
314 	BMessage message;
315 	while (fileMsg.FindMessage("spec", i++, &message) == B_OK) {
316 		int32 key;
317 		if (message.FindInt32("key", &key) != B_OK)
318 			continue;
319 
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, &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 void
376 BDeskWindow::Quit()
377 {
378 	if (fNavigationItem != NULL) {
379 		// this duplicates BContainerWindow::Quit because
380 		// fNavigationItem can be part of fTrashContextMenu
381 		// and would get deleted with it
382 		BMenu* menu = fNavigationItem->Menu();
383 		if (menu != NULL)
384 			menu->RemoveItem(fNavigationItem);
385 
386 		delete fNavigationItem;
387 		fNavigationItem = NULL;
388 	}
389 
390 	fAddOnsList->MakeEmpty(true);
391 	delete fAddOnsList;
392 
393 	delete fDeskShelf;
394 
395 	// inherited will clean up the rest
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 
447 	menu->AddSeparatorItem();
448 
449 	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
450 	BMenuItem* item;
451 
452 	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
453 	BMessage* message;
454 
455 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
456 		uint32 iconSize = kIconSizes[i];
457 		message = new BMessage(kIconMode);
458 		message->AddInt32("size", iconSize);
459 		BString label;
460 		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
461 			"The '×' is the Unicode multiplication sign U+00D7"),
462 			iconSize, iconSize);
463 		item = new BMenuItem(label, message);
464 		item->SetMarked(PoseView()->IconSizeInt() == iconSize);
465 		item->SetTarget(PoseView());
466 		iconSizeMenu->AddItem(item);
467 	}
468 
469 	iconSizeMenu->AddSeparatorItem();
470 
471 	message = new BMessage(kIconMode);
472 	message->AddInt32("scale", 0);
473 	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
474 	item->SetTarget(PoseView());
475 	iconSizeMenu->AddItem(item);
476 
477 	message = new BMessage(kIconMode);
478 	message->AddInt32("scale", 1);
479 	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
480 	item->SetTarget(PoseView());
481 	iconSizeMenu->AddItem(item);
482 
483 	// A sub menu where the super item can be invoked.
484 	menu->AddItem(iconSizeMenu);
485 	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
486 	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
487 	iconSizeMenu->Superitem()->SetTarget(PoseView());
488 	iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode);
489 
490 	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
491 		new BMessage(kMiniIconMode), '2');
492 	item->SetMarked(PoseView()->ViewMode() == kMiniIconMode);
493 	menu->AddItem(item);
494 
495 	menu->AddSeparatorItem();
496 
497 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
498 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
499 		new BMessage(B_PASTE), 'V');
500 	menu->AddItem(pasteItem);
501 	menu->AddSeparatorItem();
502 #endif
503 	menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"),
504 		new BMessage(kCleanup), 'K'));
505 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
506 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
507 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
508 		new BMessage(B_SELECT_ALL), 'A'));
509 
510 	menu->AddSeparatorItem();
511 	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
512 
513 	menu->AddSeparatorItem();
514 	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
515 
516 	// target items as needed
517 	menu->SetTargetForItems(PoseView());
518 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
519 	pasteItem->SetTarget(this);
520 #endif
521 }
522 
523 
524 void
525 BDeskWindow::WorkspaceActivated(int32 workspace, bool state)
526 {
527 	if (fBackgroundImage)
528 		fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state);
529 }
530 
531 
532 void
533 BDeskWindow::SaveDesktopPoseLocations()
534 {
535 	PoseView()->SavePoseLocations(&fOldFrame);
536 }
537 
538 
539 void
540 BDeskWindow::ScreenChanged(BRect frame, color_space space)
541 {
542 	bool frameChanged = (frame != fOldFrame);
543 
544 	SaveDesktopPoseLocations();
545 	fOldFrame = frame;
546 	ResizeTo(frame.Width(), frame.Height());
547 
548 	if (fBackgroundImage)
549 		fBackgroundImage->ScreenChanged(frame, space);
550 
551 	PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0);
552 		// if frame changed, pass new frame so that icons can
553 		// get rearranged based on old pose info for the frame
554 }
555 
556 
557 void
558 BDeskWindow::UpdateDesktopBackgroundImages()
559 {
560 	WindowStateNodeOpener opener(this, false);
561 	fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
562 		opener.Node(), true, PoseView());
563 }
564 
565 
566 void
567 BDeskWindow::Show()
568 {
569 	if (fBackgroundImage)
570 		fBackgroundImage->Show(PoseView(), current_workspace());
571 
572 	PoseView()->CheckPoseVisibility();
573 
574 	_inherited::Show();
575 }
576 
577 
578 bool
579 BDeskWindow::ShouldAddScrollBars() const
580 {
581 	return false;
582 }
583 
584 
585 bool
586 BDeskWindow::ShouldAddMenus() const
587 {
588 	return false;
589 }
590 
591 
592 bool
593 BDeskWindow::ShouldAddContainerView() const
594 {
595 	return false;
596 }
597 
598 
599 void
600 BDeskWindow::MessageReceived(BMessage* message)
601 {
602 	if (message->WasDropped()) {
603 		const rgb_color* color;
604 		ssize_t size;
605 		// handle "roColour"-style color drops
606 		if (message->FindData("RGBColor", 'RGBC',
607 			(const void**)&color, &size) == B_OK) {
608 			BScreen(this).SetDesktopColor(*color);
609 			PoseView()->SetViewColor(*color);
610 			PoseView()->SetLowColor(*color);
611 
612 			// Notify the backgrounds app that the background changed
613 			status_t initStatus;
614 			BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1,
615 				&initStatus);
616 			if (initStatus == B_OK)
617 				messenger.SendMessage(message);
618 
619 			return;
620 		}
621 	}
622 
623 	switch (message->what) {
624 		case B_PATH_MONITOR:
625 		{
626 			const char* path = "";
627 			if (!(message->FindString("path", &path) == B_OK
628 					&& strcmp(path, fShortcutsSettings) == 0)) {
629 
630 				dev_t device;
631 				ino_t node;
632 				if (fNodeRef == NULL
633 					|| message->FindInt32("device", &device) != B_OK
634 					|| message->FindInt64("node", &node) != B_OK
635 					|| device != fNodeRef->device
636 					|| node != fNodeRef->node)
637 					break;
638 			}
639 			ApplyShortcutPreferences(true);
640 			break;
641 		}
642 		case B_NODE_MONITOR:
643 			PRINT(("will update addon shortcuts\n"));
644 			InitAddOnsList(true);
645 			ApplyShortcutPreferences(true);
646 			break;
647 
648 		default:
649 			_inherited::MessageReceived(message);
650 			break;
651 	}
652 }
653