1/*
2 * Copyright 2005-2023, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Robert Polic
7 *		Stephan A��mus <superstippi@gmx.de>
8 *
9 * Copyright 1999, Be Incorporated.   All Rights Reserved.
10 * This file may be used under the terms of the Be Sample Code License.
11 */
12
13#include "PersonWindow.h"
14
15#include <stdio.h>
16#include <string.h>
17
18#include <Alert.h>
19#include <Catalog.h>
20#include <Clipboard.h>
21#include <ControlLook.h>
22#include <FilePanel.h>
23#include <FindDirectory.h>
24#include <Font.h>
25#include <LayoutBuilder.h>
26#include <Locale.h>
27#include <MenuBar.h>
28#include <MenuItem.h>
29#include <NodeInfo.h>
30#include <NodeMonitor.h>
31#include <Path.h>
32#include <Screen.h>
33#include <ScrollView.h>
34#include <String.h>
35#include <TextView.h>
36#include <Volume.h>
37
38#include "PeopleApp.h"
39#include "PersonView.h"
40
41
42#undef B_TRANSLATION_CONTEXT
43#define B_TRANSLATION_CONTEXT "People"
44
45
46PersonWindow::PersonWindow(BRect frame, const char* title,
47		const char* nameAttribute, const char* categoryAttribute,
48		const entry_ref* ref)
49	:
50	BWindow(frame, title, B_TITLED_WINDOW, B_NOT_ZOOMABLE
51		| B_AUTO_UPDATE_SIZE_LIMITS),
52	fRef(NULL),
53	fPanel(NULL),
54	fNameAttribute(nameAttribute)
55{
56	BMenu* menu;
57	BMenuItem* item;
58
59	BMenuBar* menuBar = new BMenuBar("");
60	menu = new BMenu(B_TRANSLATE("File"));
61	menu->AddItem(item = new BMenuItem(
62		B_TRANSLATE("New person" B_UTF8_ELLIPSIS),
63		new BMessage(M_NEW), 'N'));
64	item->SetTarget(be_app);
65	menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
66		new BMessage(B_QUIT_REQUESTED), 'W'));
67	menu->AddSeparatorItem();
68	menu->AddItem(fSave = new BMenuItem(B_TRANSLATE("Save"),
69		new BMessage(M_SAVE), 'S'));
70	fSave->SetEnabled(FALSE);
71	menu->AddItem(new BMenuItem(
72		B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
73		new BMessage(M_SAVE_AS)));
74	menu->AddItem(fRevert = new BMenuItem(B_TRANSLATE("Revert"),
75		new BMessage(M_REVERT), 'R'));
76	fRevert->SetEnabled(FALSE);
77	menu->AddSeparatorItem();
78	item = new BMenuItem(B_TRANSLATE("Quit"),
79		new BMessage(B_QUIT_REQUESTED), 'Q');
80	item->SetTarget(be_app);
81	menu->AddItem(item);
82	menuBar->AddItem(menu);
83
84	menu = new BMenu(B_TRANSLATE("Edit"));
85	menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
86		new BMessage(B_UNDO), 'Z'));
87	fUndo->SetEnabled(false);
88	menu->AddSeparatorItem();
89	menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
90		new BMessage(B_CUT), 'X'));
91	menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
92		new BMessage(B_COPY), 'C'));
93	menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
94		new BMessage(B_PASTE), 'V'));
95	BMenuItem* selectAllItem = new BMenuItem(B_TRANSLATE("Select all"),
96		new BMessage(M_SELECT), 'A');
97	menu->AddItem(selectAllItem);
98	menu->AddSeparatorItem();
99	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Configure attributes"),
100		new BMessage(M_CONFIGURE_ATTRIBUTES), 'F'));
101	item->SetTarget(be_app);
102	menuBar->AddItem(menu);
103
104	if (ref != NULL) {
105		SetTitle(ref->name);
106		_SetToRef(new entry_ref(*ref));
107	} else
108		_SetToRef(NULL);
109
110	fView = new PersonView("PeopleView", categoryAttribute, fRef);
111
112	BScrollView* scrollView = new BScrollView("PeopleScrollView", fView, 0,
113		false, true, B_NO_BORDER);
114	scrollView->SetExplicitMinSize(BSize(scrollView->MinSize().width, 0));
115
116	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
117		.SetInsets(0, 0, -1, 0)
118		.Add(menuBar)
119		.Add(scrollView);
120
121	fRevert->SetTarget(fView);
122	selectAllItem->SetTarget(fView);
123}
124
125
126PersonWindow::~PersonWindow()
127{
128	_SetToRef(NULL);
129}
130
131
132void
133PersonWindow::MenusBeginning()
134{
135	bool enabled = !fView->IsSaved();
136	fSave->SetEnabled(enabled);
137	fRevert->SetEnabled(enabled);
138
139	bool isRedo = false;
140	bool undoEnabled = false;
141	bool cutAndCopyEnabled = false;
142
143	BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
144	if (textView != NULL) {
145		undo_state state = textView->UndoState(&isRedo);
146		undoEnabled = state != B_UNDO_UNAVAILABLE;
147
148		cutAndCopyEnabled = fView->IsTextSelected();
149	}
150
151	if (isRedo)
152		fUndo->SetLabel(B_TRANSLATE("Redo"));
153	else
154		fUndo->SetLabel(B_TRANSLATE("Undo"));
155	fUndo->SetEnabled(undoEnabled);
156	fCut->SetEnabled(cutAndCopyEnabled);
157	fCopy->SetEnabled(cutAndCopyEnabled);
158
159	be_clipboard->Lock();
160	fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE));
161	be_clipboard->Unlock();
162
163	fView->BuildGroupMenu();
164}
165
166
167void
168PersonWindow::MessageReceived(BMessage* msg)
169{
170	switch (msg->what) {
171		case M_SAVE:
172			if (!fRef) {
173				SaveAs();
174				break;
175			}
176			// supposed to fall through
177		case M_REVERT:
178		case M_SELECT:
179			fView->MessageReceived(msg);
180			break;
181
182		case M_SAVE_AS:
183			SaveAs();
184			break;
185
186		case B_UNDO: // fall through
187		case B_CUT:
188		case B_COPY:
189		case B_PASTE:
190		{
191			BView* view = CurrentFocus();
192			if (view != NULL)
193				view->MessageReceived(msg);
194			break;
195		}
196
197		case B_SAVE_REQUESTED:
198		{
199			entry_ref dir;
200			if (msg->FindRef("directory", &dir) == B_OK) {
201				const char* name = NULL;
202				msg->FindString("name", &name);
203
204				BDirectory directory;
205				directory.SetTo(&dir);
206				if (directory.InitCheck() == B_NO_ERROR) {
207					BFile file;
208					directory.CreateFile(name, &file);
209					if (file.InitCheck() == B_NO_ERROR) {
210						BNodeInfo* node = new BNodeInfo(&file);
211						node->SetType("application/x-person");
212						delete node;
213
214						BEntry entry;
215						directory.FindEntry(name, &entry);
216						entry.GetRef(&dir);
217						_SetToRef(new entry_ref(dir));
218						SetTitle(fRef->name);
219						fView->CreateFile(fRef);
220					} else {
221						BString str;
222						str.SetToFormat(B_TRANSLATE("Could not create %s."), name);
223						BAlert* alert = new BAlert("", str.String(), B_TRANSLATE("Sorry"));
224						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
225						alert->Go();
226					}
227				}
228			}
229			break;
230		}
231
232		case B_NODE_MONITOR:
233		{
234			int32 opcode;
235			if (msg->FindInt32("opcode", &opcode) == B_OK) {
236				switch (opcode) {
237					case B_ENTRY_REMOVED:
238						// We lost our file. Close the window.
239						PostMessage(B_QUIT_REQUESTED);
240						break;
241
242					case B_ENTRY_MOVED:
243					{
244						// We may have renamed our entry. Obtain relevant data
245						// from message.
246						BString name;
247						msg->FindString("name", &name);
248
249						int64 directory;
250						msg->FindInt64("to directory", &directory);
251
252						int32 device;
253						msg->FindInt32("device", &device);
254
255						// Update our ref.
256						delete fRef;
257						fRef = new entry_ref(device, directory, name.String());
258
259						// And our window title.
260						SetTitle(name);
261
262						// If moved to Trash, close window.
263						BVolume volume(device);
264						BPath trash;
265						find_directory(B_TRASH_DIRECTORY, &trash, false,
266							&volume);
267						BPath folder(fRef);
268						folder.GetParent(&folder);
269						if (folder == trash)
270							PostMessage(B_QUIT_REQUESTED);
271
272						break;
273					}
274
275					case B_ATTR_CHANGED:
276					{
277						// An attribute was updated.
278						BString attr;
279						if (msg->FindString("attr", &attr) == B_OK)
280							fView->SetAttribute(attr.String(), true);
281						break;
282					}
283					case B_STAT_CHANGED:
284						fView->UpdatePicture(fRef);
285						break;
286				}
287			}
288			break;
289		}
290
291		default:
292			BWindow::MessageReceived(msg);
293	}
294}
295
296
297bool
298PersonWindow::QuitRequested()
299{
300	status_t result;
301
302	if (!fView->IsSaved()) {
303		BAlert* alert = new BAlert("",
304			B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"),
305			B_TRANSLATE("Don't save"), B_TRANSLATE("Save"),
306			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
307		alert->SetShortcut(0, B_ESCAPE);
308		alert->SetShortcut(1, 'd');
309		alert->SetShortcut(2, 's');
310		result = alert->Go();
311
312		if (result == 2) {
313			if (fRef)
314				fView->Save();
315			else {
316				SaveAs();
317				return false;
318			}
319		} else if (result == 0)
320			return false;
321	}
322
323	delete fPanel;
324
325	BMessage message(M_WINDOW_QUITS);
326	message.AddRect("frame", Frame());
327	if (be_app->Lock()) {
328		be_app->PostMessage(&message);
329		be_app->Unlock();
330	}
331
332	return true;
333}
334
335
336void
337PersonWindow::Show()
338{
339	BRect screenFrame = BScreen(this).Frame();
340	if (Frame().bottom > screenFrame.bottom)
341		ResizeBy(0, screenFrame.bottom - Frame().bottom - 10);
342	fView->MakeFocus();
343	BWindow::Show();
344}
345
346
347void
348PersonWindow::AddAttribute(const char* label, const char* attribute)
349{
350	fView->AddAttribute(label, attribute);
351}
352
353
354void
355PersonWindow::SetInitialValues(BMessage* message)
356{
357	char* attribute;
358	uint32 type;
359	int32 count;
360
361	for (int32 i = 0; message->GetInfo(B_STRING_TYPE, i, &attribute, &type, &count) == B_OK; i++) {
362		BString text = "";
363		if (message->FindString(attribute, &text) == B_OK)
364			fView->SetAttribute(attribute, text.String(), true);
365	}
366}
367
368
369void
370PersonWindow::SaveAs()
371{
372	char name[B_FILE_NAME_LENGTH];
373	_GetDefaultFileName(name);
374
375	if (fPanel == NULL) {
376		BMessenger target(this);
377		fPanel = new BFilePanel(B_SAVE_PANEL, &target);
378
379		BPath path;
380		find_directory(B_USER_DIRECTORY, &path, true);
381
382		BDirectory dir;
383		dir.SetTo(path.Path());
384
385		BEntry entry;
386		if (dir.FindEntry("people", &entry) == B_OK
387			|| (dir.CreateDirectory("people", &dir) == B_OK
388					&& dir.GetEntry(&entry) == B_OK)) {
389			fPanel->SetPanelDirectory(&entry);
390		}
391	}
392
393	if (fPanel->Window()->Lock()) {
394		fPanel->SetSaveText(name);
395		if (fPanel->Window()->IsHidden())
396			fPanel->Window()->Show();
397		else
398			fPanel->Window()->Activate();
399		fPanel->Window()->Unlock();
400	}
401}
402
403
404bool
405PersonWindow::RefersPersonFile(const entry_ref& ref) const
406{
407	if (fRef == NULL)
408		return false;
409	return *fRef == ref;
410}
411
412
413void
414PersonWindow::_GetDefaultFileName(char* name)
415{
416	strncpy(name, fView->AttributeValue(fNameAttribute), B_FILE_NAME_LENGTH);
417	while (*name) {
418		if (*name == '/')
419			*name = '-';
420		name++;
421	}
422}
423
424
425void
426PersonWindow::_SetToRef(entry_ref* ref)
427{
428	if (fRef != NULL) {
429		_WatchChanges(false);
430		delete fRef;
431	}
432
433	fRef = ref;
434
435	_WatchChanges(true);
436}
437
438
439void
440PersonWindow::_WatchChanges(bool enable)
441{
442	if (fRef == NULL)
443		return;
444
445	node_ref nodeRef;
446
447	BNode node(fRef);
448	node.GetNodeRef(&nodeRef);
449
450	uint32 flags;
451	BString action;
452
453	if (enable) {
454		// Start watching.
455		flags = B_WATCH_ALL;
456		action = "starting";
457	} else {
458		// Stop watching.
459		flags = B_STOP_WATCHING;
460		action = "stoping";
461	}
462
463	if (watch_node(&nodeRef, flags, this) != B_OK) {
464		printf("Error %s node monitor.\n", action.String());
465	}
466}
467