xref: /haiku/src/apps/people/PersonWindow.cpp (revision b5a859ad6285b14769a5ede7b81e4261044faf7e)
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 <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 
46 PersonWindow::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 
126 PersonWindow::~PersonWindow()
127 {
128 	_SetToRef(NULL);
129 }
130 
131 
132 void
133 PersonWindow::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 
167 void
168 PersonWindow::MessageReceived(BMessage* msg)
169 {
170 	char			str[256];
171 	BDirectory		directory;
172 	BEntry			entry;
173 	BFile			file;
174 	BNodeInfo		*node;
175 
176 	switch (msg->what) {
177 		case M_SAVE:
178 			if (!fRef) {
179 				SaveAs();
180 				break;
181 			}
182 			// supposed to fall through
183 		case M_REVERT:
184 		case M_SELECT:
185 			fView->MessageReceived(msg);
186 			break;
187 
188 		case M_SAVE_AS:
189 			SaveAs();
190 			break;
191 
192 		case B_UNDO: // fall through
193 		case B_CUT:
194 		case B_COPY:
195 		case B_PASTE:
196 		{
197 			BView* view = CurrentFocus();
198 			if (view != NULL)
199 				view->MessageReceived(msg);
200 			break;
201 		}
202 
203 		case B_SAVE_REQUESTED:
204 		{
205 			entry_ref dir;
206 			if (msg->FindRef("directory", &dir) == B_OK) {
207 				const char* name = NULL;
208 				msg->FindString("name", &name);
209 				directory.SetTo(&dir);
210 				if (directory.InitCheck() == B_NO_ERROR) {
211 					directory.CreateFile(name, &file);
212 					if (file.InitCheck() == B_NO_ERROR) {
213 						node = new BNodeInfo(&file);
214 						node->SetType("application/x-person");
215 						delete node;
216 
217 						directory.FindEntry(name, &entry);
218 						entry.GetRef(&dir);
219 						_SetToRef(new entry_ref(dir));
220 						SetTitle(fRef->name);
221 						fView->CreateFile(fRef);
222 					}
223 					else {
224 						sprintf(str, B_TRANSLATE("Could not create %s."), name);
225 						BAlert* alert = new BAlert("", str, B_TRANSLATE("Sorry"));
226 						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
227 						alert->Go();
228 					}
229 				}
230 			}
231 			break;
232 		}
233 
234 		case B_NODE_MONITOR:
235 		{
236 			int32 opcode;
237 			if (msg->FindInt32("opcode", &opcode) == B_OK) {
238 				switch (opcode) {
239 					case B_ENTRY_REMOVED:
240 						// We lost our file. Close the window.
241 						PostMessage(B_QUIT_REQUESTED);
242 						break;
243 
244 					case B_ENTRY_MOVED:
245 					{
246 						// We may have renamed our entry. Obtain relevant data
247 						// from message.
248 						BString name;
249 						msg->FindString("name", &name);
250 
251 						int64 directory;
252 						msg->FindInt64("to directory", &directory);
253 
254 						int32 device;
255 						msg->FindInt32("device", &device);
256 
257 						// Update our ref.
258 						delete fRef;
259 						fRef = new entry_ref(device, directory, name.String());
260 
261 						// And our window title.
262 						SetTitle(name);
263 
264 						// If moved to Trash, close window.
265 						BVolume volume(device);
266 						BPath trash;
267 						find_directory(B_TRASH_DIRECTORY, &trash, false,
268 							&volume);
269 						BPath folder(fRef);
270 						folder.GetParent(&folder);
271 						if (folder == trash)
272 							PostMessage(B_QUIT_REQUESTED);
273 
274 						break;
275 					}
276 
277 					case B_ATTR_CHANGED:
278 					{
279 						// An attribute was updated.
280 						BString attr;
281 						if (msg->FindString("attr", &attr) == B_OK)
282 							fView->SetAttribute(attr.String(), true);
283 						break;
284 					}
285 					case B_STAT_CHANGED:
286 						fView->UpdatePicture(fRef);
287 						break;
288 				}
289 			}
290 			break;
291 		}
292 
293 		default:
294 			BWindow::MessageReceived(msg);
295 	}
296 }
297 
298 
299 bool
300 PersonWindow::QuitRequested()
301 {
302 	status_t result;
303 
304 	if (!fView->IsSaved()) {
305 		BAlert* alert = new BAlert("",
306 			B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"),
307 			B_TRANSLATE("Don't save"), B_TRANSLATE("Save"),
308 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
309 		alert->SetShortcut(0, B_ESCAPE);
310 		alert->SetShortcut(1, 'd');
311 		alert->SetShortcut(2, 's');
312 		result = alert->Go();
313 
314 		if (result == 2) {
315 			if (fRef)
316 				fView->Save();
317 			else {
318 				SaveAs();
319 				return false;
320 			}
321 		} else if (result == 0)
322 			return false;
323 	}
324 
325 	delete fPanel;
326 
327 	BMessage message(M_WINDOW_QUITS);
328 	message.AddRect("frame", Frame());
329 	if (be_app->Lock()) {
330 		be_app->PostMessage(&message);
331 		be_app->Unlock();
332 	}
333 
334 	return true;
335 }
336 
337 
338 void
339 PersonWindow::Show()
340 {
341 	BRect screenFrame = BScreen(this).Frame();
342 	if (Frame().bottom > screenFrame.bottom)
343 		ResizeBy(0, screenFrame.bottom - Frame().bottom - 10);
344 	fView->MakeFocus();
345 	BWindow::Show();
346 }
347 
348 
349 void
350 PersonWindow::AddAttribute(const char* label, const char* attribute)
351 {
352 	fView->AddAttribute(label, attribute);
353 }
354 
355 
356 void
357 PersonWindow::SaveAs()
358 {
359 	char name[B_FILE_NAME_LENGTH];
360 	_GetDefaultFileName(name);
361 
362 	if (fPanel == NULL) {
363 		BMessenger target(this);
364 		fPanel = new BFilePanel(B_SAVE_PANEL, &target);
365 
366 		BPath path;
367 		find_directory(B_USER_DIRECTORY, &path, true);
368 
369 		BDirectory dir;
370 		dir.SetTo(path.Path());
371 
372 		BEntry entry;
373 		if (dir.FindEntry("people", &entry) == B_OK
374 			|| (dir.CreateDirectory("people", &dir) == B_OK
375 					&& dir.GetEntry(&entry) == B_OK)) {
376 			fPanel->SetPanelDirectory(&entry);
377 		}
378 	}
379 
380 	if (fPanel->Window()->Lock()) {
381 		fPanel->SetSaveText(name);
382 		if (fPanel->Window()->IsHidden())
383 			fPanel->Window()->Show();
384 		else
385 			fPanel->Window()->Activate();
386 		fPanel->Window()->Unlock();
387 	}
388 }
389 
390 
391 bool
392 PersonWindow::RefersPersonFile(const entry_ref& ref) const
393 {
394 	if (fRef == NULL)
395 		return false;
396 	return *fRef == ref;
397 }
398 
399 
400 void
401 PersonWindow::_GetDefaultFileName(char* name)
402 {
403 	strncpy(name, fView->AttributeValue(fNameAttribute), B_FILE_NAME_LENGTH);
404 	while (*name) {
405 		if (*name == '/')
406 			*name = '-';
407 		name++;
408 	}
409 }
410 
411 
412 void
413 PersonWindow::_SetToRef(entry_ref* ref)
414 {
415 	if (fRef != NULL) {
416 		_WatchChanges(false);
417 		delete fRef;
418 	}
419 
420 	fRef = ref;
421 
422 	_WatchChanges(true);
423 }
424 
425 
426 void
427 PersonWindow::_WatchChanges(bool enable)
428 {
429 	if (fRef == NULL)
430 		return;
431 
432 	node_ref nodeRef;
433 
434 	BNode node(fRef);
435 	node.GetNodeRef(&nodeRef);
436 
437 	uint32 flags;
438 	BString action;
439 
440 	if (enable) {
441 		// Start watching.
442 		flags = B_WATCH_ALL;
443 		action = "starting";
444 	} else {
445 		// Stop watching.
446 		flags = B_STOP_WATCHING;
447 		action = "stoping";
448 	}
449 
450 	if (watch_node(&nodeRef, flags, this) != B_OK) {
451 		printf("Error %s node monitor.\n", action.String());
452 	}
453 }
454