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