xref: /haiku/src/kits/tracker/FindPanel.cpp (revision d9cebac2b77547b7064f22497514eecd2d047160)
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 #include <Application.h>
36 #include <Box.h>
37 #include <Button.h>
38 #include <CheckBox.h>
39 #include <Debug.h>
40 #include <Directory.h>
41 #include <FindDirectory.h>
42 #include <File.h>
43 #include <FilePanel.h>
44 #include <MenuField.h>
45 #include <MenuItem.h>
46 #include <Mime.h>
47 #include <NodeInfo.h>
48 #include <PopUpMenu.h>
49 #include <Path.h>
50 #include <Query.h>
51 #include <TextControl.h>
52 #include <TextView.h>
53 #include <View.h>
54 #include <Volume.h>
55 #include <VolumeRoster.h>
56 
57 #include <fs_attr.h>
58 #include <parsedate.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <utility>
62 
63 #include "Attributes.h"
64 #include "AutoLock.h"
65 #include "Commands.h"
66 #include "ContainerWindow.h"
67 #include "FindPanel.h"
68 #include "FSUtils.h"
69 #include "FunctionObject.h"
70 #include "IconMenuItem.h"
71 #include "MimeTypes.h"
72 #include "MiniMenuField.h"
73 #include "Tracker.h"
74 #include "Utilities.h"
75 
76 const char *kAllMimeTypes = "mime/ALLTYPES";
77 
78 const BRect kInitialRect(100, 100, 530, 210);
79 const int32 kInitialAttrModeWindowHeight = 140;
80 const int32 kIncrementPerAttribute = 30;
81 const float kMoreOptionsDelta = 20;
82 
83 const uint32 kMoreOptionsMessage = 'mrop';
84 const uint32 kNameModifiedMessage = 'nmmd';
85 const uint32 kSwitchToQueryTemplate = 'swqt';
86 const uint32 kRunSaveAsTemplatePanel = 'svtm';
87 
88 const char *kDragNDropTypes [] = { B_QUERY_MIMETYPE, B_QUERY_TEMPLATE_MIMETYPE };
89 const char *kDragNDropActionSpecifiers [] = { "Create a Query", "Create a Query template" };
90 
91 const uint32 kAttachFile = 'attf';
92 
93 
94 namespace BPrivate {
95 
96 class MostUsedNames {
97 	public:
98 		MostUsedNames(const char *fileName, const char *directory, int32 maxCount = 5);
99 		~MostUsedNames();
100 
101 		bool ObtainList(BList *list);
102 		void ReleaseList();
103 
104 		void AddName(const char *);
105 
106 	protected:
107 		struct list_entry {
108 			char *name;
109 			int32 count;
110 		};
111 
112 		static int CompareNames(const void *a, const void *b);
113 		void LoadList();
114 		void UpdateList();
115 
116 		const char 	*fFileName;
117 		const char	*fDirectory;
118 		bool		fLoaded;
119 		mutable Benaphore fLock;
120 		BList		fList;
121 		int32		fCount;
122 };
123 
124 MostUsedNames gMostUsedMimeTypes("MostUsedMimeTypes", "Tracker");
125 
126 
127 void
128 MoreOptionsStruct::EndianSwap(void *)
129 {
130 	// noop for now
131 }
132 
133 
134 void
135 MoreOptionsStruct::SetQueryTemporary(BNode *node, bool on)
136 {
137 	MoreOptionsStruct saveMoreOptions;
138 
139 	ReadAttr(node, kAttrQueryMoreOptions, kAttrQueryMoreOptionsForeign,
140 		B_RAW_TYPE, 0, &saveMoreOptions, sizeof(MoreOptionsStruct),
141 		&MoreOptionsStruct::EndianSwap);
142 	saveMoreOptions.temporary = on;
143 	node->WriteAttr(kAttrQueryMoreOptions, B_RAW_TYPE, 0, &saveMoreOptions,
144 		sizeof(saveMoreOptions));
145 }
146 
147 
148 bool
149 MoreOptionsStruct::QueryTemporary(const BNode *node)
150 {
151 	MoreOptionsStruct saveMoreOptions;
152 
153 	if (ReadAttr(node, kAttrQueryMoreOptions, kAttrQueryMoreOptionsForeign,
154 		B_RAW_TYPE, 0, &saveMoreOptions, sizeof(MoreOptionsStruct),
155 		&MoreOptionsStruct::EndianSwap) == kReadAttrFailed)
156 		return false;
157 
158 	return saveMoreOptions.temporary;
159 }
160 
161 
162 //	#pragma mark -
163 
164 
165 FindWindow::FindWindow(const entry_ref *newRef, bool editIfTemplateOnly)
166 	:	BWindow(kInitialRect, "Find", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE),
167 		fFile(TryOpening(newRef)),
168 		fFromTemplate(false),
169 		fEditTemplateOnly(false),
170 		fSaveAsTemplatePanel(NULL)
171 {
172 	if (fFile) {
173 		fRef = *newRef;
174 		if (editIfTemplateOnly) {
175 			char type[B_MIME_TYPE_LENGTH];
176 			if (BNodeInfo(fFile).GetType(type) == B_OK
177 				&& strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0) {
178 				fEditTemplateOnly = true;
179 				SetTitle("Edit Query Template");
180 			}
181 		}
182 	} else {
183 		// no initial query, fall back on the default query template
184 		BEntry entry;
185 		GetDefaultQuery(entry);
186 		entry.GetRef(&fRef);
187 
188 		if (entry.Exists())
189 			fFile = TryOpening(&fRef);
190 		else {
191 			// no default query template yet
192 			fFile = new BFile(&entry, O_RDWR | O_CREAT);
193 			if (fFile->InitCheck() < B_OK) {
194 				delete fFile;
195 				fFile = NULL;
196 			} else
197 				SaveQueryAttributes(fFile, true);
198 		}
199 	}
200 
201 	if (fFile) {
202 	 	BRect initialRect(FindPanel::InitialViewSize(fFile));
203 		ResizeTo(initialRect.Width(), initialRect.Height());
204 	}
205 
206 	fFromTemplate = IsQueryTemplate(fFile);
207 
208 	fBackground = new FindPanel(Bounds(), fFile, this, fFromTemplate, fEditTemplateOnly);
209 	AddChild(fBackground);
210 }
211 
212 
213 FindWindow::~FindWindow()
214 {
215 	delete fFile;
216 	delete fSaveAsTemplatePanel;
217 }
218 
219 
220 BFile *
221 FindWindow::TryOpening(const entry_ref *ref)
222 {
223 	if (!ref)
224 		return NULL;
225 
226 	BFile *result = new BFile(ref, O_RDWR);
227 	if (result->InitCheck() != B_OK) {
228 		delete result;
229 		result = NULL;
230 	}
231 	return result;
232 }
233 
234 
235 void
236 FindWindow::GetDefaultQuery(BEntry &entry)
237 {
238 	BPath path;
239 	find_directory(B_USER_DIRECTORY, &path, true);
240 	path.Append("queries");
241 	mkdir(path.Path(), 0777);
242 	BDirectory directory(path.Path());
243 
244 	entry.SetTo(&directory, "default");
245 }
246 
247 
248 bool
249 FindWindow::IsQueryTemplate(BNode *file)
250 {
251 	char type[B_MIME_TYPE_LENGTH];
252 	if (BNodeInfo(file).GetType(type) != B_OK)
253 		return false;
254 
255 	return strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0;
256 }
257 
258 
259 void
260 FindWindow::SwitchToTemplate(const entry_ref *ref)
261 {
262 	try {
263 		BEntry entry(ref, true);
264 		BFile templateFile(&entry, O_RDONLY);
265 
266 		ThrowOnInitCheckError(&templateFile);
267 		DisableUpdates();
268 			// turn off updates to reduce flicker while re-populating the
269 			// window
270 		fBackground->SwitchToTemplate(&templateFile);
271 		EnableUpdates();
272 
273 	} catch (...) {
274 	}
275 }
276 
277 
278 const char *
279 FindWindow::QueryName() const
280 {
281 	if (fFromTemplate) {
282 		if (!fQueryNameFromTemplate.Length())
283 			fFile->ReadAttrString(kAttrQueryTemplateName, &fQueryNameFromTemplate);
284 
285 		return fQueryNameFromTemplate.String();
286 	}
287 	if (!fFile)
288 		return "";
289 
290 	return fRef.name;
291 }
292 
293 
294 static const char *
295 MakeValidFilename(BString &string)
296 {
297 	// make a file name that is legal under bfs and hfs - possibly could
298 	// add code here to accomodate FAT32 etc. too
299 	if (string.Length() > B_FILE_NAME_LENGTH - 1) {
300 		string.Truncate(B_FILE_NAME_LENGTH - 4);
301 		string += B_UTF8_ELLIPSIS;
302 	}
303 
304 	// replace slashes
305 	int32 length = string.Length();
306 	char *buf = string.LockBuffer(length);
307 	for (int32 index = length; index-- > 0;)
308 		if (buf[index] == '/' /*|| buf[index] == ':'*/)
309 			buf[index] = '_';
310 	string.UnlockBuffer(length);
311 
312 	return string.String();
313 }
314 
315 
316 void
317 FindWindow::GetPredicateString(BString &predicate, bool &dynamicDate)
318 {
319 	BQuery query;
320 	BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
321 	switch (fBackground->Mode()) {
322 		case kByNameItem:
323 			fBackground->GetByNamePredicate(&query);
324 			query.GetPredicate(&predicate);
325 			break;
326 
327 		case kByForumlaItem:
328 			predicate.SetTo(textControl->TextView()->Text(), 1023);
329 			break;
330 
331 		case kByAttributeItem:
332 			fBackground->GetByAttrPredicate(&query, dynamicDate);
333 			query.GetPredicate(&predicate);
334 			break;
335 	}
336 }
337 
338 
339 void
340 FindWindow::GetDefaultName(BString &result)
341 {
342 	fBackground->GetDefaultName(result);
343 
344 	time_t timeValue = time(0);
345 	char namebuf[B_FILE_NAME_LENGTH];
346 
347 	tm timeData;
348 	localtime_r(&timeValue, &timeData);
349 
350 	strftime(namebuf, 32, " - %b %d, %I:%M:%S %p", &timeData);
351 	result << namebuf;
352 
353 	MakeValidFilename(result);
354 }
355 
356 
357 void
358 FindWindow::SaveQueryAttributes(BNode *file, bool queryTemplate)
359 {
360 	ThrowOnError( BNodeInfo(file).SetType(
361 		queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE) );
362 
363 	// save date/time info for recent query support and transient query killer
364 	int32 currentTime = (int32)time(0);
365 	file->WriteAttr(kAttrQueryLastChange, B_INT32_TYPE, 0, &currentTime, sizeof(int32));
366 	int32 tmp = 1;
367 	file->WriteAttr("_trk/recentQuery", B_INT32_TYPE, 0, &tmp, sizeof(int32));
368 }
369 
370 
371 status_t
372 FindWindow::SaveQueryAsAttributes(BNode *file, BEntry *entry, bool queryTemplate,
373 	const BMessage *oldAttributes, const BPoint *oldLocation)
374 {
375 	if (oldAttributes)
376 		// revive old window settings
377 		BContainerWindow::SetLayoutState(file, oldAttributes);
378 
379 	if (oldLocation)
380 		// and the file's location
381 		FSSetPoseLocation(entry, *oldLocation);
382 
383 	BNodeInfo(file).SetType(queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE);
384 
385 	BString predicate;
386 	bool dynamicDate;
387 	GetPredicateString(predicate, dynamicDate);
388 	file->WriteAttrString(kAttrQueryString, &predicate);
389 
390 	if (dynamicDate)
391 		file->WriteAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, &dynamicDate,
392 			sizeof(dynamicDate));
393 
394 	int32 tmp = 1;
395 	file->WriteAttr("_trk/recentQuery", B_INT32_TYPE, 0, &tmp, sizeof(int32));
396 
397 	// write some useful info to help locate the volume to query
398 	BMenuItem *item = fBackground->VolMenu()->FindMarked();
399 	if (item) {
400 		dev_t dev;
401 		BMessage message;
402 		int32 count = 0;
403 
404 		int32 itemCount = fBackground->VolMenu()->CountItems();
405 		for (int32 index = 2; index < itemCount; index++) {
406 			BMenuItem *item = fBackground->VolMenu()->ItemAt(index);
407 
408 			if (!item->IsMarked())
409 				continue;
410 
411 			if (item->Message()->FindInt32("device", &dev) != B_OK)
412 				continue;
413 
414 			count++;
415 			BVolume volume(dev);
416 			EmbedUniqueVolumeInfo(&message, &volume);
417 		}
418 
419 		if (count) {
420 			// do we need to embed any volumes
421 			ssize_t size = message.FlattenedSize();
422 			BString buffer;
423 			status_t result = message.Flatten(buffer.LockBuffer(size), size);
424 			ASSERT(result == B_OK);
425 			result = file->WriteAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0,
426 				buffer.String(), (size_t)size);
427 			ASSERT(result == size);
428 			buffer.UnlockBuffer();
429 		}
430 		// default to query for everything
431 	}
432 
433 	fBackground->SaveWindowState(file, fEditTemplateOnly);
434 		// write out all the dialog items as attributes so that the query can
435 		// be reopened and edited later
436 
437 	BView *focusedItem = CurrentFocus();
438 	if (focusedItem) {
439 		// text controls never get the focus, their internal text views do
440 		BView *parent = focusedItem->Parent();
441 		if (dynamic_cast<BTextControl *>(parent))
442 			focusedItem = parent;
443 
444 		// write out the current focus and, if text control, selection
445 		BString name(focusedItem->Name());
446 		file->WriteAttrString("_trk/focusedView", &name);
447 		BTextControl *textControl = dynamic_cast<BTextControl *>(focusedItem);
448 		if (textControl) {
449 			int32 selStart, selEnd;
450 			textControl->TextView()->GetSelection(&selStart, &selEnd);
451 			file->WriteAttr("_trk/focusedSelStart", B_INT32_TYPE, 0,
452 				&selStart, sizeof(selStart));
453 			file->WriteAttr("_trk/focusedSelEnd", B_INT32_TYPE, 0,
454 				&selEnd, sizeof(selEnd));
455 		}
456 	}
457 	return B_OK;
458 }
459 
460 
461 void
462 FindWindow::Save()
463 {
464 	FindSaveCommon(false);
465 
466 	// close the find panel
467 	PostMessage(B_QUIT_REQUESTED);
468 }
469 
470 
471 void
472 FindWindow::Find()
473 {
474 	if (!FindSaveCommon(true)) {
475 		// have to wait for the node monitor to force old query to close
476 		// to avoid a race condition
477 		TTracker *tracker = dynamic_cast<TTracker *>(be_app);
478 		ASSERT(tracker);
479 		for (int32 timeOut = 0; ; timeOut++) {
480 			if (!tracker->EntryHasWindowOpen(&fRef))
481 				// window quit, we can post refs received to open a
482 				// new copy
483 				break;
484 
485 			// PRINT(("waiting for query window to quit, %d\n", timeOut));
486 			if (timeOut == 5000) {
487 				// the old query window would not quit for some reason
488 				TRESPASS();
489 				PostMessage(B_QUIT_REQUESTED);
490 				return;
491 			}
492 			snooze(1000);
493 		}
494 	}
495 
496 	int32 currentTime = (int32)time(0);
497 	fFile->WriteAttr(kAttrQueryLastChange, B_INT32_TYPE, 0, &currentTime, sizeof(int32));
498 
499 	// tell the tracker about it
500 	BMessage message(B_REFS_RECEIVED);
501 	message.AddRef("refs", &fRef);
502 	be_app->PostMessage(&message);
503 
504 	// close the find panel
505 	PostMessage(B_QUIT_REQUESTED);
506 }
507 
508 
509 bool
510 FindWindow::FindSaveCommon(bool find)
511 {
512 	// figure out what we need to do
513 	bool readFromOldFile = fFile != NULL;
514 	bool replaceOriginal = fFile && (!fFromTemplate || fEditTemplateOnly);
515 	bool keepPoseLocation = replaceOriginal;
516 	bool newFile = !fFile || (fFromTemplate && !fEditTemplateOnly);
517 
518 	BEntry entry;
519 	BMessage oldAttributes;
520 	BPoint location;
521 	bool hadLocation = false;
522 	const char *userSpecifiedName = fBackground->UserSpecifiedName();
523 
524 	if (readFromOldFile) {
525 		entry.SetTo(&fRef);
526 		BContainerWindow::GetLayoutState(fFile, &oldAttributes);
527 		hadLocation = FSGetPoseLocation(fFile, &location);
528 	}
529 
530 	if (replaceOriginal) {
531 		fFile->Unset();
532 		entry.Remove();
533 			// remove the current entry - need to do this to quit the
534 			// running query and to close the corresponding window
535 
536 		if (userSpecifiedName && !fEditTemplateOnly) {
537 			// change the name of the old query per users request
538 			fRef.set_name(userSpecifiedName);
539 			entry.SetTo(&fRef);
540 		}
541 	}
542 
543 	if (newFile) {
544 		// create query file in the user's directory
545 		BPath path;
546 		find_directory(B_USER_DIRECTORY, &path, true);
547 		path.Append("queries");
548 		// there might be no queries folder yet, create one
549 		mkdir(path.Path(), 0777);
550 
551 		// either use the user specified name, or go with the name
552 		// generated from the predicate, etc.
553 		if (!userSpecifiedName) {
554 			BString text;
555 			GetDefaultName(text);
556 			path.Append(text.String());
557 		} else
558 			path.Append(userSpecifiedName);
559 
560 		entry.SetTo(path.Path());
561 		entry.Remove();
562 		entry.GetRef(&fRef);
563 	}
564 
565 	fFile = new BFile(&entry, O_RDWR | O_CREAT);
566 	ASSERT(fFile->InitCheck() == B_OK);
567 
568 	SaveQueryAsAttributes(fFile, &entry, !find, newFile ? 0 : &oldAttributes,
569 		(hadLocation && keepPoseLocation) ? &location : 0);
570 
571 	return newFile;
572 }
573 
574 
575 void
576 FindWindow::MessageReceived(BMessage *message)
577 {
578 	switch (message->what) {
579 		case kFindButton:
580 			Find();
581 			break;
582 
583 		case kSaveButton:
584 			Save();
585 			break;
586 
587 		case kAttachFile:
588 			{
589 				entry_ref dir;
590 				const char *name;
591 				bool queryTemplate;
592 				if (message->FindString("name", &name) == B_OK
593 					&& message->FindRef("directory", &dir) == B_OK
594 					&& message->FindBool("template", &queryTemplate) == B_OK) {
595 					delete fFile;
596 					fFile = NULL;
597 					BDirectory directory(&dir);
598 					BEntry entry(&directory, name);
599 					entry_ref tmpRef;
600 					entry.GetRef(&tmpRef);
601 					fFile = TryOpening(&tmpRef);
602 					if (fFile) {
603 						fRef = tmpRef;
604 						SaveQueryAsAttributes(fFile, &entry, queryTemplate, 0, 0);
605 							// try to save whatever state we aleady have
606 							// to the new query so that if the user
607 							// opens it before runing it from the find panel,
608 							// something reasonable happens
609 					}
610 				}
611 			}
612 			break;
613 
614 		case kSwitchToQueryTemplate:
615 			{
616 				entry_ref ref;
617 				if (message->FindRef("refs", &ref) == B_OK)
618 					SwitchToTemplate(&ref);
619 			}
620 			break;
621 
622 		case kRunSaveAsTemplatePanel:
623 			if (fSaveAsTemplatePanel)
624 				fSaveAsTemplatePanel->Show();
625 			else {
626 				BMessenger panel(BackgroundView());
627 				fSaveAsTemplatePanel = new BFilePanel(B_SAVE_PANEL, &panel);
628 				fSaveAsTemplatePanel->SetSaveText("Query template");
629 				fSaveAsTemplatePanel->Window()->SetTitle("Save As Query Template:");
630 				fSaveAsTemplatePanel->Show();
631 			}
632 			break;
633 
634 		default:
635 			_inherited::MessageReceived(message);
636 			break;
637 	}
638 }
639 
640 
641 //	#pragma mark -
642 
643 
644 FindPanel::FindPanel(BRect frame, BFile *node, FindWindow *parent,
645 	bool , bool editTemplateOnly)
646 	:	BView(frame, "MainView", B_FOLLOW_ALL, B_WILL_DRAW),
647 		fMode(kByNameItem),
648 		fDraggableIcon(NULL)
649 {
650 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
651 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
652 
653 	uint32 initialMode = InitialMode(node);
654 
655 	BRect bounds(Bounds());
656 	BRect boxBounds(bounds);
657 
658 	boxBounds.InsetBy(15, 30);
659 	boxBounds.bottom -= 10;
660 	AddChild(new BBox(boxBounds, "Box"));
661 
662 	BRect rect(boxBounds);
663 	rect.top -= 25;
664 	rect.right = rect.left + 20;
665 	rect.bottom = rect.top + 20;
666 
667 	BMessenger self(this);
668 	fRecentQueries = new BPopUpMenu("RecentQueries");
669 	FindPanel::AddRecentQueries(fRecentQueries, true, &self, kSwitchToQueryTemplate);
670 
671 	AddChild(new MiniMenuField(rect, "RecentQueries", fRecentQueries));
672 
673 	rect.left = rect.right + 15;
674 
675 	// add popup for mime types
676 	fMimeTypeMenu = new BPopUpMenu("MimeTypeMenu");
677 	fMimeTypeMenu->SetRadioMode(false);
678 	AddMimeTypesToMenu();
679 
680 	rect.right = rect.left + 150;
681 	fMimeTypeField = new BMenuField(rect, "MimeTypeMenu", "", fMimeTypeMenu);
682 	fMimeTypeField->SetDivider(0.0f);
683 	fMimeTypeField->MenuItem()->SetLabel("All files and folders");
684 	AddChild(fMimeTypeField);
685 
686 	// add popup for search criteria
687 	fSearchModeMenu = new BPopUpMenu("searchMode");
688 	fSearchModeMenu->AddItem(new BMenuItem("by Name", new BMessage(kByNameItem)));
689 	fSearchModeMenu->AddItem(new BMenuItem("by Attribute", new BMessage(kByAttributeItem)));
690 	fSearchModeMenu->AddItem(new BMenuItem("by Formula", new BMessage(kByForumlaItem)));
691 
692 	fSearchModeMenu->ItemAt(initialMode == kByNameItem ? 0 :
693 		(initialMode == kByAttributeItem ? 1 : 2))->SetMarked(true);
694 		// mark the appropriate mode
695 	rect.left = rect.right + 10;
696 	rect.right = rect.left + 100;
697 	rect.bottom = rect.top + 15;
698 	BMenuField *menuField = new BMenuField(rect, "", "", fSearchModeMenu);
699 	menuField->SetDivider(0.0f);
700 	AddChild(menuField);
701 
702 	// add popup for volume list
703 	rect.right = bounds.right - 15;
704 	rect.left = rect.right - 100;
705 	fVolMenu = new BPopUpMenu("", false, false);	// don't radioMode
706 	menuField = new BMenuField(rect, "", "On", fVolMenu);
707 	menuField->SetDivider(menuField->StringWidth(menuField->Label()) + 8);
708 	AddChild(menuField);
709 	AddVolumes(fVolMenu);
710 
711 	if (!editTemplateOnly) {
712 		BPoint draggableIconOrigin(15, bounds.bottom - 35);
713 		BMessage dragNDropMessage(B_SIMPLE_DATA);
714 		dragNDropMessage.AddInt32("be:actions", B_COPY_TARGET);
715 		dragNDropMessage.AddString("be:types", B_FILE_MIME_TYPE);
716 		dragNDropMessage.AddString("be:filetypes", kDragNDropTypes[0]);
717 		dragNDropMessage.AddString("be:filetypes", kDragNDropTypes[1]);
718 		dragNDropMessage.AddString("be:actionspecifier", kDragNDropActionSpecifiers[0]);
719 		dragNDropMessage.AddString("be:actionspecifier", kDragNDropActionSpecifiers[1]);
720 
721 		BMessenger self(this);
722 		fDraggableIcon = new DraggableQueryIcon(DraggableIcon::PreferredRect(draggableIconOrigin,
723 			B_LARGE_ICON), "saveHere", &dragNDropMessage,
724 			self, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
725 		AddChild(fDraggableIcon);
726 	}
727 
728 	// add the more options collapsible pane
729 	BRect paneInitialRect(bounds);
730 	paneInitialRect.InsetBy(80, 5);
731 	paneInitialRect.right = paneInitialRect.left + 255;
732 	paneInitialRect.top = paneInitialRect.bottom - 30;
733 	BRect paneExpandedRect(paneInitialRect);
734 	paneExpandedRect.bottom += kMoreOptionsDelta;
735 	fMoreOptionsPane = new DialogPane(paneInitialRect, paneExpandedRect, 0,
736 		"moreOptions", B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
737 	AddChild(fMoreOptionsPane);
738 
739 	fMoreOptionsPane->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
740 	fMoreOptionsPane->SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
741 
742 	// set up the contents of the more options pane
743 	BRect expandedBounds(paneExpandedRect);
744 	expandedBounds.OffsetTo(0, 0);
745 	expandedBounds.InsetBy(5, 5);
746 
747 	rect = expandedBounds;
748 	rect.right = rect.left + 200;
749 	rect.bottom = rect.top + 20;;
750 	fQueryName = new BTextControl(rect, "queryName", "Query name:", "", 0);
751 	fQueryName->SetDivider(fQueryName->StringWidth(fQueryName->Label()) + 5);
752 	fMoreOptionsPane->AddItem(fQueryName, 1);
753 	FillCurrentQueryName(fQueryName, parent);
754 
755 	rect.top = rect.bottom + 6;
756 	rect.bottom = rect.top + 16;
757 	rect.right = rect.left + 100;
758 	fSearchTrashCheck = new BCheckBox(rect, "searchTrash", "Include trash", 0);
759 	fMoreOptionsPane->AddItem(fSearchTrashCheck, 1);
760 
761 	rect.OffsetBy(120, 0);
762 	fTemporaryCheck = new BCheckBox(rect, "temporary", "Temporary", 0);
763 	fMoreOptionsPane->AddItem(fTemporaryCheck, 1);
764 	fTemporaryCheck->SetValue(1);
765 
766 	BRect latchRect(paneInitialRect);
767 	latchRect.left -= 20;
768 	latchRect.right = latchRect.left + 10;
769 	latchRect.top = paneInitialRect.top + paneInitialRect.Height() / 2 - 5;
770 
771 	latchRect.bottom = latchRect.top + 12;
772 
773 	fLatch = new PaneSwitch(latchRect, "moreOptionsLatch", true,
774 		B_FOLLOW_BOTTOM | B_FOLLOW_LEFT);
775 	AddChild(fLatch);
776 	fMoreOptionsPane->SetSwitch(fLatch);
777 
778 	if (initialMode != kByAttributeItem)
779 		AddByNameOrFormulaItems();
780 	else
781 		AddByAttributeItems(node);
782 
783 	// add Search button
784 	rect = bounds;
785 	rect.left = rect.right - 80;
786 	rect.top = rect.bottom - 30;
787 	rect.right = rect.left + 60;
788 	rect.bottom = rect.top + 20;
789 	BButton *button;
790 	if (editTemplateOnly)
791 		button = new BButton(rect, "save", "Save",
792 			new BMessage(kSaveButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
793 	else
794 		button = new BButton(rect, "find", "Search",
795 			new BMessage(kFindButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
796 
797 	AddChild(button);
798 	button->MakeDefault(true);
799 }
800 
801 
802 FindPanel::~FindPanel()
803 {
804 }
805 
806 
807 void
808 FindPanel::AttachedToWindow()
809 {
810 	BNode *node = dynamic_cast<FindWindow *>(Window())->QueryNode();
811 	fSearchModeMenu->SetTargetForItems(this);
812 	fQueryName->SetTarget(this);
813 	fLatch->SetTarget(fMoreOptionsPane);
814 	RestoreMimeTypeMenuSelection(node);
815 		// preselect the mime we used the last time
816 		// have to do it here because AddByAttributeItems will build different
817 		// menus based on which mime type is preselected
818 	RestoreWindowState(node);
819 
820 	if (!Window()->CurrentFocus()) {
821 		// try to pick a good focus if we restore to one already
822 		BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
823 		if (!textControl) {
824 			// pick the last text control in the attribute view
825 			BString title("TextEntry");
826 			title << (fAttrViewList.CountItems() - 1);
827 			textControl = dynamic_cast<BTextControl *>(FindView(title.String()));
828 		}
829 		if (textControl)
830 			textControl->MakeFocus();
831 	}
832 
833 	BButton *button = dynamic_cast<BButton *>(FindView("remove"));
834 	if (button)
835 		button->SetTarget(this);
836 
837 	button = dynamic_cast<BButton *>(FindView("add"));
838 	if (button)
839 		button->SetTarget(this);
840 
841 	fVolMenu->SetTargetForItems(this);
842 
843 	// set target for MIME type items
844 	for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) {
845 		BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu();
846 		if (submenu != NULL)
847 				submenu->SetTargetForItems(this);
848 	}
849 	fMimeTypeMenu->SetTargetForItems(this);
850 
851 
852 	if (fDraggableIcon)
853 		fDraggableIcon->SetTarget(BMessenger(this));
854 
855 	fRecentQueries->SetTargetForItems(Window());
856 }
857 
858 const float kAttrViewDelta = 30;
859 
860 BRect
861 FindPanel::InitialViewSize(const BNode *node)
862 {
863 	if (!node || InitialMode(node) != (int32)kByAttributeItem)
864 		return kInitialRect;
865 
866 	int32 numAttributes = InitialAttrCount(node);
867 	if (numAttributes < 1)
868 		numAttributes = 1;
869 
870 	BRect result = kInitialRect;
871 	result.bottom = result.top + kInitialAttrModeWindowHeight
872 		+ (numAttributes - 1) * kIncrementPerAttribute;
873 
874 	return result;
875 }
876 
877 
878 float
879 FindPanel::ViewHeightForMode(uint32 mode, bool moreOptions)
880 {
881 	float result = moreOptions ? kMoreOptionsDelta : 0;
882 	switch (mode) {
883 		case kByForumlaItem:
884 		case kByNameItem:
885 			return 110 + result;
886 
887 		case kByAttributeItem:
888 			return 110 + result + kAttrViewDelta;
889 
890 	}
891 	TRESPASS();
892 	return 0;
893 }
894 
895 
896 float
897 FindPanel::BoxHeightForMode(uint32 mode, bool /*moreOptions*/)
898 {
899 	switch (mode) {
900 		case kByForumlaItem:
901 		case kByNameItem:
902 			return 40;
903 
904 		case kByAttributeItem:
905 			return 40 + kAttrViewDelta;
906 
907 	}
908 	TRESPASS();
909 	return 0;
910 }
911 
912 
913 static void
914 PopUpMenuSetTitle(BMenu *menu, const char *title)
915 {
916 	// This should really be in BMenuField
917 	BMenu *bar = menu->Supermenu();
918 
919 	ASSERT(bar);
920 	ASSERT(bar->ItemAt(0));
921 	if (!bar || !bar->ItemAt(0))
922 		return;
923 
924 	bar->ItemAt(0)->SetLabel(title);
925 }
926 
927 
928 void
929 FindPanel::ShowVolumeMenuLabel()
930 {
931 	if (fVolMenu->ItemAt(0)->IsMarked()) {
932 		// "all disks" selected
933 		PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label());
934 		return;
935 	}
936 
937 	// find out if more than one items are marked
938 	int32 count = fVolMenu->CountItems();
939 	int32 countSelected = 0;
940 	BMenuItem *tmpItem = NULL;
941 	for (int32 index = 2; index < count; index++) {
942 		BMenuItem *item = fVolMenu->ItemAt(index);
943 		if (item->IsMarked()) {
944 			countSelected++;
945 			tmpItem = item;
946 		}
947 	}
948 	if (countSelected == 0) {
949 		// no disk selected, for now revert to search all disks
950 		// ToDo:
951 		// show no disks here and add a check that will not let the
952 		// query go if the user doesn't pick at least one
953 		fVolMenu->ItemAt(0)->SetMarked(true);
954 		PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label());
955 	} else if (countSelected > 1)
956 		// if more than two disks selected, don't use the disk name
957 		// as a label
958 		PopUpMenuSetTitle(fVolMenu, "multiple disks");
959 	else {
960 		ASSERT(tmpItem);
961 		PopUpMenuSetTitle(fVolMenu, tmpItem->Label());
962 	}
963 }
964 
965 
966 void
967 FindPanel::MessageReceived(BMessage *message)
968 {
969 	entry_ref dir;
970 	const char *name;
971 
972 	switch (message->what) {
973 		case kVolumeItem:
974 			{
975 				// volume changed
976 				BMenuItem *invokedItem;
977 				dev_t dev;
978 				if (message->FindPointer("source", (void **)&invokedItem) != B_OK)
979 					return;
980 
981 				if (message->FindInt32("device", &dev) != B_OK)
982 					break;
983 
984 				BMenu *menu = invokedItem->Menu();
985 				ASSERT(menu);
986 
987 				if (dev == -1) {
988 
989 					// all disks selected, uncheck everything else
990 					int32 count = menu->CountItems();
991 					for (int32 index = 2; index < count; index++)
992 						menu->ItemAt(index)->SetMarked(false);
993 
994 					// make all disks the title and check it
995 					PopUpMenuSetTitle(menu, menu->ItemAt(0)->Label());
996 					menu->ItemAt(0)->SetMarked(true);
997 
998 				} else {
999 					// a specific volume selected, unmark "all disks"
1000 					menu->ItemAt(0)->SetMarked(false);
1001 
1002 					// toggle mark on invoked item
1003 					int32 count = menu->CountItems();
1004 					for (int32 index = 2; index < count; index++) {
1005 						BMenuItem *item = menu->ItemAt(index);
1006 
1007 						if (invokedItem == item) {
1008 							// we just selected this
1009 							bool wasMarked = item->IsMarked();
1010 							item->SetMarked(!wasMarked);
1011 						}
1012 					}
1013 				}
1014 				// make sure the right label is showing
1015 				ShowVolumeMenuLabel();
1016 
1017 				break;
1018 			}
1019 
1020 		case kByAttributeItem:
1021 		case kByNameItem:
1022 		case kByForumlaItem:
1023 			SwitchMode(message->what);
1024 			break;
1025 
1026 		case kAddItem:
1027 			AddAttrView();
1028 			break;
1029 
1030 		case kRemoveItem:
1031 			RemoveAttrView();
1032 			break;
1033 
1034 		case kMIMETypeItem:
1035 			{
1036 				BMenuItem *item;
1037 				if (message->FindPointer("source", (void **)&item) == B_OK) {
1038 					// don't add the "All files and folders" to the list
1039 					if (fMimeTypeMenu->IndexOf(item) != 0)
1040 						gMostUsedMimeTypes.AddName(item->Label());
1041 
1042 					SetCurrentMimeType(item);
1043 				}
1044 
1045 				// mime type switched
1046 				if (fMode != kByAttributeItem)
1047 					break;
1048 
1049 				// the attributes for this type may be different, rip out the existing ones
1050 				RemoveAttrViewItems();
1051 
1052 				Window()->ResizeTo(Window()->Frame().Width(),
1053 					ViewHeightForMode(kByAttributeItem, fLatch->Value() != 0));
1054 
1055 				BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1056 				ASSERT(box);
1057 				box->ResizeTo(box->Bounds().Width(),
1058 					BoxHeightForMode(kByAttributeItem, fLatch->Value() != 0));
1059 
1060 				AddAttrView();
1061 				break;
1062 			}
1063 
1064 		case kNameModifiedMessage:
1065 			// the query name was edited, make the query permanent
1066 			fTemporaryCheck->SetValue(0);
1067 			break;
1068 
1069 		case B_SAVE_REQUESTED:
1070 			{
1071 				// finish saving query template from a SaveAs panel
1072 				entry_ref ref;
1073 				status_t error = message->FindRef("refs", &ref);
1074 
1075 				if (error == B_OK) {
1076 					// direct entry selected, convert to parent dir and name
1077 					BEntry entry(&ref);
1078 					error = entry.GetParent(&entry);
1079 					if (error == B_OK) {
1080 						entry.GetRef(&dir);
1081 						name = ref.name;
1082 					}
1083 				} else {
1084 					// parent dir and name selected
1085 					error = message->FindRef("directory", &dir);
1086 					if (error == B_OK)
1087 						error = message->FindString("name", &name);
1088 				}
1089 				if (error == B_OK)
1090 					SaveAsQueryOrTemplate(&dir, name, true);
1091 			}
1092 			break;
1093 
1094 		case B_COPY_TARGET:
1095 			{
1096 				// finish drag&drop
1097 				const char *str;
1098 				const char *mimeType = NULL;
1099 				const char *actionSpecifier = NULL;
1100 				if (message->FindString("be:types", &str) == B_OK
1101 					&& strcasecmp(str, B_FILE_MIME_TYPE) == 0
1102 					&& (message->FindString("be:actionspecifier", &actionSpecifier) == B_OK
1103 						|| message->FindString("be:filetypes", &mimeType) == B_OK)
1104 					&& message->FindString("name", &name) == B_OK
1105 					&& message->FindRef("directory", &dir) == B_OK) {
1106 
1107 					bool query = false;
1108 					bool queryTemplate = false;
1109 
1110 					if (actionSpecifier
1111 						&& strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[0]) == 0)
1112 						query = true;
1113 					else if (actionSpecifier
1114 						&& strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[1]) == 0)
1115 						queryTemplate = true;
1116 					else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[0]) == 0)
1117 						query = true;
1118 					else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[1]) == 0)
1119 						queryTemplate = true;
1120 
1121 					if (query || queryTemplate)
1122 						SaveAsQueryOrTemplate(&dir, name, queryTemplate);
1123 				}
1124 			}
1125 			break;
1126 
1127 		default:
1128 			_inherited::MessageReceived(message);
1129 			break;
1130 	}
1131 }
1132 
1133 
1134 void
1135 FindPanel::SaveAsQueryOrTemplate(const entry_ref *dir, const char *name, bool queryTemplate)
1136 {
1137 	BDirectory directory(dir);
1138 	BFile file(&directory, name, O_RDWR | O_CREAT | O_TRUNC);
1139 	BNodeInfo(&file).SetType(queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE);
1140 
1141 	BMessage attach(kAttachFile);
1142 	attach.AddRef("directory", dir);
1143 	attach.AddString("name", name);
1144 	attach.AddBool("template", queryTemplate);
1145 	Window()->PostMessage(&attach, 0);
1146 }
1147 
1148 
1149 void
1150 FindPanel::BuildAttrQuery(BQuery *query, bool &dynamicDate) const
1151 {
1152 	dynamicDate = false;
1153 
1154 	// go through each attrview and add the attr and comparison info
1155 	for (int32 index = 0; index < fAttrViewList.CountItems(); index++) {
1156 
1157 		TAttrView *view = fAttrViewList.ItemAt(index);
1158 		BString title;
1159 		title << "TextEntry" << index;
1160 
1161 		BTextControl *textControl = dynamic_cast<BTextControl *>
1162 			(view->FindView(title.String()));
1163 		if (!textControl)
1164 			return;
1165 
1166 		BMenuField *menuField = dynamic_cast<BMenuField *>(view->FindView("MenuField"));
1167 		if (!menuField)
1168 			return;
1169 
1170 		BMenuItem *item = menuField->Menu()->FindMarked();
1171 		if (!item)
1172 			continue;
1173 
1174 		BMessage *message = item->Message();
1175 		int32 type;
1176 		if (message->FindInt32("type", &type) == B_OK) {
1177 
1178 			const char *str;
1179 			if (message->FindString("name", &str) == B_OK)
1180 				query->PushAttr(str);
1181 			else
1182 				query->PushAttr(item->Label());
1183 
1184 			switch (type) {
1185 				case B_STRING_TYPE:
1186 					query->PushString(textControl->TextView()->Text(), true);
1187 					break;
1188 
1189 				case B_TIME_TYPE:
1190 					{
1191 						int flags = 0;
1192 						DEBUG_ONLY(time_t result =)
1193 						parsedate_etc(textControl->TextView()->Text(), -1, &flags);
1194 						dynamicDate = (flags & PARSEDATE_RELATIVE_TIME) != 0;
1195 						PRINT(("parsedate_etc - date is %srelative, %l\n",
1196 							dynamicDate ? "" : "not ", result));
1197 
1198 						query->PushDate(textControl->TextView()->Text());
1199 					}
1200 					break;
1201 
1202 				case B_BOOL_TYPE:
1203 					{
1204 						uint32 value;
1205 						if (strcasecmp(textControl->TextView()->Text(), "true") == 0)
1206 							value = 1;
1207 						else if (strcasecmp(textControl->TextView()->Text(), "true") == 0)
1208 							value = 1;
1209 						else
1210 							value = (uint32)atoi(textControl->TextView()->Text());
1211 
1212 						value %= 2;
1213 						query->PushUInt32(value);
1214 					}
1215 					break;
1216 
1217 				case B_UINT8_TYPE:
1218 				case B_UINT16_TYPE:
1219 				case B_UINT32_TYPE:
1220 					query->PushUInt32((uint32)StringToScalar(textControl->TextView()->Text()));
1221 					break;
1222 
1223 				case B_INT8_TYPE:
1224 				case B_INT16_TYPE:
1225 				case B_INT32_TYPE:
1226 					query->PushInt32((int32)StringToScalar(textControl->TextView()->Text()));
1227 					break;
1228 
1229 				case B_UINT64_TYPE:
1230 					query->PushUInt64((uint64)StringToScalar(textControl->TextView()->Text()));
1231 					break;
1232 
1233 				case B_OFF_T_TYPE:
1234 				case B_INT64_TYPE:
1235 					query->PushInt64(StringToScalar(textControl->TextView()->Text()));
1236 					break;
1237 
1238 				case B_FLOAT_TYPE:
1239 					{
1240 						float floatVal;
1241 						sscanf(textControl->TextView()->Text(), "%f", &floatVal);
1242 						query->PushFloat(floatVal);
1243 					}
1244 					break;
1245 
1246 				case B_DOUBLE_TYPE:
1247 					{
1248 						double doubleVal;
1249 						sscanf(textControl->TextView()->Text(), "%lf", &doubleVal);
1250 						query->PushDouble(doubleVal);
1251 					}
1252 					break;
1253 
1254 			}
1255 		}
1256 
1257 		query_op theOperator;
1258 		BMenuItem *operatorItem = item->Submenu()->FindMarked();
1259 		if (operatorItem && operatorItem->Message() != NULL) {
1260 			operatorItem->Message()->FindInt32("operator", (int32 *)&theOperator);
1261 			query->PushOp(theOperator);
1262 		} else
1263 			query->PushOp(B_EQ);
1264 
1265 		// add logic based on selection in Logic menufield
1266 		if (index > 0) {
1267 			TAttrView *prevView = fAttrViewList.ItemAt(index - 1);
1268 			menuField = dynamic_cast<BMenuField *>(prevView->FindView("Logic"));
1269 			if (menuField) {
1270 				item = menuField->Menu()->FindMarked();
1271 				if (item) {
1272 					message = item->Message();
1273 					message->FindInt32("combine", (int32 *)&theOperator);
1274 					query->PushOp(theOperator);
1275 				}
1276 			} else
1277 				query->PushOp(B_AND);
1278 		}
1279 	}
1280 }
1281 
1282 
1283 void
1284 FindPanel::PushMimeType(BQuery *query) const
1285 {
1286 	const char *type;
1287 	if (CurrentMimeType(&type) == NULL)
1288 		return;
1289 
1290 	if (strcmp(kAllMimeTypes, type)) {
1291 		// add an asterisk if we are searching for a supertype
1292 		char buffer[B_FILE_NAME_LENGTH];
1293 		if (strchr(type,'/') == NULL) {
1294 			strcpy(buffer,type);
1295 			strcat(buffer,"/*");
1296 			type = buffer;
1297 		}
1298 
1299 		query->PushAttr(kAttrMIMEType);
1300 		query->PushString(type);
1301 		query->PushOp(B_EQ);
1302 		query->PushOp(B_AND);
1303 	}
1304 }
1305 
1306 
1307 void
1308 FindPanel::GetByAttrPredicate(BQuery *query, bool &dynamicDate) const
1309 {
1310 	ASSERT(Mode() == (int32)kByAttributeItem);
1311 	BuildAttrQuery(query, dynamicDate);
1312 	PushMimeType(query);
1313 }
1314 
1315 
1316 void
1317 FindPanel::GetDefaultName(BString &result) const
1318 {
1319 	BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
1320 	switch (Mode()) {
1321 		case kByNameItem:
1322 			result << "Name = " << textControl->TextView()->Text();
1323 			break;
1324 
1325 		case kByForumlaItem:
1326 			result << "Formula " << textControl->TextView()->Text();
1327 			break;
1328 
1329 		case kByAttributeItem:
1330 		{
1331 			BMenuItem *item = fMimeTypeMenu->FindMarked();
1332 			if (item != NULL)
1333 				result << item->Label() << ": ";
1334 
1335 			for (int32 i = 0; i < fAttrViewList.CountItems(); i++) {
1336 				fAttrViewList.ItemAt(i)->GetDefaultName(result);
1337 				if (i + 1 < fAttrViewList.CountItems())
1338 					result << ", ";
1339 			}
1340 			break;
1341 		}
1342 	}
1343 }
1344 
1345 
1346 const char *
1347 FindPanel::UserSpecifiedName() const
1348 {
1349 	if (fQueryName->Text()[0] == '\0')
1350 		return NULL;
1351 
1352 	return fQueryName->Text();
1353 }
1354 
1355 
1356 void
1357 FindPanel::GetByNamePredicate(BQuery *query) const
1358 {
1359 	ASSERT(Mode() == (int32)kByNameItem);
1360 	BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
1361 	ASSERT(textControl);
1362 
1363 	query->PushAttr("name");
1364 	query->PushString(textControl->TextView()->Text(), true);
1365 
1366 	if (strstr(textControl->TextView()->Text(), "*"))
1367 		// assume pattern is a regular expression and try doing an exact match
1368 		query->PushOp(B_EQ);
1369 	else
1370 		query->PushOp(B_CONTAINS);
1371 
1372 	PushMimeType(query);
1373 }
1374 
1375 
1376 void
1377 FindPanel::SwitchMode(uint32 mode)
1378 {
1379 	if (fMode == mode)
1380 		// no work, bail
1381 		return;
1382 
1383 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1384 	ASSERT(box);
1385 
1386 	uint32 oldMode = fMode;
1387 	BString buffer;
1388 
1389 	switch (mode) {
1390 		case kByForumlaItem:
1391 			if (oldMode == kByAttributeItem || oldMode == kByNameItem) {
1392 				BQuery query;
1393 				if (oldMode == kByAttributeItem) {
1394 					bool dummy;
1395 					GetByAttrPredicate(&query, dummy);
1396 				} else
1397 					GetByNamePredicate(&query);
1398 
1399 				query.GetPredicate(&buffer);
1400 			}
1401 			// fall thru
1402 
1403 		case kByNameItem:
1404 			{
1405 				fMode = mode;
1406 				Window()->ResizeTo(Window()->Frame().Width(),
1407 					ViewHeightForMode(mode, fLatch->Value() != 0));
1408 				BRect bounds(Bounds());
1409 				bounds.InsetBy(15, 30);
1410 				bounds.bottom -= 10;
1411 				if (fLatch->Value())
1412 					bounds.bottom -= kMoreOptionsDelta;
1413 				box->ResizeTo(bounds.Width(), BoxHeightForMode(mode, fLatch->Value() != 0));
1414 
1415 				RemoveByAttributeItems();
1416 				ShowOrHideMimeTypeMenu();
1417 				AddByNameOrFormulaItems();
1418 
1419 				if (buffer.Length()) {
1420 					ASSERT(mode == kByForumlaItem || oldMode == kByAttributeItem);
1421 					BTextControl *textControl = dynamic_cast<BTextControl *>
1422 						(FindView("TextControl"));
1423 					textControl->SetText(buffer.String());
1424 				}
1425 				break;
1426 			}
1427 
1428 		case kByAttributeItem:
1429 			{
1430 				fMode = mode;
1431 				box->ResizeTo(box->Bounds().Width(),
1432 					BoxHeightForMode(mode, fLatch->Value() != 0));
1433 
1434 				Window()->ResizeTo(Window()->Frame().Width(),
1435 					ViewHeightForMode(mode, fLatch->Value() != 0));
1436 
1437 				BTextControl *textControl = dynamic_cast<BTextControl *>
1438 					(FindView("TextControl"));
1439 
1440 				if (textControl) {
1441 					textControl->RemoveSelf();
1442 					delete textControl;
1443 				}
1444 
1445 				ShowOrHideMimeTypeMenu();
1446 				AddAttrView();
1447 				break;
1448 			}
1449 	}
1450 }
1451 
1452 
1453 BMenuItem *
1454 FindPanel::CurrentMimeType(const char **type) const
1455 {
1456 	// search for marked item in the list
1457 	BMenuItem *item = MimeTypeMenu()->FindMarked();
1458 
1459 	// if it's one of the most used items, ignore it
1460 	if (item != NULL && MimeTypeMenu()->IndexOf(item) != 0 && item->Submenu() == NULL)
1461 		item = NULL;
1462 
1463 	if (item == NULL) {
1464 		for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) {
1465 			BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu();
1466 			if (submenu != NULL && (item = submenu->FindMarked()) != NULL)
1467 				break;
1468 		}
1469 	}
1470 
1471 	if (type && item != NULL) {
1472 		BMessage *message = item->Message();
1473 		if (!message)
1474 			return NULL;
1475 
1476 		if (message->FindString("mimetype", type) != B_OK)
1477 			return NULL;
1478 	}
1479 	return item;
1480 }
1481 
1482 
1483 status_t
1484 FindPanel::SetCurrentMimeType(BMenuItem *item)
1485 {
1486 	// unmark old MIME type (in most used list, and the tree)
1487 
1488 	BMenuItem *marked = CurrentMimeType();
1489 	if (marked != NULL) {
1490 		marked->SetMarked(false);
1491 
1492 		if ((marked = MimeTypeMenu()->FindMarked()) != NULL)
1493 			marked->SetMarked(false);
1494 	}
1495 
1496 	// mark new MIME type (in most used list, and the tree)
1497 
1498 	if (item != NULL) {
1499 		item->SetMarked(true);
1500 		fMimeTypeField->MenuItem()->SetLabel(item->Label());
1501 
1502 		BMenuItem *search;
1503 		for (int32 i = 2;(search = MimeTypeMenu()->ItemAt(i)) != NULL;i++) {
1504 			if (item == search || !search->Label())
1505 				continue;
1506 			if (!strcmp(item->Label(),search->Label())) {
1507 				search->SetMarked(true);
1508 				break;
1509 			}
1510 			BMenu *submenu = search->Submenu();
1511 			if (submenu) {
1512 				for (int32 j = submenu->CountItems();j-- > 0;) {
1513 					BMenuItem *sub = submenu->ItemAt(j);
1514 					if (!strcmp(item->Label(),sub->Label())) {
1515 						sub->SetMarked(true);
1516 						break;
1517 					}
1518 				}
1519 			}
1520 		}
1521 	}
1522 	return B_OK;
1523 }
1524 
1525 
1526 status_t
1527 FindPanel::SetCurrentMimeType(const char *label)
1528 {
1529 	// unmark old MIME type (in most used list, and the tree)
1530 
1531 	BMenuItem *marked = CurrentMimeType();
1532 	if (marked != NULL) {
1533 		marked->SetMarked(false);
1534 
1535 		if ((marked = MimeTypeMenu()->FindMarked()) != NULL)
1536 			marked->SetMarked(false);
1537 	}
1538 
1539 	// mark new MIME type (in most used list, and the tree)
1540 
1541 	fMimeTypeField->MenuItem()->SetLabel(label);
1542 	bool found = false;
1543 
1544 	for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) {
1545 		BMenuItem *item = MimeTypeMenu()->ItemAt(index);
1546 		BMenu *submenu = item->Submenu();
1547 		if (submenu != NULL && !found) {
1548 			for (int32 subIndex = submenu->CountItems(); subIndex-- > 0;) {
1549 				BMenuItem *subItem = submenu->ItemAt(subIndex);
1550 				if (subItem->Label() != NULL && !strcmp(label, subItem->Label())) {
1551 					subItem->SetMarked(true);
1552 					found = true;
1553 				}
1554 			}
1555 		}
1556 		if (item->Label() != NULL && !strcmp(label, item->Label())) {
1557 			item->SetMarked(true);
1558 			return B_OK;
1559 		}
1560 	}
1561 
1562 	return found ? B_OK : B_ENTRY_NOT_FOUND;
1563 }
1564 
1565 
1566 bool
1567 FindPanel::AddOneMimeTypeToMenu(const ShortMimeInfo *info, void *castToMenu)
1568 {
1569 	BPopUpMenu *menu = static_cast<BPopUpMenu *>(castToMenu);
1570 
1571 	BMimeType type(info->InternalName());
1572 	BMimeType super;
1573 	type.GetSupertype(&super);
1574 	if (super.InitCheck() < B_OK)
1575 		return false;
1576 
1577 	BMenuItem *superItem = menu->FindItem(super.Type());
1578 	if (superItem != NULL) {
1579 		BMessage *msg = new BMessage(kMIMETypeItem);
1580 		msg->AddString("mimetype", info->InternalName());
1581 
1582 		superItem->Submenu()->AddItem(new BMenuItem(info->ShortDescription(), msg));
1583 	}
1584 
1585 	return false;
1586 }
1587 
1588 
1589 void
1590 FindPanel::AddMimeTypesToMenu()
1591 {
1592 	BMessage *itemMessage = new BMessage(kMIMETypeItem);
1593 	itemMessage->AddString("mimetype", kAllMimeTypes);
1594 	MimeTypeMenu()->AddItem(new BMenuItem("All files and folders", itemMessage));
1595 	MimeTypeMenu()->AddSeparatorItem();
1596 	MimeTypeMenu()->ItemAt(0)->SetMarked(true);
1597 
1598 	// add recent MIME types
1599 
1600 	TTracker *tracker = dynamic_cast<TTracker *>(be_app);
1601 
1602 	BList list;
1603 	if (gMostUsedMimeTypes.ObtainList(&list) && tracker) {
1604 		int32 count = 0;
1605 		for (int32 index = 0; index < list.CountItems(); index++) {
1606 			const char *name = (const char *)list.ItemAt(index);
1607 
1608 			const ShortMimeInfo *info;
1609 			if ((info = tracker->MimeTypes()->FindMimeType(name)) == NULL)
1610 				continue;
1611 
1612 			BMessage *message = new BMessage(kMIMETypeItem);
1613 			message->AddString("mimetype", info->InternalName());
1614 
1615 			MimeTypeMenu()->AddItem(new BMenuItem(name, message));
1616 			count++;
1617 		}
1618 		if (count != 0)
1619 			MimeTypeMenu()->AddSeparatorItem();
1620 
1621 		gMostUsedMimeTypes.ReleaseList();
1622 	}
1623 
1624 	// add MIME type tree list
1625 
1626 	BMessage types;
1627 	if (BMimeType::GetInstalledSupertypes(&types) == B_OK) {
1628 		const char *superType;
1629 		int32 index = 0;
1630 
1631 		while (types.FindString("super_types",index++,&superType) == B_OK) {
1632 			BMenu *superMenu = new BMenu(superType);
1633 
1634 			BMessage *message = new BMessage(kMIMETypeItem);
1635 			message->AddString("mimetype",superType);
1636 
1637 			MimeTypeMenu()->AddItem(new BMenuItem(superMenu,message));
1638 
1639 			// the MimeTypeMenu's font is not correct at this time
1640 			superMenu->SetFont(be_plain_font);
1641 		}
1642 	}
1643 
1644 	if (tracker)
1645 		tracker->MimeTypes()->EachCommonType(&FindPanel::AddOneMimeTypeToMenu,
1646 			MimeTypeMenu());
1647 
1648 	// remove empty super type menus (and set target)
1649 
1650 	for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) {
1651 		BMenuItem *item = MimeTypeMenu()->ItemAt(index);
1652 		BMenu *submenu = item->Submenu();
1653 		if (submenu != NULL) {
1654 			if (submenu->CountItems() == 0) {
1655 				MimeTypeMenu()->RemoveItem(item);
1656 				delete item;
1657 			} else
1658 				submenu->SetTargetForItems(this);
1659 		}
1660 	}
1661 
1662 	MimeTypeMenu()->SetTargetForItems(this);
1663 }
1664 
1665 
1666 void
1667 FindPanel::AddVolumes(BMenu *menu)
1668 {
1669 // ToDo: add calls to this to rebuild the menu when a volume gets mounted
1670 
1671 	BMessage *message = new BMessage(kVolumeItem);
1672 	message->AddInt32("device", -1);
1673 	menu->AddItem(new BMenuItem("All disks", message));
1674 	menu->AddSeparatorItem();
1675 	PopUpMenuSetTitle(menu, "All disks");
1676 
1677 	BVolumeRoster roster;
1678 	BVolume volume;
1679 	roster.Rewind();
1680 	while (roster.GetNextVolume(&volume) == B_OK) {
1681 		if (volume.IsPersistent() && volume.KnowsQuery()) {
1682 			BDirectory root;
1683 			if (volume.GetRootDirectory(&root) != B_OK)
1684 				continue;
1685 
1686 			BEntry entry;
1687 			root.GetEntry(&entry);
1688 
1689 			Model model(&entry, true);
1690 			if (model.InitCheck() != B_OK)
1691 				continue;
1692 
1693 			message = new BMessage(kVolumeItem);
1694 			message->AddInt32("device", volume.Device());
1695 			menu->AddItem(new ModelMenuItem(&model, model.Name(), message));
1696 		}
1697 	}
1698 
1699 	if (menu->ItemAt(0))
1700 		menu->ItemAt(0)->SetMarked(true);
1701 
1702 	menu->SetTargetForItems(this);
1703 }
1704 
1705 
1706 typedef std::pair<entry_ref, uint32> EntryWithDate;
1707 
1708 static int
1709 SortByDatePredicate(const EntryWithDate *entry1, const EntryWithDate *entry2)
1710 {
1711 	return entry1->second > entry2->second ?
1712 		-1 : (entry1->second == entry2->second ? 0 : 1);
1713 }
1714 
1715 struct AddOneRecentParams {
1716 	BMenu *menu;
1717 	const BMessenger *target;
1718 	uint32 what;
1719 };
1720 
1721 static const entry_ref *
1722 AddOneRecentItem(const entry_ref *ref, void *castToParams)
1723 {
1724 	AddOneRecentParams *params = (AddOneRecentParams *)castToParams;
1725 
1726 	BMessage *message = new BMessage(params->what);
1727 	message->AddRef("refs", ref);
1728 
1729 	char type[B_MIME_TYPE_LENGTH];
1730 	BNode node(ref);
1731 	BNodeInfo(&node).GetType(type);
1732 	BMenuItem *item = new IconMenuItem(ref->name, message, type, B_MINI_ICON);
1733 	item->SetTarget(*params->target);
1734 	params->menu->AddItem(item);
1735 
1736 	return NULL;
1737 }
1738 
1739 
1740 void
1741 FindPanel::AddRecentQueries(BMenu *menu, bool addSaveAsItem, const BMessenger *target,
1742 	uint32 what)
1743 {
1744 	BObjectList<entry_ref> templates(10, true);
1745 	BObjectList<EntryWithDate> recentQueries(10, true);
1746 
1747 	// find all the queries on all volumes
1748 	BVolumeRoster roster;
1749 	BVolume volume;
1750 	roster.Rewind();
1751 	while (roster.GetNextVolume(&volume) == B_OK) {
1752 		if (volume.IsPersistent() && volume.KnowsQuery() && volume.KnowsAttr()) {
1753 
1754 			BQuery query;
1755 			query.SetVolume(&volume);
1756 			query.SetPredicate("_trk/recentQuery == 1");
1757 			if (query.Fetch() != B_OK)
1758 				continue;
1759 
1760 			entry_ref ref;
1761 			while (query.GetNextRef(&ref) == B_OK) {
1762 				// ignore queries in the Trash
1763 				if (FSInTrashDir(&ref))
1764 					continue;
1765 
1766 				char type[B_MIME_TYPE_LENGTH];
1767 				BNode node(&ref);
1768 				BNodeInfo(&node).GetType(type);
1769 
1770 				if (strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0)
1771 					templates.AddItem(new entry_ref(ref));
1772 				else {
1773 					uint32 changeTime;
1774 					if (node.ReadAttr(kAttrQueryLastChange, B_INT32_TYPE, 0,
1775 						&changeTime, sizeof(uint32)) != sizeof(uint32))
1776 						continue;
1777 
1778 					recentQueries.AddItem(new EntryWithDate(ref, changeTime));
1779 				}
1780 
1781 			}
1782 		}
1783 	}
1784 
1785 	// we are only adding last ten queries
1786 	recentQueries.SortItems(SortByDatePredicate);
1787 
1788 	// but all templates
1789 	AddOneRecentParams params;
1790 	params.menu = menu;
1791 	params.target = target;
1792 	params.what = what;
1793 	templates.EachElement(AddOneRecentItem, &params);
1794 
1795 	int32 count = recentQueries.CountItems();
1796 	// show only up to 10 recent queries
1797 	if (count > 10)
1798 		count = 10;
1799 
1800 	if (templates.CountItems() && count)
1801 		menu->AddSeparatorItem();
1802 
1803 	for (int32 index = 0; index < count; index++)
1804 		AddOneRecentItem(&recentQueries.ItemAt(index)->first, &params);
1805 
1806 
1807 	if (addSaveAsItem) {
1808 		// add a Save as template item
1809 		if (count || templates.CountItems())
1810 			menu->AddSeparatorItem();
1811 
1812 		BMessage *message = new BMessage(kRunSaveAsTemplatePanel);
1813 		BMenuItem *item = new BMenuItem("Save Query as template"B_UTF8_ELLIPSIS, message);
1814 		menu->AddItem(item);
1815 	}
1816 }
1817 
1818 
1819 void
1820 FindPanel::AddOneAttributeItem(BBox *box, BRect rect)
1821 {
1822 	TAttrView *attrView = new TAttrView(rect, fAttrViewList.CountItems());
1823 	fAttrViewList.AddItem(attrView);
1824 
1825 	box->AddChild(attrView);
1826 	attrView->MakeTextViewFocus();
1827 }
1828 
1829 
1830 void
1831 FindPanel::SetUpAddRemoveButtons(BBox *box)
1832 {
1833 	BButton *button = Window() != NULL
1834 		? dynamic_cast<BButton *>(Window()->FindView("remove"))
1835 		: NULL;
1836 	if (button == NULL) {
1837 		BRect rect = box->Bounds();
1838 		rect.InsetBy(5, 10);
1839 		rect.top = rect.bottom - 20;
1840 		rect.right = rect.left + 40;
1841 
1842 		button = new BButton(rect, "add", "Add", new BMessage(kAddItem),
1843 			B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
1844 		button->SetTarget(this);
1845 		box->AddChild(button);
1846 
1847 		rect.OffsetBy(50, 0);
1848 		rect.right = rect.left + 55;
1849 		button = new BButton(rect, "remove", "Remove", new BMessage(kRemoveItem),
1850 			B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
1851 
1852 		button->SetEnabled(false);
1853 		button->SetTarget(this);
1854 		box->AddChild(button);
1855 	}
1856 	// enable remove button as needed
1857 	button->SetEnabled(fAttrViewList.CountItems() > 1);
1858 }
1859 
1860 
1861 void
1862 FindPanel::FillCurrentQueryName(BTextControl *queryName, FindWindow *window)
1863 {
1864 	ASSERT(window);
1865 	queryName->SetText(window->QueryName());
1866 }
1867 
1868 
1869 void
1870 FindPanel::AddAttrView()
1871 {
1872 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1873 	BRect bounds(Bounds());
1874 
1875 	TAttrView *previous = fAttrViewList.LastItem();
1876 
1877 	if (previous)
1878 		Window()->ResizeBy(0, 30);
1879 
1880 	bounds = Bounds();
1881 	bounds.InsetBy(15, 30);
1882 	bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0);
1883 
1884 	if (previous) {
1885 		box->ResizeTo(bounds.Width(), bounds.Height());
1886 		bounds = previous->Frame();
1887 		bounds.OffsetBy(0, 30);
1888 	} else {
1889 		bounds = box->Bounds();
1890 		bounds.InsetBy(5, 5);
1891 		bounds.bottom = bounds.top + 25;
1892 	}
1893 	AddOneAttributeItem(box, bounds);
1894 
1895 		// add logic to previous attrview
1896 	if (previous)
1897 		previous->AddLogicMenu();
1898 
1899 	SetUpAddRemoveButtons(box);
1900 
1901 	// populate mime popup
1902 	TAttrView *last = fAttrViewList.LastItem();
1903 	last->AddMimeTypeAttrs();
1904 }
1905 
1906 
1907 void
1908 FindPanel::RemoveAttrView()
1909 {
1910 	if (fAttrViewList.CountItems() < 2)
1911 		return;
1912 
1913 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1914 	TAttrView *attrView = fAttrViewList.LastItem();
1915 	if (!box || !attrView)
1916 		return;
1917 
1918 	Window()->ResizeBy(0, -30);
1919 	BRect bounds(Bounds());
1920 	bounds.InsetBy(15, 30);
1921 	bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0);
1922 	box->ResizeTo(bounds.Width(), bounds.Height());
1923 
1924 	fAttrViewList.RemoveItem(attrView);
1925 	attrView->RemoveSelf();
1926 	delete attrView;
1927 
1928 	attrView = fAttrViewList.LastItem();
1929 	attrView->RemoveLogicMenu();
1930 	attrView->MakeTextViewFocus();
1931 
1932 	if (fAttrViewList.CountItems() != 1)
1933 		return;
1934 
1935 	BButton *button = dynamic_cast<BButton *>(Window()->FindView("remove"));
1936 	if (button)
1937 		button->SetEnabled(false);
1938 }
1939 
1940 
1941 uint32
1942 FindPanel::InitialMode(const BNode *node)
1943 {
1944 	if (!node || node->InitCheck() != B_OK)
1945 		return kByNameItem;
1946 
1947 	uint32 result;
1948 	if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
1949 		(int32 *)&result, sizeof(int32)) <= 0)
1950 		return kByNameItem;
1951 
1952 	return result;
1953 }
1954 
1955 
1956 int32
1957 FindPanel::InitialAttrCount(const BNode *node)
1958 {
1959 	if (!node || node->InitCheck() != B_OK)
1960 		return 1;
1961 
1962 	int32 result;
1963 	if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
1964 		&result, sizeof(int32)) <= 0)
1965 		return 1;
1966 
1967 	return result;
1968 }
1969 
1970 
1971 static int32
1972 SelectItemWithLabel(BMenu *menu, const char *label)
1973 {
1974 	for (int32 index = menu->CountItems(); index-- > 0;)  {
1975 		BMenuItem *item = menu->ItemAt(index);
1976 
1977 		if (strcmp(label, item->Label()) == 0) {
1978 			item->SetMarked(true);
1979 			return index;
1980 		}
1981 	}
1982 	return -1;
1983 }
1984 
1985 
1986 void
1987 FindPanel::SaveWindowState(BNode *node, bool editTemplate)
1988 {
1989 	ASSERT(node->InitCheck() == B_OK);
1990 
1991 	BMenuItem *item = CurrentMimeType();
1992 	if (item) {
1993 		BString label(item->Label());
1994 		node->WriteAttrString(kAttrQueryInitialMime, &label);
1995 	}
1996 
1997 	uint32 mode = Mode();
1998 	node->WriteAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
1999 		(int32 *)&mode, sizeof(int32));
2000 
2001 	MoreOptionsStruct saveMoreOptions;
2002 	saveMoreOptions.showMoreOptions = fLatch->Value() != 0;
2003 
2004 	saveMoreOptions.searchTrash = fSearchTrashCheck->Value() != 0;
2005 	saveMoreOptions.temporary = fTemporaryCheck->Value() != 0;
2006 
2007 	if (node->WriteAttr(kAttrQueryMoreOptions, B_RAW_TYPE, 0, &saveMoreOptions,
2008 		sizeof(saveMoreOptions)) == sizeof(saveMoreOptions))
2009 		node->RemoveAttr(kAttrQueryMoreOptionsForeign);
2010 
2011 	if (editTemplate) {
2012 		if (UserSpecifiedName()) {
2013 			BString name(UserSpecifiedName());
2014 			node->WriteAttrString(kAttrQueryTemplateName, &name);
2015 		}
2016 	}
2017 
2018 	switch (Mode()) {
2019 		case kByAttributeItem:
2020 			{
2021 				BMessage message;
2022 				int32 count = fAttrViewList.CountItems();
2023 				node->WriteAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
2024 					&count, sizeof(int32));
2025 
2026 				for (int32 index = 0; index < count; index++)
2027 					fAttrViewList.ItemAt(index)->SaveState(&message, index);
2028 
2029 				ssize_t size = message.FlattenedSize();
2030 				char *buffer = new char[size];
2031 				status_t result = message.Flatten(buffer, size);
2032 				ASSERT(result == B_OK);
2033 				result = node->WriteAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
2034 					buffer, (size_t)size);
2035 				ASSERT(result == size);
2036 				delete [] buffer;
2037 			}
2038 			break;
2039 
2040 		case kByNameItem:
2041 		case kByForumlaItem:
2042 			{
2043 				BTextControl *textControl = dynamic_cast<BTextControl *>
2044 					(FindView("TextControl"));
2045 				ASSERT(textControl);
2046 				BString formula(textControl->TextView()->Text());
2047 				node->WriteAttrString(kAttrQueryInitialString, &formula);
2048 				break;
2049 			}
2050 	}
2051 }
2052 
2053 
2054 void
2055 FindPanel::SwitchToTemplate(const BNode *node)
2056 {
2057 	if (fLatch->Value()) {
2058 		// this is kind of a hack - the following code up to
2059 		// RestoreWindowState assumes the latch is closed
2060 		// Would be nicer if all the size of the window were set once
2061 		// and correctly - this is not easy thought because the latch
2062 		// controls the window size in relative increments
2063 		fLatch->SetValue(0);
2064 		fMoreOptionsPane->SetMode(0);
2065 	}
2066 
2067 	SwitchMode(InitialMode(node));
2068 		// update the menu to correspond to the mode
2069 	MarkNamedMenuItem(fSearchModeMenu, InitialMode(node), true);
2070 
2071  	BRect initialRect(InitialViewSize(node));
2072 	Window()->ResizeTo(initialRect.Width(), initialRect.Height());
2073 	if (Mode() == (int32)kByAttributeItem) {
2074 		RemoveByAttributeItems();
2075 		ResizeAttributeBox(node);
2076 		AddByAttributeItems(node);
2077 	}
2078 
2079 	RestoreWindowState(node);
2080 }
2081 
2082 
2083 void
2084 FindPanel::RestoreMimeTypeMenuSelection(const BNode *node)
2085 {
2086 	if (Mode() == (int32)kByForumlaItem || node == NULL || node->InitCheck() != B_OK)
2087 		return;
2088 
2089 	BString buffer;
2090 	if (node->ReadAttrString(kAttrQueryInitialMime, &buffer) == B_OK)
2091 		SetCurrentMimeType(buffer.String());
2092 }
2093 
2094 
2095 void
2096 FindPanel::RestoreWindowState(const BNode *node)
2097 {
2098 	fMode = InitialMode(node);
2099 	if (!node || node->InitCheck() != B_OK)
2100 		return;
2101 
2102 	ShowOrHideMimeTypeMenu();
2103 	RestoreMimeTypeMenuSelection(node);
2104 	MoreOptionsStruct saveMoreOptions;
2105 
2106 	bool storesMoreOptions = ReadAttr(node, kAttrQueryMoreOptions,
2107 		kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions,
2108 		sizeof(saveMoreOptions), &MoreOptionsStruct::EndianSwap)
2109 		!= kReadAttrFailed;
2110 
2111 	if (storesMoreOptions) {
2112 		// need to sanitize to true or false here, could have picked
2113 		// up garbage from attributes
2114 
2115 		saveMoreOptions.showMoreOptions =
2116 			(saveMoreOptions.showMoreOptions != 0);
2117 
2118 		fLatch->SetValue(saveMoreOptions.showMoreOptions);
2119 		fMoreOptionsPane->SetMode(saveMoreOptions.showMoreOptions);
2120 
2121 		fSearchTrashCheck->SetValue(saveMoreOptions.searchTrash);
2122 		fTemporaryCheck->SetValue(saveMoreOptions.temporary);
2123 
2124 		fQueryName->SetModificationMessage(NULL);
2125 		FillCurrentQueryName(fQueryName, dynamic_cast<FindWindow *>(Window()));
2126 
2127 		// set modification message after checking the temporary check box,
2128 		// and filling out the text control so that we do not
2129 		// always trigger clearing of the temporary check box.
2130 		fQueryName->SetModificationMessage(new BMessage(kNameModifiedMessage));
2131 	}
2132 
2133 	// get volumes to perform query on
2134 	bool searchAllVolumes = true;
2135 
2136 	attr_info info;
2137 	if (node->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) {
2138 		char *buffer = new char[info.size];
2139 		if (node->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size)
2140 			== info.size) {
2141 			BMessage message;
2142 			if (message.Unflatten(buffer) == B_OK) {
2143 				for (int32 index = 0; ;index++) {
2144 					ASSERT(index < 100);
2145 					BVolume volume;
2146 						// match a volume with the info embedded in the message
2147 					status_t result = MatchArchivedVolume(&volume, &message, index);
2148 					if (result == B_OK) {
2149 						char name[256];
2150 						volume.GetName(name);
2151 						SelectItemWithLabel(fVolMenu, name);
2152 						searchAllVolumes = false;
2153 					} else if (result != B_DEV_BAD_DRIVE_NUM)
2154 						// if B_DEV_BAD_DRIVE_NUM, the volume just isn't mounted this
2155 						// time around, keep looking for more
2156 						// if other error, bail
2157 						break;
2158 				}
2159 			}
2160 		}
2161 		delete [] buffer;
2162 	}
2163 	// mark or unmark "All disks"
2164 	fVolMenu->ItemAt(0)->SetMarked(searchAllVolumes);
2165 	ShowVolumeMenuLabel();
2166 
2167 	switch (Mode()) {
2168 		case kByAttributeItem:
2169 			{
2170 				int32 count = InitialAttrCount(node);
2171 
2172 				attr_info info;
2173 				if (node->GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
2174 					break;
2175 				char *buffer = new char[info.size];
2176 				if (node->ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size)
2177 					== info.size) {
2178 					BMessage message;
2179 					if (message.Unflatten(buffer) == B_OK)
2180 						for (int32 index = 0; index < count; index++)
2181 							fAttrViewList.ItemAt(index)->RestoreState(message, index);
2182 				}
2183 				delete [] buffer;
2184 				break;
2185 			}
2186 
2187 		case kByNameItem:
2188 		case kByForumlaItem:
2189 			{
2190 				BString buffer;
2191 				if (node->ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) {
2192 					BTextControl *textControl = dynamic_cast<BTextControl *>
2193 						(FindView("TextControl"));
2194 					ASSERT(textControl);
2195 
2196 					textControl->TextView()->SetText(buffer.String());
2197 				}
2198 			}
2199 			break;
2200 	}
2201 
2202 	// try to restore focus and possibly text selection
2203 	BString focusedView;
2204 	if (node->ReadAttrString("_trk/focusedView", &focusedView) == B_OK) {
2205 		BView *view = FindView(focusedView.String());
2206 		if (view) {
2207 			view->MakeFocus();
2208 			BTextControl *textControl = dynamic_cast<BTextControl *>(view);
2209 			if (textControl) {
2210 				int32 selStart = 0, selEnd = LONG_MAX;
2211 				node->ReadAttr("_trk/focusedSelStart", B_INT32_TYPE, 0,
2212 					&selStart, sizeof(selStart));
2213 				node->ReadAttr("_trk/focusedSelEnd", B_INT32_TYPE, 0,
2214 					&selEnd, sizeof(selEnd));
2215 				textControl->TextView()->Select(selStart, selEnd);
2216 			}
2217 		}
2218 	}
2219 }
2220 
2221 
2222 void
2223 FindPanel::ResizeAttributeBox(const BNode *node)
2224 {
2225 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2226 	BRect bounds(box->Bounds());
2227 	int32 count = InitialAttrCount(node);
2228 
2229 	bounds.bottom = count * 30 + 40;
2230 	box->ResizeTo(bounds.Width(), bounds.Height());
2231 }
2232 
2233 
2234 void
2235 FindPanel::AddByAttributeItems(const BNode *node)
2236 {
2237 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2238 	ASSERT(box);
2239 	BRect bounds(box->Bounds());
2240 
2241 	int32 numAttributes = InitialAttrCount(node);
2242 	if (numAttributes < 1)
2243 		numAttributes = 1;
2244 
2245 	BRect rect(bounds);
2246 	rect.InsetBy(5, 5);
2247 	rect.bottom = rect.top + 25;
2248 
2249 	for (int32 index = 0; index < numAttributes; index ++) {
2250 		AddOneAttributeItem(box, rect);
2251 		rect.OffsetBy(0, 30);
2252 	}
2253 	SetUpAddRemoveButtons(box);
2254 }
2255 
2256 
2257 void
2258 FindPanel::AddByNameOrFormulaItems()
2259 {
2260 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2261 
2262 	BRect bounds(box->Bounds());
2263 	bounds.InsetBy(10, 10);
2264 	BTextControl *textControl = new BTextControl(bounds, "TextControl", "", "", NULL);
2265 	textControl->SetDivider(0.0f);
2266 	box->AddChild(textControl);
2267 	textControl->MakeFocus();
2268 }
2269 
2270 
2271 void
2272 FindPanel::RemoveAttrViewItems()
2273 {
2274 	for (;;) {
2275 		BView *view = FindView("AttrView");
2276 		if (view == NULL)
2277 			break;
2278 		view->RemoveSelf();
2279 		delete view;
2280 	}
2281 
2282 	fAttrViewList.MakeEmpty();
2283 }
2284 
2285 
2286 void
2287 FindPanel::RemoveByAttributeItems()
2288 {
2289 	RemoveAttrViewItems();
2290 	BView *view = FindView("add");
2291 	if (view) {
2292 		view->RemoveSelf();
2293 		delete view;
2294 	}
2295 
2296 	view = FindView("remove");
2297 	if (view) {
2298 		view->RemoveSelf();
2299 		delete view;
2300 	}
2301 
2302 	view = dynamic_cast<BTextControl *>(FindView("TextControl"));
2303 	if (view) {
2304 		view->RemoveSelf();
2305 		delete view;
2306 	}
2307 }
2308 
2309 
2310 void
2311 FindPanel::ShowOrHideMimeTypeMenu()
2312 {
2313 	BMenuField *menuField = dynamic_cast<BMenuField *>(FindView("MimeTypeMenu"));
2314 	if (Mode() == (int32)kByForumlaItem && !menuField->IsHidden())
2315 		menuField->Hide();
2316 	else if (menuField->IsHidden())
2317 		menuField->Show();
2318 }
2319 
2320 
2321 // #pragma mark -
2322 
2323 
2324 TAttrView::TAttrView(BRect frame, int32 index)
2325 	:	BView(frame, "AttrView", B_FOLLOW_NONE, B_WILL_DRAW)
2326 {
2327 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2328 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2329 
2330 	BPopUpMenu *menu = new BPopUpMenu("PopUp");
2331 
2332 	// add NAME attribute to popup
2333 	BMenu *submenu = new BMenu("Name");
2334 	submenu->SetRadioMode(true);
2335 	submenu->SetFont(be_plain_font);
2336 	BMessage *message = new BMessage(kAttributeItemMain);
2337 	message->AddString("name", "name");
2338 	message->AddInt32("type", B_STRING_TYPE);
2339 	BMenuItem *item = new BMenuItem(submenu, message);
2340 	menu->AddItem(item);
2341 
2342 	const int32 operators[] = {B_CONTAINS, B_EQ, B_NE, B_BEGINS_WITH, B_ENDS_WITH};
2343 	const char *operatorLabels[] = {"contains", "is", "is not", "starts with", "ends with"};
2344 
2345 	for (int32 i = 0;i < 5;i++) {
2346 		message = new BMessage(kAttributeItem);
2347 		message->AddInt32("operator", operators[i]);
2348 		submenu->AddItem(new BMenuItem(operatorLabels[i], message));
2349 	}
2350 
2351 	// mark first item
2352 	menu->ItemAt(0)->SetMarked(true);
2353 	submenu->ItemAt(0)->SetMarked(true);
2354 
2355 	// add SIZE attribute
2356 	submenu = new BMenu("Size");
2357 	submenu->SetRadioMode(true);
2358 	submenu->SetFont(be_plain_font);
2359 	message = new BMessage(kAttributeItemMain);
2360 	message->AddString("name", "size");
2361 	message->AddInt32("type", B_OFF_T_TYPE);
2362 	item = new BMenuItem(submenu, message);
2363 	menu->AddItem(item);
2364 
2365 	message = new BMessage(kAttributeItem);
2366 	message->AddInt32("operator", B_GE);
2367 	submenu->AddItem(new BMenuItem("greater than", message));
2368 
2369 	message = new BMessage(kAttributeItem);
2370 	message->AddInt32("operator", B_LE);
2371 	submenu->AddItem(new BMenuItem("less than", message));
2372 
2373 	message = new BMessage(kAttributeItem);
2374 	message->AddInt32("operator", B_EQ);
2375 	submenu->AddItem(new BMenuItem("is", message));
2376 
2377 	// add "modified" field
2378 	submenu = new BMenu("Modified");
2379 	submenu->SetRadioMode(true);
2380 	submenu->SetFont(be_plain_font);
2381 	message = new BMessage(kAttributeItemMain);
2382 	message->AddString("name", "last_modified");
2383 	message->AddInt32("type", B_TIME_TYPE);
2384 	item = new BMenuItem(submenu, message);
2385 	menu->AddItem(item);
2386 
2387 	message = new BMessage(kAttributeItem);
2388 	message->AddInt32("operator", B_LE);
2389 	submenu->AddItem(new BMenuItem("before", message));
2390 
2391 	message = new BMessage(kAttributeItem);
2392 	message->AddInt32("operator", B_GE);
2393 	submenu->AddItem(new BMenuItem("after", message));
2394 
2395 	BRect bounds(Bounds());
2396 	bounds.right = bounds.left + 100;
2397 	bounds.bottom = bounds.top + 15;
2398 	fMenuField = new BMenuField(bounds, "MenuField", "", menu);
2399 	fMenuField->SetDivider(0.0f);
2400 
2401 	// add text entry box
2402 	bounds = Bounds();
2403 	bounds.left += bounds.right - 180;
2404 	bounds.top += 2;
2405 	bounds.right -= 42;
2406 	BString title("TextEntry");
2407 	title << index;
2408 	fTextControl = new BTextControl(bounds, title.String(), "", "", NULL);
2409 	fTextControl->SetDivider(0.0f);
2410 	AddChild(fTextControl);
2411 
2412 	AddChild(fMenuField);
2413 	// add attributes from currently selected mimetype
2414 }
2415 
2416 
2417 TAttrView::~TAttrView()
2418 {
2419 }
2420 
2421 
2422 void
2423 TAttrView::AttachedToWindow()
2424 {
2425 	BMenu *menu = fMenuField->Menu();
2426 	// target everything
2427 	menu->SetTargetForItems(this);
2428 
2429 	for (int32 index = menu->CountItems() - 1; index >= 0; index--)
2430 		menu->SubmenuAt(index)->SetTargetForItems(this);
2431 }
2432 
2433 
2434 void
2435 TAttrView::MakeTextViewFocus()
2436 {
2437 	fTextControl->MakeFocus();
2438 }
2439 
2440 
2441 void
2442 TAttrView::RestoreState(const BMessage &message, int32 index)
2443 {
2444 	BMenu *menu = fMenuField->Menu();
2445 	// decode menu selections
2446 
2447 	AddMimeTypeAttrs(menu);
2448 
2449 	const char *label;
2450 	if (message.FindString("menuSelection", index, &label) == B_OK) {
2451 		int32 itemIndex = SelectItemWithLabel(menu, label);
2452 		if (itemIndex >=0) {
2453 			menu = menu->SubmenuAt(itemIndex);
2454 			if (menu && message.FindString("subMenuSelection", index, &label)
2455 					== B_OK)
2456 				SelectItemWithLabel(menu, label);
2457 		}
2458 	}
2459 
2460 	// decode attribute text
2461 	ASSERT(fTextControl);
2462 	const char *string;
2463 	if (message.FindString("attrViewText", index, &string) == B_OK)
2464 		fTextControl->TextView()->SetText(string);
2465 
2466 	int32 logicMenuSelectedIndex;
2467 	BMenuField *field = dynamic_cast<BMenuField *>(FindView("Logic"));
2468 	if (message.FindInt32("logicalRelation", index, &logicMenuSelectedIndex) == B_OK)
2469 		if (field)
2470 			field->Menu()->ItemAt(logicMenuSelectedIndex)->SetMarked(true);
2471 		else
2472 			AddLogicMenu(logicMenuSelectedIndex == 0);
2473 }
2474 
2475 
2476 void
2477 TAttrView::SaveState(BMessage *message, int32)
2478 {
2479 	BMenu *menu = fMenuField->Menu();
2480 
2481 	// encode main attribute menu selection
2482 	BMenuItem *item = menu->FindMarked();
2483 	message->AddString("menuSelection", item ? item->Label() : "");
2484 
2485 	// encode submenu selection
2486 	const char *label = "";
2487 	if (item) {
2488 		BMenu *submenu = menu->SubmenuAt(menu->IndexOf(item));
2489 		if (submenu) {
2490 			item = submenu->FindMarked();
2491 			if (item)
2492 				label = item->Label();
2493 		}
2494 	}
2495 	message->AddString("subMenuSelection", label);
2496 
2497 	// encode attribute text
2498 	ASSERT(fTextControl);
2499 	message->AddString("attrViewText", fTextControl->TextView()->Text());
2500 
2501 	BMenuField *field = dynamic_cast<BMenuField *>(FindView("Logic"));
2502 	if (field) {
2503 		BMenuItem *item = field->Menu()->FindMarked();
2504 		ASSERT(item);
2505 		message->AddInt32("logicalRelation", item ? field->Menu()->IndexOf(item) : 0);
2506 	}
2507 }
2508 
2509 void
2510 TAttrView::AddLogicMenu(bool selectAnd)
2511 {
2512 	// add "AND/OR" menu
2513 	BPopUpMenu *menu = new BPopUpMenu("");
2514 	BMessage *message = new BMessage();
2515 	message->AddInt32("combine", B_AND);
2516 	BMenuItem *item = new BMenuItem("And", message);
2517 	menu->AddItem(item);
2518 	if (selectAnd)
2519 		item->SetMarked(true);
2520 
2521 	message = new BMessage();
2522 	message->AddInt32("combine", B_OR);
2523 	item = new BMenuItem("Or", message);
2524 	menu->AddItem(item);
2525 	if (!selectAnd)
2526 		item->SetMarked(true);
2527 
2528 	menu->SetTargetForItems(this);
2529 
2530 	BRect bounds(Bounds());
2531 	bounds.left = bounds.right - 40;
2532 	bounds.bottom = bounds.top + 15;
2533 	BMenuField *menufield = new BMenuField(bounds, "Logic", "", menu);
2534 	menufield->SetDivider(0.0f);
2535 	menufield->HidePopUpMarker();
2536 	AddChild(menufield);
2537 }
2538 
2539 
2540 void
2541 TAttrView::RemoveLogicMenu()
2542 {
2543 	BMenuField *menufield = dynamic_cast<BMenuField *>(FindView("Logic"));
2544 	if (menufield) {
2545 		menufield->RemoveSelf();
2546 		delete menufield;
2547 	}
2548 }
2549 
2550 
2551 void
2552 TAttrView::Draw(BRect)
2553 {
2554 	BMenuItem *item = fMenuField->Menu()->FindMarked();
2555 	if (!item)
2556 		return;
2557 
2558 	if (item->Submenu()->FindMarked()) {
2559 		float width = StringWidth(item->Submenu()->FindMarked()->Label());
2560 		BRect bounds(fTextControl->Frame());
2561 
2562 		// draws the is/contains, etc. string
2563 		bounds.left -= (width + 10);
2564 		bounds.bottom -= 6;
2565 		MovePenTo(bounds.LeftBottom());
2566 		DrawString(item->Submenu()->FindMarked()->Label());
2567 	}
2568 }
2569 
2570 
2571 void
2572 TAttrView::MessageReceived(BMessage *message)
2573 {
2574 	BMenuItem *item;
2575 
2576 	switch (message->what) {
2577 		case kAttributeItem:
2578 			if (message->FindPointer("source", (void **)&item) != B_OK)
2579 				return;
2580 
2581 			item->Menu()->Superitem()->SetMarked(true);
2582 			Invalidate();
2583 			break;
2584 
2585 		case kAttributeItemMain:
2586 			// in case someone selected just and attribute without the
2587 			// comparator
2588 			if (message->FindPointer("source", (void **)&item) != B_OK)
2589 				return;
2590 
2591 			if (item->Submenu()->ItemAt(0))
2592 				item->Submenu()->ItemAt(0)->SetMarked(true);
2593 			Invalidate();
2594 			break;
2595 
2596 		default:
2597 			_inherited::MessageReceived(message);
2598 			break;
2599 	}
2600 }
2601 
2602 
2603 void
2604 TAttrView::AddMimeTypeAttrs()
2605 {
2606 	BMenu *menu = fMenuField->Menu();
2607 	AddMimeTypeAttrs(menu);
2608 }
2609 
2610 
2611 void
2612 TAttrView::AddMimeTypeAttrs(BMenu *menu)
2613 {
2614 	FindPanel *mainView = dynamic_cast<FindPanel *>(Parent()->
2615 		Parent()->FindView("MainView"));
2616 	if (!mainView)
2617 		return;
2618 
2619 	const char *typeName;
2620 	if (mainView->CurrentMimeType(&typeName) == NULL)
2621 		return;
2622 
2623 	BMimeType mimeType(typeName);
2624 	if (!mimeType.IsInstalled())
2625 		return;
2626 
2627 	// only add things to menu which have "user-visible" data
2628 	BMessage attributeMessage;
2629 	if (mimeType.GetAttrInfo(&attributeMessage) != B_OK)
2630 		return;
2631 
2632 	char desc[B_MIME_TYPE_LENGTH];
2633 	mimeType.GetShortDescription(desc);
2634 
2635 	// go through each field in meta mime and add it to a menu
2636 	for (int32 index = 0; ; index++) {
2637 		const char *publicName;
2638 		if (attributeMessage.FindString("attr:public_name", index, &publicName) != B_OK)
2639 			break;
2640 
2641 		if (!attributeMessage.FindBool("attr:viewable"))
2642 			continue;
2643 
2644 		const char *attributeName;
2645 		if (attributeMessage.FindString("attr:name", index, &attributeName) != B_OK)
2646 			continue;
2647 
2648 		int32 type;
2649 		if (attributeMessage.FindInt32("attr:type", index, &type) != B_OK)
2650 			continue;
2651 
2652 		BMenu *submenu = new BMenu(publicName);
2653 		submenu->SetRadioMode(true);
2654 		submenu->SetFont(be_plain_font);
2655 		BMessage *message = new BMessage(kAttributeItemMain);
2656 		message->AddString("name", attributeName);
2657 		message->AddInt32("type", type);
2658 		BMenuItem *item = new BMenuItem(submenu, message);
2659 		menu->AddItem(item);
2660 		menu->SetTargetForItems(this);
2661 
2662 		switch (type) {
2663 			case B_STRING_TYPE:
2664 				message = new BMessage(kAttributeItem);
2665 				message->AddInt32("operator", B_CONTAINS);
2666 				submenu->AddItem(new BMenuItem("contains", message));
2667 
2668 				message = new BMessage(kAttributeItem);
2669 				message->AddInt32("operator", B_EQ);
2670 				submenu->AddItem(new BMenuItem("is", message));
2671 
2672 				message = new BMessage(kAttributeItem);
2673 				message->AddInt32("operator", B_NE);
2674 				submenu->AddItem(new BMenuItem("is not", message));
2675 				submenu->SetTargetForItems(this);
2676 
2677 				message = new BMessage(kAttributeItem);
2678 				message->AddInt32("operator", B_BEGINS_WITH);
2679 				submenu->AddItem(new BMenuItem("starts with", message));
2680 				submenu->SetTargetForItems(this);
2681 
2682 				message = new BMessage(kAttributeItem);
2683 				message->AddInt32("operator", B_ENDS_WITH);
2684 				submenu->AddItem(new BMenuItem("ends with", message));
2685 				break;
2686 
2687 			case B_BOOL_TYPE:
2688 			case B_INT16_TYPE:
2689 			case B_UINT8_TYPE:
2690 			case B_INT8_TYPE:
2691 			case B_UINT16_TYPE:
2692 			case B_INT32_TYPE:
2693 			case B_UINT32_TYPE:
2694 			case B_INT64_TYPE:
2695 			case B_UINT64_TYPE:
2696 			case B_OFF_T_TYPE:
2697 			case B_FLOAT_TYPE:
2698 			case B_DOUBLE_TYPE:
2699 				message = new BMessage(kAttributeItem);
2700 				message->AddInt32("operator", B_EQ);
2701 				submenu->AddItem(new BMenuItem("is", message));
2702 
2703 				message = new BMessage(kAttributeItem);
2704 				message->AddInt32("operator", B_GE);
2705 				submenu->AddItem(new BMenuItem("greater than", message));
2706 
2707 				message = new BMessage(kAttributeItem);
2708 				message->AddInt32("operator", B_LE);
2709 				submenu->AddItem(new BMenuItem("less than", message));
2710 				break;
2711 
2712 			case B_TIME_TYPE:
2713 				message = new BMessage(kAttributeItem);
2714 				message->AddInt32("operator", B_LE);
2715 				submenu->AddItem(new BMenuItem("before", message));
2716 
2717 				message = new BMessage(kAttributeItem);
2718 				message->AddInt32("operator", B_GE);
2719 				submenu->AddItem(new BMenuItem("after", message));
2720 				break;
2721 		}
2722 		submenu->SetTargetForItems(this);
2723 	}
2724 }
2725 
2726 
2727 void
2728 TAttrView::GetDefaultName(BString &result) const
2729 {
2730 	BMenuItem *item = NULL;
2731 	if (fMenuField->Menu() != NULL)
2732 		item = fMenuField->Menu()->FindMarked();
2733 	if (item != NULL)
2734 		result << item->Label();
2735 	else
2736 		result << "Name";
2737 
2738 	if (item->Submenu() != NULL)
2739 		item = item->Submenu()->FindMarked();
2740 	else
2741 		item = NULL;
2742 
2743 	if (item != NULL)
2744 		result << " " << item->Label() << " ";
2745 	else
2746 		result << " = ";
2747 
2748 	result << fTextControl->Text();
2749 }
2750 
2751 
2752 // #pragma mark -
2753 
2754 
2755 DeleteTransientQueriesTask::DeleteTransientQueriesTask()
2756 	:	state(kInitial),
2757 		fWalker(NULL)
2758 {
2759 }
2760 
2761 
2762 DeleteTransientQueriesTask::~DeleteTransientQueriesTask()
2763 {
2764 	delete fWalker;
2765 }
2766 
2767 
2768 bool
2769 DeleteTransientQueriesTask::DoSomeWork()
2770 {
2771 	switch (state) {
2772 		case kInitial:
2773 			Initialize();
2774 			break;
2775 
2776 		case kAllocatedWalker:
2777 		case kTraversing:
2778 			if (GetSome()) {
2779 				PRINT(("transient query killer done\n"));
2780 				return true;
2781 			}
2782 			break;
2783 
2784 		case kError:
2785 			return true;
2786 
2787 	}
2788 	return false;
2789 }
2790 
2791 
2792 void
2793 DeleteTransientQueriesTask::Initialize()
2794 {
2795 	PRINT(("starting up transient query killer\n"));
2796 	BPath path;
2797 	status_t result = find_directory(B_USER_DIRECTORY, &path, false);
2798 	if (result != B_OK) {
2799 		state = kError;
2800 		return;
2801 	}
2802 	fWalker = new WALKER_NS::TNodeWalker(path.Path());
2803 	state = kAllocatedWalker;
2804 }
2805 
2806 
2807 const int32 kBatchCount = 100;
2808 
2809 bool
2810 DeleteTransientQueriesTask::GetSome()
2811 {
2812 	state = kTraversing;
2813 	for (int32 count = kBatchCount; count > 0; count--) {
2814 		entry_ref ref;
2815 		if (fWalker->GetNextRef(&ref) != B_OK) {
2816 			state = kError;
2817 			return true;
2818 		}
2819 		Model model(&ref);
2820 		if (model.IsQuery())
2821 			ProcessOneRef(&model);
2822 #if xDEBUG
2823 		else
2824 			PRINT(("transient query killer: %s not a query\n", model.Name()));
2825 #endif
2826 	}
2827 	return false;
2828 }
2829 
2830 
2831 const int32 kDaysToExpire = 7;
2832 
2833 static bool
2834 QueryOldEnough(Model *model)
2835 {
2836 	// check if it is old and ready to be deleted
2837 	time_t now = time(0);
2838 
2839 	tm nowTimeData;
2840 	tm fileModData;
2841 
2842 	localtime_r(&now, &nowTimeData);
2843 	localtime_r(&model->StatBuf()->st_ctime, &fileModData);
2844 
2845 	if ((nowTimeData.tm_mday - fileModData.tm_mday) < kDaysToExpire
2846 		&& (nowTimeData.tm_mday - fileModData.tm_mday) > -kDaysToExpire) {
2847 		PRINT(("query %s, not old enough\n", model->Name()));
2848 		return false;
2849 	}
2850 	return true;
2851 }
2852 
2853 
2854 bool
2855 DeleteTransientQueriesTask::ProcessOneRef(Model *model)
2856 {
2857 	BModelOpener opener(model);
2858 
2859 	// is this a temporary query
2860 	if (!MoreOptionsStruct::QueryTemporary(model->Node())) {
2861 		PRINT(("query %s, not temporary\n", model->Name()));
2862 		return false;
2863 	}
2864 
2865 	if (!QueryOldEnough(model))
2866 		return false;
2867 
2868 	ASSERT(dynamic_cast<TTracker *>(be_app));
2869 
2870 	// check that it is not showing
2871 	if (dynamic_cast<TTracker *>(be_app)->EntryHasWindowOpen(model->EntryRef())) {
2872 		PRINT(("query %s, showing, can't delete\n", model->Name()));
2873 		return false;
2874 	}
2875 
2876 	PRINT(("query %s, old, temporary, not shownig - deleting\n", model->Name()));
2877 	BEntry entry(model->EntryRef());
2878 	entry.Remove();
2879 
2880 	return true;
2881 }
2882 
2883 
2884 class DeleteTransientQueriesFunctor : public FunctionObjectWithResult<bool> {
2885 public:
2886 	DeleteTransientQueriesFunctor(DeleteTransientQueriesTask *task)
2887 		:	task(task)
2888 		{}
2889 
2890 	virtual ~DeleteTransientQueriesFunctor()
2891 		{
2892 			delete task;
2893 		}
2894 
2895 	virtual void operator()()
2896 		{ result = task->DoSomeWork(); }
2897 
2898 private:
2899 	DeleteTransientQueriesTask *task;
2900 };
2901 
2902 
2903 void
2904 DeleteTransientQueriesTask::StartUpTransientQueryCleaner()
2905 {
2906 	// set up a task that wakes up when the machine is idle and starts
2907 	// killing off old transient queries
2908 	DeleteTransientQueriesFunctor *worker
2909 		= new DeleteTransientQueriesFunctor(new DeleteTransientQueriesTask());
2910 	TTracker *tracker = dynamic_cast<TTracker *>(be_app);
2911 	ASSERT(tracker);
2912 	tracker->MainTaskLoop()->RunWhenIdle(worker,
2913 		30 * 60 * 1000000,	// half an hour initial delay
2914 		5 * 60 * 1000000,	// idle for five minutes
2915 		10 * 1000000);
2916 }
2917 
2918 
2919 //	#pragma mark -
2920 
2921 
2922 RecentFindItemsMenu::RecentFindItemsMenu(const char *title, const BMessenger *target,
2923 	uint32 what)
2924 	:	BMenu(title, B_ITEMS_IN_COLUMN),
2925 		fTarget(*target),
2926 		fWhat(what)
2927 {
2928 }
2929 
2930 
2931 void
2932 RecentFindItemsMenu::AttachedToWindow()
2933 {
2934 	// re-populate the menu with fresh items
2935 	for (int32 index = CountItems() - 1; index >= 0; index--)
2936 		delete RemoveItem(index);
2937 
2938 	FindPanel::AddRecentQueries(this, false, &fTarget, fWhat);
2939 	BMenu::AttachedToWindow();
2940 }
2941 
2942 
2943 #if !B_BEOS_VERSION_DANO
2944 _IMPEXP_TRACKER
2945 #endif
2946 BMenu *
2947 TrackerBuildRecentFindItemsMenu(const char *title)
2948 {
2949 	BMessenger tracker(kTrackerSignature);
2950 	return new RecentFindItemsMenu(title, &tracker, B_REFS_RECEIVED);
2951 }
2952 
2953 
2954 //	#pragma mark -
2955 
2956 
2957 DraggableQueryIcon::DraggableQueryIcon(BRect frame, const char *name,
2958 	const BMessage *message, BMessenger messenger, uint32 resizeFlags, uint32 flags)
2959 	:	DraggableIcon(frame, name, B_QUERY_MIMETYPE, B_LARGE_ICON,
2960 			message, messenger, resizeFlags, flags)
2961 {
2962 }
2963 
2964 
2965 bool
2966 DraggableQueryIcon::DragStarted(BMessage *dragMessage)
2967 {
2968 	// override to substitute the user-specified query name
2969 	dragMessage->RemoveData("be:clip_name");
2970 
2971 	FindWindow *window = dynamic_cast<FindWindow *>(Window());
2972 	ASSERT(window);
2973 	dragMessage->AddString("be:clip_name",
2974 		window->BackgroundView()->UserSpecifiedName() ?
2975 			window->BackgroundView()->UserSpecifiedName() : "New Query");
2976 
2977 	return true;
2978 }
2979 
2980 
2981 //	#pragma mark -
2982 
2983 
2984 MostUsedNames::MostUsedNames(const char *fileName, const char *directory, int32 maxCount)
2985 	:
2986 	fFileName(fileName),
2987 	fDirectory(directory),
2988 	fLoaded(false),
2989 	fCount(maxCount)
2990 {
2991 }
2992 
2993 
2994 MostUsedNames::~MostUsedNames()
2995 {
2996 	// only write back settings when we've been used
2997 	if (!fLoaded)
2998 		return;
2999 
3000 	// write most used list to file
3001 
3002 	BPath path;
3003 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
3004 		return;
3005 
3006 	path.Append(fDirectory);
3007 	path.Append(fFileName);
3008 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
3009 	if (file.InitCheck() == B_OK) {
3010 		for (int32 i = 0; i < fList.CountItems(); i++) {
3011 			list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3012 
3013 			char line[B_FILE_NAME_LENGTH + 5];
3014 
3015 			// limit upper bound to react more dynamically to changes
3016 			if (--entry->count > 20)
3017 				entry->count = 20;
3018 
3019 			// if the item hasn't been chosen in a while, remove it
3020 			// (but leave at least one item in the list)
3021 			if (entry->count < -10 && i > 0)
3022 				continue;
3023 
3024 			sprintf(line, "%ld %s\n", entry->count, entry->name);
3025 			if (file.Write(line, strlen(line)) < B_OK)
3026 				break;
3027 		}
3028 	}
3029 	file.Unset();
3030 
3031 	// free data
3032 
3033 	for (int32 i = fList.CountItems(); i-- > 0;) {
3034 		list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3035 		free(entry->name);
3036 		delete entry;
3037 	}
3038 }
3039 
3040 
3041 bool
3042 MostUsedNames::ObtainList(BList *list)
3043 {
3044 	if (!list)
3045 		return false;
3046 
3047 	if (!fLoaded)
3048 		UpdateList();
3049 
3050 	fLock.Lock();
3051 
3052 	list->MakeEmpty();
3053 	for (int32 i = 0; i < fCount; i++) {
3054 		list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3055 		if (entry == NULL)
3056 			return true;
3057 
3058 		list->AddItem(entry->name);
3059 	}
3060 	return true;
3061 }
3062 
3063 
3064 void
3065 MostUsedNames::ReleaseList()
3066 {
3067 	fLock.Unlock();
3068 }
3069 
3070 
3071 void
3072 MostUsedNames::AddName(const char *name)
3073 {
3074 	fLock.Lock();
3075 
3076 	if (!fLoaded)
3077 		LoadList();
3078 
3079 	// remove last entry if there are more than
3080 	// 2*fCount entries in the list
3081 
3082 	list_entry *entry = NULL;
3083 
3084 	if (fList.CountItems() > fCount * 2) {
3085 		entry = static_cast<list_entry *>(fList.RemoveItem(fList.CountItems() - 1));
3086 
3087 		// is this the name we want to add here?
3088 		if (strcmp(name, entry->name)) {
3089 			free(entry->name);
3090 			delete entry;
3091 			entry = NULL;
3092 		} else
3093 			fList.AddItem(entry);
3094 	}
3095 
3096 	if (entry == NULL) {
3097 		for (int32 i = 0; (entry = static_cast<list_entry *>(fList.ItemAt(i))) != NULL; i++)
3098 			if (!strcmp(entry->name, name))
3099 				break;
3100 	}
3101 
3102 	if (entry == NULL) {
3103 		entry = new list_entry;
3104 		entry->name = strdup(name);
3105 		entry->count = 1;
3106 
3107 		fList.AddItem(entry);
3108 	} else if (entry->count < 0)
3109 		entry->count = 1;
3110 	else
3111 		entry->count++;
3112 
3113 	fLock.Unlock();
3114 	UpdateList();
3115 }
3116 
3117 
3118 int
3119 MostUsedNames::CompareNames(const void *a,const void *b)
3120 {
3121 	list_entry *entryA = *(list_entry **)a;
3122 	list_entry *entryB = *(list_entry **)b;
3123 
3124 	if (entryA->count == entryB->count)
3125 		return strcasecmp(entryA->name,entryB->name);
3126 
3127 	return entryB->count - entryA->count;
3128 }
3129 
3130 
3131 void
3132 MostUsedNames::LoadList()
3133 {
3134 	if (fLoaded)
3135 		return;
3136 	fLoaded = true;
3137 
3138 	// load the most used names list
3139 
3140 	BPath path;
3141 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
3142 		return;
3143 
3144 	path.Append(fDirectory);
3145 	path.Append(fFileName);
3146 
3147 	FILE *file = fopen(path.Path(), "r");
3148 	if (file == NULL)
3149 		return;
3150 
3151 	char line[B_FILE_NAME_LENGTH + 5];
3152 	while (fgets(line, sizeof(line), file) != NULL) {
3153 		int32 length = (int32)strlen(line) - 1;
3154 		if (length >= 0 && line[length] == '\n')
3155 			line[length] = '\0';
3156 
3157 		int32 count = atoi(line);
3158 
3159 		char *name = strchr(line, ' ');
3160 		if (name == NULL || *(++name) == '\0')
3161 			continue;
3162 
3163 		list_entry *entry = new list_entry;
3164 		entry->name = strdup(name);
3165 		entry->count = count;
3166 
3167 		fList.AddItem(entry);
3168 	}
3169 	fclose(file);
3170 }
3171 
3172 
3173 void
3174 MostUsedNames::UpdateList()
3175 {
3176 	AutoLock<Benaphore> locker(fLock);
3177 
3178 	if (!fLoaded)
3179 		LoadList();
3180 
3181 	// sort list items
3182 
3183 	fList.SortItems(MostUsedNames::CompareNames);
3184 }
3185 
3186 }	// namespace BPrivate
3187 
3188