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
PersonWindow(BRect frame,const char * title,const char * nameAttribute,const char * categoryAttribute,const entry_ref * ref)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
~PersonWindow()126 PersonWindow::~PersonWindow()
127 {
128 _SetToRef(NULL);
129 }
130
131
132 void
MenusBeginning()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
MessageReceived(BMessage * msg)168 PersonWindow::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
297 bool
QuitRequested()298 PersonWindow::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
336 void
Show()337 PersonWindow::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
347 void
AddAttribute(const char * label,const char * attribute)348 PersonWindow::AddAttribute(const char* label, const char* attribute)
349 {
350 fView->AddAttribute(label, attribute);
351 }
352
353
354 void
SetInitialValues(BMessage * message)355 PersonWindow::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
369 void
SaveAs()370 PersonWindow::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
404 bool
RefersPersonFile(const entry_ref & ref) const405 PersonWindow::RefersPersonFile(const entry_ref& ref) const
406 {
407 if (fRef == NULL)
408 return false;
409 return *fRef == ref;
410 }
411
412
413 void
_GetDefaultFileName(char * name)414 PersonWindow::_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
425 void
_SetToRef(entry_ref * ref)426 PersonWindow::_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
439 void
_WatchChanges(bool enable)440 PersonWindow::_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