xref: /haiku/src/kits/tracker/FindPanel.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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 kByFormulaItem:
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(kByFormulaItem)));
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 	fSearchTrashCheck->ResizeToPreferred();
760 	fMoreOptionsPane->AddItem(fSearchTrashCheck, 1);
761 
762 	rect.OffsetBy(fSearchTrashCheck->Bounds().Width() + 8, 0);
763 	fTemporaryCheck = new BCheckBox(rect, "temporary", "Temporary", 0);
764 	fMoreOptionsPane->AddItem(fTemporaryCheck, 1);
765 	fTemporaryCheck->SetValue(1);
766 
767 	BRect latchRect(paneInitialRect);
768 	latchRect.left -= 20;
769 	latchRect.right = latchRect.left + 10;
770 	latchRect.top = paneInitialRect.top + paneInitialRect.Height() / 2 - 5;
771 
772 	latchRect.bottom = latchRect.top + 12;
773 
774 	fLatch = new PaneSwitch(latchRect, "moreOptionsLatch", true,
775 		B_FOLLOW_BOTTOM | B_FOLLOW_LEFT);
776 	AddChild(fLatch);
777 	fMoreOptionsPane->SetSwitch(fLatch);
778 
779 	if (initialMode != kByAttributeItem)
780 		AddByNameOrFormulaItems();
781 	else
782 		AddByAttributeItems(node);
783 
784 	// add Search button
785 	rect = bounds;
786 	rect.left = rect.right - 80;
787 	rect.top = rect.bottom - 30;
788 	rect.right = rect.left + 60;
789 	rect.bottom = rect.top + 20;
790 	BButton *button;
791 	if (editTemplateOnly)
792 		button = new BButton(rect, "save", "Save",
793 			new BMessage(kSaveButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
794 	else
795 		button = new BButton(rect, "find", "Search",
796 			new BMessage(kFindButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
797 
798 	AddChild(button);
799 	button->MakeDefault(true);
800 }
801 
802 
803 FindPanel::~FindPanel()
804 {
805 }
806 
807 
808 void
809 FindPanel::AttachedToWindow()
810 {
811 	BNode *node = dynamic_cast<FindWindow *>(Window())->QueryNode();
812 	fSearchModeMenu->SetTargetForItems(this);
813 	fQueryName->SetTarget(this);
814 	fLatch->SetTarget(fMoreOptionsPane);
815 	RestoreMimeTypeMenuSelection(node);
816 		// preselect the mime we used the last time
817 		// have to do it here because AddByAttributeItems will build different
818 		// menus based on which mime type is preselected
819 	RestoreWindowState(node);
820 
821 	if (!Window()->CurrentFocus()) {
822 		// try to pick a good focus if we restore to one already
823 		BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
824 		if (!textControl) {
825 			// pick the last text control in the attribute view
826 			BString title("TextEntry");
827 			title << (fAttrViewList.CountItems() - 1);
828 			textControl = dynamic_cast<BTextControl *>(FindView(title.String()));
829 		}
830 		if (textControl)
831 			textControl->MakeFocus();
832 	}
833 
834 	BButton *button = dynamic_cast<BButton *>(FindView("remove"));
835 	if (button)
836 		button->SetTarget(this);
837 
838 	button = dynamic_cast<BButton *>(FindView("add"));
839 	if (button)
840 		button->SetTarget(this);
841 
842 	fVolMenu->SetTargetForItems(this);
843 
844 	// set target for MIME type items
845 	for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) {
846 		BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu();
847 		if (submenu != NULL)
848 				submenu->SetTargetForItems(this);
849 	}
850 	fMimeTypeMenu->SetTargetForItems(this);
851 
852 
853 	if (fDraggableIcon)
854 		fDraggableIcon->SetTarget(BMessenger(this));
855 
856 	fRecentQueries->SetTargetForItems(Window());
857 }
858 
859 const float kAttrViewDelta = 30;
860 
861 BRect
862 FindPanel::InitialViewSize(const BNode *node)
863 {
864 	if (!node || InitialMode(node) != (int32)kByAttributeItem)
865 		return kInitialRect;
866 
867 	int32 numAttributes = InitialAttrCount(node);
868 	if (numAttributes < 1)
869 		numAttributes = 1;
870 
871 	BRect result = kInitialRect;
872 	result.bottom = result.top + kInitialAttrModeWindowHeight
873 		+ (numAttributes - 1) * kIncrementPerAttribute;
874 
875 	return result;
876 }
877 
878 
879 float
880 FindPanel::ViewHeightForMode(uint32 mode, bool moreOptions)
881 {
882 	float result = moreOptions ? kMoreOptionsDelta : 0;
883 	switch (mode) {
884 		case kByFormulaItem:
885 		case kByNameItem:
886 			return 110 + result;
887 
888 		case kByAttributeItem:
889 			return 110 + result + kAttrViewDelta;
890 
891 	}
892 	TRESPASS();
893 	return 0;
894 }
895 
896 
897 float
898 FindPanel::BoxHeightForMode(uint32 mode, bool /*moreOptions*/)
899 {
900 	switch (mode) {
901 		case kByFormulaItem:
902 		case kByNameItem:
903 			return 40;
904 
905 		case kByAttributeItem:
906 			return 40 + kAttrViewDelta;
907 
908 	}
909 	TRESPASS();
910 	return 0;
911 }
912 
913 
914 static void
915 PopUpMenuSetTitle(BMenu *menu, const char *title)
916 {
917 	// This should really be in BMenuField
918 	BMenu *bar = menu->Supermenu();
919 
920 	ASSERT(bar);
921 	ASSERT(bar->ItemAt(0));
922 	if (!bar || !bar->ItemAt(0))
923 		return;
924 
925 	bar->ItemAt(0)->SetLabel(title);
926 }
927 
928 
929 void
930 FindPanel::ShowVolumeMenuLabel()
931 {
932 	if (fVolMenu->ItemAt(0)->IsMarked()) {
933 		// "all disks" selected
934 		PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label());
935 		return;
936 	}
937 
938 	// find out if more than one items are marked
939 	int32 count = fVolMenu->CountItems();
940 	int32 countSelected = 0;
941 	BMenuItem *tmpItem = NULL;
942 	for (int32 index = 2; index < count; index++) {
943 		BMenuItem *item = fVolMenu->ItemAt(index);
944 		if (item->IsMarked()) {
945 			countSelected++;
946 			tmpItem = item;
947 		}
948 	}
949 	if (countSelected == 0) {
950 		// no disk selected, for now revert to search all disks
951 		// ToDo:
952 		// show no disks here and add a check that will not let the
953 		// query go if the user doesn't pick at least one
954 		fVolMenu->ItemAt(0)->SetMarked(true);
955 		PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label());
956 	} else if (countSelected > 1)
957 		// if more than two disks selected, don't use the disk name
958 		// as a label
959 		PopUpMenuSetTitle(fVolMenu, "multiple disks");
960 	else {
961 		ASSERT(tmpItem);
962 		PopUpMenuSetTitle(fVolMenu, tmpItem->Label());
963 	}
964 }
965 
966 
967 void
968 FindPanel::MessageReceived(BMessage *message)
969 {
970 	entry_ref dir;
971 	const char *name;
972 
973 	switch (message->what) {
974 		case kVolumeItem:
975 			{
976 				// volume changed
977 				BMenuItem *invokedItem;
978 				dev_t dev;
979 				if (message->FindPointer("source", (void **)&invokedItem) != B_OK)
980 					return;
981 
982 				if (message->FindInt32("device", &dev) != B_OK)
983 					break;
984 
985 				BMenu *menu = invokedItem->Menu();
986 				ASSERT(menu);
987 
988 				if (dev == -1) {
989 
990 					// all disks selected, uncheck everything else
991 					int32 count = menu->CountItems();
992 					for (int32 index = 2; index < count; index++)
993 						menu->ItemAt(index)->SetMarked(false);
994 
995 					// make all disks the title and check it
996 					PopUpMenuSetTitle(menu, menu->ItemAt(0)->Label());
997 					menu->ItemAt(0)->SetMarked(true);
998 
999 				} else {
1000 					// a specific volume selected, unmark "all disks"
1001 					menu->ItemAt(0)->SetMarked(false);
1002 
1003 					// toggle mark on invoked item
1004 					int32 count = menu->CountItems();
1005 					for (int32 index = 2; index < count; index++) {
1006 						BMenuItem *item = menu->ItemAt(index);
1007 
1008 						if (invokedItem == item) {
1009 							// we just selected this
1010 							bool wasMarked = item->IsMarked();
1011 							item->SetMarked(!wasMarked);
1012 						}
1013 					}
1014 				}
1015 				// make sure the right label is showing
1016 				ShowVolumeMenuLabel();
1017 
1018 				break;
1019 			}
1020 
1021 		case kByAttributeItem:
1022 		case kByNameItem:
1023 		case kByFormulaItem:
1024 			SwitchMode(message->what);
1025 			break;
1026 
1027 		case kAddItem:
1028 			AddAttrView();
1029 			break;
1030 
1031 		case kRemoveItem:
1032 			RemoveAttrView();
1033 			break;
1034 
1035 		case kMIMETypeItem:
1036 			{
1037 				BMenuItem *item;
1038 				if (message->FindPointer("source", (void **)&item) == B_OK) {
1039 					// don't add the "All files and folders" to the list
1040 					if (fMimeTypeMenu->IndexOf(item) != 0)
1041 						gMostUsedMimeTypes.AddName(item->Label());
1042 
1043 					SetCurrentMimeType(item);
1044 				}
1045 
1046 				// mime type switched
1047 				if (fMode != kByAttributeItem)
1048 					break;
1049 
1050 				// the attributes for this type may be different, rip out the existing ones
1051 				RemoveAttrViewItems();
1052 
1053 				Window()->ResizeTo(Window()->Frame().Width(),
1054 					ViewHeightForMode(kByAttributeItem, fLatch->Value() != 0));
1055 
1056 				BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1057 				ASSERT(box);
1058 				box->ResizeTo(box->Bounds().Width(),
1059 					BoxHeightForMode(kByAttributeItem, fLatch->Value() != 0));
1060 
1061 				AddAttrView();
1062 				break;
1063 			}
1064 
1065 		case kNameModifiedMessage:
1066 			// the query name was edited, make the query permanent
1067 			fTemporaryCheck->SetValue(0);
1068 			break;
1069 
1070 		case B_SAVE_REQUESTED:
1071 			{
1072 				// finish saving query template from a SaveAs panel
1073 				entry_ref ref;
1074 				status_t error = message->FindRef("refs", &ref);
1075 
1076 				if (error == B_OK) {
1077 					// direct entry selected, convert to parent dir and name
1078 					BEntry entry(&ref);
1079 					error = entry.GetParent(&entry);
1080 					if (error == B_OK) {
1081 						entry.GetRef(&dir);
1082 						name = ref.name;
1083 					}
1084 				} else {
1085 					// parent dir and name selected
1086 					error = message->FindRef("directory", &dir);
1087 					if (error == B_OK)
1088 						error = message->FindString("name", &name);
1089 				}
1090 				if (error == B_OK)
1091 					SaveAsQueryOrTemplate(&dir, name, true);
1092 			}
1093 			break;
1094 
1095 		case B_COPY_TARGET:
1096 			{
1097 				// finish drag&drop
1098 				const char *str;
1099 				const char *mimeType = NULL;
1100 				const char *actionSpecifier = NULL;
1101 				if (message->FindString("be:types", &str) == B_OK
1102 					&& strcasecmp(str, B_FILE_MIME_TYPE) == 0
1103 					&& (message->FindString("be:actionspecifier", &actionSpecifier) == B_OK
1104 						|| message->FindString("be:filetypes", &mimeType) == B_OK)
1105 					&& message->FindString("name", &name) == B_OK
1106 					&& message->FindRef("directory", &dir) == B_OK) {
1107 
1108 					bool query = false;
1109 					bool queryTemplate = false;
1110 
1111 					if (actionSpecifier
1112 						&& strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[0]) == 0)
1113 						query = true;
1114 					else if (actionSpecifier
1115 						&& strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[1]) == 0)
1116 						queryTemplate = true;
1117 					else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[0]) == 0)
1118 						query = true;
1119 					else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[1]) == 0)
1120 						queryTemplate = true;
1121 
1122 					if (query || queryTemplate)
1123 						SaveAsQueryOrTemplate(&dir, name, queryTemplate);
1124 				}
1125 			}
1126 			break;
1127 
1128 		default:
1129 			_inherited::MessageReceived(message);
1130 			break;
1131 	}
1132 }
1133 
1134 
1135 void
1136 FindPanel::SaveAsQueryOrTemplate(const entry_ref *dir, const char *name, bool queryTemplate)
1137 {
1138 	BDirectory directory(dir);
1139 	BFile file(&directory, name, O_RDWR | O_CREAT | O_TRUNC);
1140 	BNodeInfo(&file).SetType(queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE);
1141 
1142 	BMessage attach(kAttachFile);
1143 	attach.AddRef("directory", dir);
1144 	attach.AddString("name", name);
1145 	attach.AddBool("template", queryTemplate);
1146 	Window()->PostMessage(&attach, 0);
1147 }
1148 
1149 
1150 void
1151 FindPanel::BuildAttrQuery(BQuery *query, bool &dynamicDate) const
1152 {
1153 	dynamicDate = false;
1154 
1155 	// go through each attrview and add the attr and comparison info
1156 	for (int32 index = 0; index < fAttrViewList.CountItems(); index++) {
1157 
1158 		TAttrView *view = fAttrViewList.ItemAt(index);
1159 		BString title;
1160 		title << "TextEntry" << index;
1161 
1162 		BTextControl *textControl = dynamic_cast<BTextControl *>
1163 			(view->FindView(title.String()));
1164 		if (!textControl)
1165 			return;
1166 
1167 		BMenuField *menuField = dynamic_cast<BMenuField *>(view->FindView("MenuField"));
1168 		if (!menuField)
1169 			return;
1170 
1171 		BMenuItem *item = menuField->Menu()->FindMarked();
1172 		if (!item)
1173 			continue;
1174 
1175 		BMessage *message = item->Message();
1176 		int32 type;
1177 		if (message->FindInt32("type", &type) == B_OK) {
1178 
1179 			const char *str;
1180 			if (message->FindString("name", &str) == B_OK)
1181 				query->PushAttr(str);
1182 			else
1183 				query->PushAttr(item->Label());
1184 
1185 			switch (type) {
1186 				case B_STRING_TYPE:
1187 					query->PushString(textControl->TextView()->Text(), true);
1188 					break;
1189 
1190 				case B_TIME_TYPE:
1191 					{
1192 						int flags = 0;
1193 						DEBUG_ONLY(time_t result =)
1194 						parsedate_etc(textControl->TextView()->Text(), -1, &flags);
1195 						dynamicDate = (flags & PARSEDATE_RELATIVE_TIME) != 0;
1196 						PRINT(("parsedate_etc - date is %srelative, %l\n",
1197 							dynamicDate ? "" : "not ", result));
1198 
1199 						query->PushDate(textControl->TextView()->Text());
1200 					}
1201 					break;
1202 
1203 				case B_BOOL_TYPE:
1204 					{
1205 						uint32 value;
1206 						if (strcasecmp(textControl->TextView()->Text(), "true") == 0)
1207 							value = 1;
1208 						else if (strcasecmp(textControl->TextView()->Text(), "true") == 0)
1209 							value = 1;
1210 						else
1211 							value = (uint32)atoi(textControl->TextView()->Text());
1212 
1213 						value %= 2;
1214 						query->PushUInt32(value);
1215 					}
1216 					break;
1217 
1218 				case B_UINT8_TYPE:
1219 				case B_UINT16_TYPE:
1220 				case B_UINT32_TYPE:
1221 					query->PushUInt32((uint32)StringToScalar(textControl->TextView()->Text()));
1222 					break;
1223 
1224 				case B_INT8_TYPE:
1225 				case B_INT16_TYPE:
1226 				case B_INT32_TYPE:
1227 					query->PushInt32((int32)StringToScalar(textControl->TextView()->Text()));
1228 					break;
1229 
1230 				case B_UINT64_TYPE:
1231 					query->PushUInt64((uint64)StringToScalar(textControl->TextView()->Text()));
1232 					break;
1233 
1234 				case B_OFF_T_TYPE:
1235 				case B_INT64_TYPE:
1236 					query->PushInt64(StringToScalar(textControl->TextView()->Text()));
1237 					break;
1238 
1239 				case B_FLOAT_TYPE:
1240 					{
1241 						float floatVal;
1242 						sscanf(textControl->TextView()->Text(), "%f", &floatVal);
1243 						query->PushFloat(floatVal);
1244 					}
1245 					break;
1246 
1247 				case B_DOUBLE_TYPE:
1248 					{
1249 						double doubleVal;
1250 						sscanf(textControl->TextView()->Text(), "%lf", &doubleVal);
1251 						query->PushDouble(doubleVal);
1252 					}
1253 					break;
1254 
1255 			}
1256 		}
1257 
1258 		query_op theOperator;
1259 		BMenuItem *operatorItem = item->Submenu()->FindMarked();
1260 		if (operatorItem && operatorItem->Message() != NULL) {
1261 			operatorItem->Message()->FindInt32("operator", (int32 *)&theOperator);
1262 			query->PushOp(theOperator);
1263 		} else
1264 			query->PushOp(B_EQ);
1265 
1266 		// add logic based on selection in Logic menufield
1267 		if (index > 0) {
1268 			TAttrView *prevView = fAttrViewList.ItemAt(index - 1);
1269 			menuField = dynamic_cast<BMenuField *>(prevView->FindView("Logic"));
1270 			if (menuField) {
1271 				item = menuField->Menu()->FindMarked();
1272 				if (item) {
1273 					message = item->Message();
1274 					message->FindInt32("combine", (int32 *)&theOperator);
1275 					query->PushOp(theOperator);
1276 				}
1277 			} else
1278 				query->PushOp(B_AND);
1279 		}
1280 	}
1281 }
1282 
1283 
1284 void
1285 FindPanel::PushMimeType(BQuery *query) const
1286 {
1287 	const char *type;
1288 	if (CurrentMimeType(&type) == NULL)
1289 		return;
1290 
1291 	if (strcmp(kAllMimeTypes, type)) {
1292 		// add an asterisk if we are searching for a supertype
1293 		char buffer[B_FILE_NAME_LENGTH];
1294 		if (strchr(type,'/') == NULL) {
1295 			strcpy(buffer,type);
1296 			strcat(buffer,"/*");
1297 			type = buffer;
1298 		}
1299 
1300 		query->PushAttr(kAttrMIMEType);
1301 		query->PushString(type);
1302 		query->PushOp(B_EQ);
1303 		query->PushOp(B_AND);
1304 	}
1305 }
1306 
1307 
1308 void
1309 FindPanel::GetByAttrPredicate(BQuery *query, bool &dynamicDate) const
1310 {
1311 	ASSERT(Mode() == (int32)kByAttributeItem);
1312 	BuildAttrQuery(query, dynamicDate);
1313 	PushMimeType(query);
1314 }
1315 
1316 
1317 void
1318 FindPanel::GetDefaultName(BString &result) const
1319 {
1320 	BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
1321 	switch (Mode()) {
1322 		case kByNameItem:
1323 			result << "Name = " << textControl->TextView()->Text();
1324 			break;
1325 
1326 		case kByFormulaItem:
1327 			result << "Formula " << textControl->TextView()->Text();
1328 			break;
1329 
1330 		case kByAttributeItem:
1331 		{
1332 			BMenuItem *item = fMimeTypeMenu->FindMarked();
1333 			if (item != NULL)
1334 				result << item->Label() << ": ";
1335 
1336 			for (int32 i = 0; i < fAttrViewList.CountItems(); i++) {
1337 				fAttrViewList.ItemAt(i)->GetDefaultName(result);
1338 				if (i + 1 < fAttrViewList.CountItems())
1339 					result << ", ";
1340 			}
1341 			break;
1342 		}
1343 	}
1344 }
1345 
1346 
1347 const char *
1348 FindPanel::UserSpecifiedName() const
1349 {
1350 	if (fQueryName->Text()[0] == '\0')
1351 		return NULL;
1352 
1353 	return fQueryName->Text();
1354 }
1355 
1356 
1357 void
1358 FindPanel::GetByNamePredicate(BQuery *query) const
1359 {
1360 	ASSERT(Mode() == (int32)kByNameItem);
1361 	BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("TextControl"));
1362 	ASSERT(textControl);
1363 
1364 	query->PushAttr("name");
1365 	query->PushString(textControl->TextView()->Text(), true);
1366 
1367 	if (strstr(textControl->TextView()->Text(), "*"))
1368 		// assume pattern is a regular expression and try doing an exact match
1369 		query->PushOp(B_EQ);
1370 	else
1371 		query->PushOp(B_CONTAINS);
1372 
1373 	PushMimeType(query);
1374 }
1375 
1376 
1377 void
1378 FindPanel::SwitchMode(uint32 mode)
1379 {
1380 	if (fMode == mode)
1381 		// no work, bail
1382 		return;
1383 
1384 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1385 	ASSERT(box);
1386 
1387 	uint32 oldMode = fMode;
1388 	BString buffer;
1389 
1390 	switch (mode) {
1391 		case kByFormulaItem:
1392 			if (oldMode == kByAttributeItem || oldMode == kByNameItem) {
1393 				BQuery query;
1394 				if (oldMode == kByAttributeItem) {
1395 					bool dummy;
1396 					GetByAttrPredicate(&query, dummy);
1397 				} else
1398 					GetByNamePredicate(&query);
1399 
1400 				query.GetPredicate(&buffer);
1401 			}
1402 			// fall thru
1403 
1404 		case kByNameItem:
1405 			{
1406 				fMode = mode;
1407 				Window()->ResizeTo(Window()->Frame().Width(),
1408 					ViewHeightForMode(mode, fLatch->Value() != 0));
1409 				BRect bounds(Bounds());
1410 				bounds.InsetBy(15, 30);
1411 				bounds.bottom -= 10;
1412 				if (fLatch->Value())
1413 					bounds.bottom -= kMoreOptionsDelta;
1414 				box->ResizeTo(bounds.Width(), BoxHeightForMode(mode, fLatch->Value() != 0));
1415 
1416 				RemoveByAttributeItems();
1417 				ShowOrHideMimeTypeMenu();
1418 				AddByNameOrFormulaItems();
1419 
1420 				if (buffer.Length()) {
1421 					ASSERT(mode == kByFormulaItem || oldMode == kByAttributeItem);
1422 					BTextControl *textControl = dynamic_cast<BTextControl *>
1423 						(FindView("TextControl"));
1424 					textControl->SetText(buffer.String());
1425 				}
1426 				break;
1427 			}
1428 
1429 		case kByAttributeItem:
1430 			{
1431 				fMode = mode;
1432 				box->ResizeTo(box->Bounds().Width(),
1433 					BoxHeightForMode(mode, fLatch->Value() != 0));
1434 
1435 				Window()->ResizeTo(Window()->Frame().Width(),
1436 					ViewHeightForMode(mode, fLatch->Value() != 0));
1437 
1438 				BTextControl *textControl = dynamic_cast<BTextControl *>
1439 					(FindView("TextControl"));
1440 
1441 				if (textControl) {
1442 					textControl->RemoveSelf();
1443 					delete textControl;
1444 				}
1445 
1446 				ShowOrHideMimeTypeMenu();
1447 				AddAttrView();
1448 				break;
1449 			}
1450 	}
1451 }
1452 
1453 
1454 BMenuItem *
1455 FindPanel::CurrentMimeType(const char **type) const
1456 {
1457 	// search for marked item in the list
1458 	BMenuItem *item = MimeTypeMenu()->FindMarked();
1459 
1460 	// if it's one of the most used items, ignore it
1461 	if (item != NULL && MimeTypeMenu()->IndexOf(item) != 0 && item->Submenu() == NULL)
1462 		item = NULL;
1463 
1464 	if (item == NULL) {
1465 		for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) {
1466 			BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu();
1467 			if (submenu != NULL && (item = submenu->FindMarked()) != NULL)
1468 				break;
1469 		}
1470 	}
1471 
1472 	if (type && item != NULL) {
1473 		BMessage *message = item->Message();
1474 		if (!message)
1475 			return NULL;
1476 
1477 		if (message->FindString("mimetype", type) != B_OK)
1478 			return NULL;
1479 	}
1480 	return item;
1481 }
1482 
1483 
1484 status_t
1485 FindPanel::SetCurrentMimeType(BMenuItem *item)
1486 {
1487 	// unmark old MIME type (in most used list, and the tree)
1488 
1489 	BMenuItem *marked = CurrentMimeType();
1490 	if (marked != NULL) {
1491 		marked->SetMarked(false);
1492 
1493 		if ((marked = MimeTypeMenu()->FindMarked()) != NULL)
1494 			marked->SetMarked(false);
1495 	}
1496 
1497 	// mark new MIME type (in most used list, and the tree)
1498 
1499 	if (item != NULL) {
1500 		item->SetMarked(true);
1501 		fMimeTypeField->MenuItem()->SetLabel(item->Label());
1502 
1503 		BMenuItem *search;
1504 		for (int32 i = 2;(search = MimeTypeMenu()->ItemAt(i)) != NULL;i++) {
1505 			if (item == search || !search->Label())
1506 				continue;
1507 			if (!strcmp(item->Label(),search->Label())) {
1508 				search->SetMarked(true);
1509 				break;
1510 			}
1511 			BMenu *submenu = search->Submenu();
1512 			if (submenu) {
1513 				for (int32 j = submenu->CountItems();j-- > 0;) {
1514 					BMenuItem *sub = submenu->ItemAt(j);
1515 					if (!strcmp(item->Label(),sub->Label())) {
1516 						sub->SetMarked(true);
1517 						break;
1518 					}
1519 				}
1520 			}
1521 		}
1522 	}
1523 	return B_OK;
1524 }
1525 
1526 
1527 status_t
1528 FindPanel::SetCurrentMimeType(const char *label)
1529 {
1530 	// unmark old MIME type (in most used list, and the tree)
1531 
1532 	BMenuItem *marked = CurrentMimeType();
1533 	if (marked != NULL) {
1534 		marked->SetMarked(false);
1535 
1536 		if ((marked = MimeTypeMenu()->FindMarked()) != NULL)
1537 			marked->SetMarked(false);
1538 	}
1539 
1540 	// mark new MIME type (in most used list, and the tree)
1541 
1542 	fMimeTypeField->MenuItem()->SetLabel(label);
1543 	bool found = false;
1544 
1545 	for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) {
1546 		BMenuItem *item = MimeTypeMenu()->ItemAt(index);
1547 		BMenu *submenu = item->Submenu();
1548 		if (submenu != NULL && !found) {
1549 			for (int32 subIndex = submenu->CountItems(); subIndex-- > 0;) {
1550 				BMenuItem *subItem = submenu->ItemAt(subIndex);
1551 				if (subItem->Label() != NULL && !strcmp(label, subItem->Label())) {
1552 					subItem->SetMarked(true);
1553 					found = true;
1554 				}
1555 			}
1556 		}
1557 		if (item->Label() != NULL && !strcmp(label, item->Label())) {
1558 			item->SetMarked(true);
1559 			return B_OK;
1560 		}
1561 	}
1562 
1563 	return found ? B_OK : B_ENTRY_NOT_FOUND;
1564 }
1565 
1566 
1567 bool
1568 FindPanel::AddOneMimeTypeToMenu(const ShortMimeInfo *info, void *castToMenu)
1569 {
1570 	BPopUpMenu *menu = static_cast<BPopUpMenu *>(castToMenu);
1571 
1572 	BMimeType type(info->InternalName());
1573 	BMimeType super;
1574 	type.GetSupertype(&super);
1575 	if (super.InitCheck() < B_OK)
1576 		return false;
1577 
1578 	BMenuItem *superItem = menu->FindItem(super.Type());
1579 	if (superItem != NULL) {
1580 		BMessage *msg = new BMessage(kMIMETypeItem);
1581 		msg->AddString("mimetype", info->InternalName());
1582 
1583 		superItem->Submenu()->AddItem(new IconMenuItem(info->ShortDescription(),
1584 			msg, info->InternalName(), B_MINI_ICON));
1585 	}
1586 
1587 	return false;
1588 }
1589 
1590 
1591 void
1592 FindPanel::AddMimeTypesToMenu()
1593 {
1594 	BMessage *itemMessage = new BMessage(kMIMETypeItem);
1595 	itemMessage->AddString("mimetype", kAllMimeTypes);
1596 	MimeTypeMenu()->AddItem(new BMenuItem("All files and folders", itemMessage));
1597 	MimeTypeMenu()->AddSeparatorItem();
1598 	MimeTypeMenu()->ItemAt(0)->SetMarked(true);
1599 
1600 	// add recent MIME types
1601 
1602 	TTracker *tracker = dynamic_cast<TTracker *>(be_app);
1603 
1604 	BList list;
1605 	if (gMostUsedMimeTypes.ObtainList(&list) && tracker) {
1606 		int32 count = 0;
1607 		for (int32 index = 0; index < list.CountItems(); index++) {
1608 			const char *name = (const char *)list.ItemAt(index);
1609 
1610 			const ShortMimeInfo *info;
1611 			if ((info = tracker->MimeTypes()->FindMimeType(name)) == NULL)
1612 				continue;
1613 
1614 			BMessage *message = new BMessage(kMIMETypeItem);
1615 			message->AddString("mimetype", info->InternalName());
1616 
1617 			MimeTypeMenu()->AddItem(new BMenuItem(name, message));
1618 			count++;
1619 		}
1620 		if (count != 0)
1621 			MimeTypeMenu()->AddSeparatorItem();
1622 
1623 		gMostUsedMimeTypes.ReleaseList();
1624 	}
1625 
1626 	// add MIME type tree list
1627 
1628 	BMessage types;
1629 	if (BMimeType::GetInstalledSupertypes(&types) == B_OK) {
1630 		const char *superType;
1631 		int32 index = 0;
1632 
1633 		while (types.FindString("super_types",index++,&superType) == B_OK) {
1634 			BMenu *superMenu = new BMenu(superType);
1635 
1636 			BMessage *message = new BMessage(kMIMETypeItem);
1637 			message->AddString("mimetype", superType);
1638 
1639 			MimeTypeMenu()->AddItem(new IconMenuItem(superMenu, message,
1640 				superType, B_MINI_ICON));
1641 
1642 			// the MimeTypeMenu's font is not correct at this time
1643 			superMenu->SetFont(be_plain_font);
1644 		}
1645 	}
1646 
1647 	if (tracker)
1648 		tracker->MimeTypes()->EachCommonType(&FindPanel::AddOneMimeTypeToMenu,
1649 			MimeTypeMenu());
1650 
1651 	// remove empty super type menus (and set target)
1652 
1653 	for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) {
1654 		BMenuItem *item = MimeTypeMenu()->ItemAt(index);
1655 		BMenu *submenu = item->Submenu();
1656 		if (submenu != NULL) {
1657 			if (submenu->CountItems() == 0) {
1658 				MimeTypeMenu()->RemoveItem(item);
1659 				delete item;
1660 			} else
1661 				submenu->SetTargetForItems(this);
1662 		}
1663 	}
1664 
1665 	MimeTypeMenu()->SetTargetForItems(this);
1666 }
1667 
1668 
1669 void
1670 FindPanel::AddVolumes(BMenu *menu)
1671 {
1672 // ToDo: add calls to this to rebuild the menu when a volume gets mounted
1673 
1674 	BMessage *message = new BMessage(kVolumeItem);
1675 	message->AddInt32("device", -1);
1676 	menu->AddItem(new BMenuItem("All disks", message));
1677 	menu->AddSeparatorItem();
1678 	PopUpMenuSetTitle(menu, "All disks");
1679 
1680 	BVolumeRoster roster;
1681 	BVolume volume;
1682 	roster.Rewind();
1683 	while (roster.GetNextVolume(&volume) == B_OK) {
1684 		if (volume.IsPersistent() && volume.KnowsQuery()) {
1685 			BDirectory root;
1686 			if (volume.GetRootDirectory(&root) != B_OK)
1687 				continue;
1688 
1689 			BEntry entry;
1690 			root.GetEntry(&entry);
1691 
1692 			Model model(&entry, true);
1693 			if (model.InitCheck() != B_OK)
1694 				continue;
1695 
1696 			message = new BMessage(kVolumeItem);
1697 			message->AddInt32("device", volume.Device());
1698 			menu->AddItem(new ModelMenuItem(&model, model.Name(), message));
1699 		}
1700 	}
1701 
1702 	if (menu->ItemAt(0))
1703 		menu->ItemAt(0)->SetMarked(true);
1704 
1705 	menu->SetTargetForItems(this);
1706 }
1707 
1708 
1709 typedef std::pair<entry_ref, uint32> EntryWithDate;
1710 
1711 static int
1712 SortByDatePredicate(const EntryWithDate *entry1, const EntryWithDate *entry2)
1713 {
1714 	return entry1->second > entry2->second ?
1715 		-1 : (entry1->second == entry2->second ? 0 : 1);
1716 }
1717 
1718 struct AddOneRecentParams {
1719 	BMenu *menu;
1720 	const BMessenger *target;
1721 	uint32 what;
1722 };
1723 
1724 static const entry_ref *
1725 AddOneRecentItem(const entry_ref *ref, void *castToParams)
1726 {
1727 	AddOneRecentParams *params = (AddOneRecentParams *)castToParams;
1728 
1729 	BMessage *message = new BMessage(params->what);
1730 	message->AddRef("refs", ref);
1731 
1732 	char type[B_MIME_TYPE_LENGTH];
1733 	BNode node(ref);
1734 	BNodeInfo(&node).GetType(type);
1735 	BMenuItem *item = new IconMenuItem(ref->name, message, type, B_MINI_ICON);
1736 	item->SetTarget(*params->target);
1737 	params->menu->AddItem(item);
1738 
1739 	return NULL;
1740 }
1741 
1742 
1743 void
1744 FindPanel::AddRecentQueries(BMenu *menu, bool addSaveAsItem, const BMessenger *target,
1745 	uint32 what)
1746 {
1747 	BObjectList<entry_ref> templates(10, true);
1748 	BObjectList<EntryWithDate> recentQueries(10, true);
1749 
1750 	// find all the queries on all volumes
1751 	BVolumeRoster roster;
1752 	BVolume volume;
1753 	roster.Rewind();
1754 	while (roster.GetNextVolume(&volume) == B_OK) {
1755 		if (volume.IsPersistent() && volume.KnowsQuery() && volume.KnowsAttr()) {
1756 
1757 			BQuery query;
1758 			query.SetVolume(&volume);
1759 			query.SetPredicate("_trk/recentQuery == 1");
1760 			if (query.Fetch() != B_OK)
1761 				continue;
1762 
1763 			entry_ref ref;
1764 			while (query.GetNextRef(&ref) == B_OK) {
1765 				// ignore queries in the Trash
1766 				if (FSInTrashDir(&ref))
1767 					continue;
1768 
1769 				char type[B_MIME_TYPE_LENGTH];
1770 				BNode node(&ref);
1771 				BNodeInfo(&node).GetType(type);
1772 
1773 				if (strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0)
1774 					templates.AddItem(new entry_ref(ref));
1775 				else {
1776 					uint32 changeTime;
1777 					if (node.ReadAttr(kAttrQueryLastChange, B_INT32_TYPE, 0,
1778 						&changeTime, sizeof(uint32)) != sizeof(uint32))
1779 						continue;
1780 
1781 					recentQueries.AddItem(new EntryWithDate(ref, changeTime));
1782 				}
1783 
1784 			}
1785 		}
1786 	}
1787 
1788 	// we are only adding last ten queries
1789 	recentQueries.SortItems(SortByDatePredicate);
1790 
1791 	// but all templates
1792 	AddOneRecentParams params;
1793 	params.menu = menu;
1794 	params.target = target;
1795 	params.what = what;
1796 	templates.EachElement(AddOneRecentItem, &params);
1797 
1798 	int32 count = recentQueries.CountItems();
1799 	// show only up to 10 recent queries
1800 	if (count > 10)
1801 		count = 10;
1802 
1803 	if (templates.CountItems() && count)
1804 		menu->AddSeparatorItem();
1805 
1806 	for (int32 index = 0; index < count; index++)
1807 		AddOneRecentItem(&recentQueries.ItemAt(index)->first, &params);
1808 
1809 
1810 	if (addSaveAsItem) {
1811 		// add a Save as template item
1812 		if (count || templates.CountItems())
1813 			menu->AddSeparatorItem();
1814 
1815 		BMessage *message = new BMessage(kRunSaveAsTemplatePanel);
1816 		BMenuItem *item = new BMenuItem("Save Query as template"B_UTF8_ELLIPSIS, message);
1817 		menu->AddItem(item);
1818 	}
1819 }
1820 
1821 
1822 void
1823 FindPanel::AddOneAttributeItem(BBox *box, BRect rect)
1824 {
1825 	TAttrView *attrView = new TAttrView(rect, fAttrViewList.CountItems());
1826 	fAttrViewList.AddItem(attrView);
1827 
1828 	box->AddChild(attrView);
1829 	attrView->MakeTextViewFocus();
1830 }
1831 
1832 
1833 void
1834 FindPanel::SetUpAddRemoveButtons(BBox *box)
1835 {
1836 	BButton *button = Window() != NULL
1837 		? dynamic_cast<BButton *>(Window()->FindView("remove"))
1838 		: NULL;
1839 	if (button == NULL) {
1840 		BRect rect = box->Bounds();
1841 		rect.InsetBy(5, 10);
1842 		rect.top = rect.bottom - 20;
1843 		rect.right = rect.left + 22 + be_plain_font->StringWidth("Add");
1844 
1845 		button = new BButton(rect, "add", "Add", new BMessage(kAddItem),
1846 			B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
1847 		button->SetTarget(this);
1848 		box->AddChild(button);
1849 
1850 		rect.OffsetBy(rect.Width() + 6, 0);
1851 		rect.right = rect.left + 22 + be_plain_font->StringWidth("Remove");
1852 		button = new BButton(rect, "remove", "Remove", new BMessage(kRemoveItem),
1853 			B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
1854 
1855 		button->SetEnabled(false);
1856 		button->SetTarget(this);
1857 		box->AddChild(button);
1858 	}
1859 	// enable remove button as needed
1860 	button->SetEnabled(fAttrViewList.CountItems() > 1);
1861 }
1862 
1863 
1864 void
1865 FindPanel::FillCurrentQueryName(BTextControl *queryName, FindWindow *window)
1866 {
1867 	ASSERT(window);
1868 	queryName->SetText(window->QueryName());
1869 }
1870 
1871 
1872 void
1873 FindPanel::AddAttrView()
1874 {
1875 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1876 	BRect bounds(Bounds());
1877 
1878 	TAttrView *previous = fAttrViewList.LastItem();
1879 
1880 	if (previous)
1881 		Window()->ResizeBy(0, 30);
1882 
1883 	bounds = Bounds();
1884 	bounds.InsetBy(15, 30);
1885 	bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0);
1886 
1887 	if (previous) {
1888 		box->ResizeTo(bounds.Width(), bounds.Height());
1889 		bounds = previous->Frame();
1890 		bounds.OffsetBy(0, 30);
1891 	} else {
1892 		bounds = box->Bounds();
1893 		bounds.InsetBy(5, 5);
1894 		bounds.bottom = bounds.top + 25;
1895 	}
1896 	AddOneAttributeItem(box, bounds);
1897 
1898 		// add logic to previous attrview
1899 	if (previous)
1900 		previous->AddLogicMenu();
1901 
1902 	SetUpAddRemoveButtons(box);
1903 
1904 	// populate mime popup
1905 	TAttrView *last = fAttrViewList.LastItem();
1906 	last->AddMimeTypeAttrs();
1907 }
1908 
1909 
1910 void
1911 FindPanel::RemoveAttrView()
1912 {
1913 	if (fAttrViewList.CountItems() < 2)
1914 		return;
1915 
1916 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
1917 	TAttrView *attrView = fAttrViewList.LastItem();
1918 	if (!box || !attrView)
1919 		return;
1920 
1921 	Window()->ResizeBy(0, -30);
1922 	BRect bounds(Bounds());
1923 	bounds.InsetBy(15, 30);
1924 	bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0);
1925 	box->ResizeTo(bounds.Width(), bounds.Height());
1926 
1927 	fAttrViewList.RemoveItem(attrView);
1928 	attrView->RemoveSelf();
1929 	delete attrView;
1930 
1931 	attrView = fAttrViewList.LastItem();
1932 	attrView->RemoveLogicMenu();
1933 	attrView->MakeTextViewFocus();
1934 
1935 	if (fAttrViewList.CountItems() != 1)
1936 		return;
1937 
1938 	BButton *button = dynamic_cast<BButton *>(Window()->FindView("remove"));
1939 	if (button)
1940 		button->SetEnabled(false);
1941 }
1942 
1943 
1944 uint32
1945 FindPanel::InitialMode(const BNode *node)
1946 {
1947 	if (!node || node->InitCheck() != B_OK)
1948 		return kByNameItem;
1949 
1950 	uint32 result;
1951 	if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
1952 		(int32 *)&result, sizeof(int32)) <= 0)
1953 		return kByNameItem;
1954 
1955 	return result;
1956 }
1957 
1958 
1959 int32
1960 FindPanel::InitialAttrCount(const BNode *node)
1961 {
1962 	if (!node || node->InitCheck() != B_OK)
1963 		return 1;
1964 
1965 	int32 result;
1966 	if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
1967 		&result, sizeof(int32)) <= 0)
1968 		return 1;
1969 
1970 	return result;
1971 }
1972 
1973 
1974 static int32
1975 SelectItemWithLabel(BMenu *menu, const char *label)
1976 {
1977 	for (int32 index = menu->CountItems(); index-- > 0;)  {
1978 		BMenuItem *item = menu->ItemAt(index);
1979 
1980 		if (strcmp(label, item->Label()) == 0) {
1981 			item->SetMarked(true);
1982 			return index;
1983 		}
1984 	}
1985 	return -1;
1986 }
1987 
1988 
1989 void
1990 FindPanel::SaveWindowState(BNode *node, bool editTemplate)
1991 {
1992 	ASSERT(node->InitCheck() == B_OK);
1993 
1994 	BMenuItem *item = CurrentMimeType();
1995 	if (item) {
1996 		BString label(item->Label());
1997 		node->WriteAttrString(kAttrQueryInitialMime, &label);
1998 	}
1999 
2000 	uint32 mode = Mode();
2001 	node->WriteAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0,
2002 		(int32 *)&mode, sizeof(int32));
2003 
2004 	MoreOptionsStruct saveMoreOptions;
2005 	saveMoreOptions.showMoreOptions = fLatch->Value() != 0;
2006 
2007 	saveMoreOptions.searchTrash = fSearchTrashCheck->Value() != 0;
2008 	saveMoreOptions.temporary = fTemporaryCheck->Value() != 0;
2009 
2010 	if (node->WriteAttr(kAttrQueryMoreOptions, B_RAW_TYPE, 0, &saveMoreOptions,
2011 		sizeof(saveMoreOptions)) == sizeof(saveMoreOptions))
2012 		node->RemoveAttr(kAttrQueryMoreOptionsForeign);
2013 
2014 	if (editTemplate) {
2015 		if (UserSpecifiedName()) {
2016 			BString name(UserSpecifiedName());
2017 			node->WriteAttrString(kAttrQueryTemplateName, &name);
2018 		}
2019 	}
2020 
2021 	switch (Mode()) {
2022 		case kByAttributeItem:
2023 			{
2024 				BMessage message;
2025 				int32 count = fAttrViewList.CountItems();
2026 				node->WriteAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0,
2027 					&count, sizeof(int32));
2028 
2029 				for (int32 index = 0; index < count; index++)
2030 					fAttrViewList.ItemAt(index)->SaveState(&message, index);
2031 
2032 				ssize_t size = message.FlattenedSize();
2033 				char *buffer = new char[size];
2034 				status_t result = message.Flatten(buffer, size);
2035 				ASSERT(result == B_OK);
2036 				result = node->WriteAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0,
2037 					buffer, (size_t)size);
2038 				ASSERT(result == size);
2039 				delete [] buffer;
2040 			}
2041 			break;
2042 
2043 		case kByNameItem:
2044 		case kByFormulaItem:
2045 			{
2046 				BTextControl *textControl = dynamic_cast<BTextControl *>
2047 					(FindView("TextControl"));
2048 				ASSERT(textControl);
2049 				BString formula(textControl->TextView()->Text());
2050 				node->WriteAttrString(kAttrQueryInitialString, &formula);
2051 				break;
2052 			}
2053 	}
2054 }
2055 
2056 
2057 void
2058 FindPanel::SwitchToTemplate(const BNode *node)
2059 {
2060 	if (fLatch->Value()) {
2061 		// this is kind of a hack - the following code up to
2062 		// RestoreWindowState assumes the latch is closed
2063 		// Would be nicer if all the size of the window were set once
2064 		// and correctly - this is not easy thought because the latch
2065 		// controls the window size in relative increments
2066 		fLatch->SetValue(0);
2067 		fMoreOptionsPane->SetMode(0);
2068 	}
2069 
2070 	SwitchMode(InitialMode(node));
2071 		// update the menu to correspond to the mode
2072 	MarkNamedMenuItem(fSearchModeMenu, InitialMode(node), true);
2073 
2074  	BRect initialRect(InitialViewSize(node));
2075 	Window()->ResizeTo(initialRect.Width(), initialRect.Height());
2076 	if (Mode() == (int32)kByAttributeItem) {
2077 		RemoveByAttributeItems();
2078 		ResizeAttributeBox(node);
2079 		AddByAttributeItems(node);
2080 	}
2081 
2082 	RestoreWindowState(node);
2083 }
2084 
2085 
2086 void
2087 FindPanel::RestoreMimeTypeMenuSelection(const BNode *node)
2088 {
2089 	if (Mode() == (int32)kByFormulaItem || node == NULL || node->InitCheck() != B_OK)
2090 		return;
2091 
2092 	BString buffer;
2093 	if (node->ReadAttrString(kAttrQueryInitialMime, &buffer) == B_OK)
2094 		SetCurrentMimeType(buffer.String());
2095 }
2096 
2097 
2098 void
2099 FindPanel::RestoreWindowState(const BNode *node)
2100 {
2101 	fMode = InitialMode(node);
2102 	if (!node || node->InitCheck() != B_OK)
2103 		return;
2104 
2105 	ShowOrHideMimeTypeMenu();
2106 	RestoreMimeTypeMenuSelection(node);
2107 	MoreOptionsStruct saveMoreOptions;
2108 
2109 	bool storesMoreOptions = ReadAttr(node, kAttrQueryMoreOptions,
2110 		kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions,
2111 		sizeof(saveMoreOptions), &MoreOptionsStruct::EndianSwap)
2112 		!= kReadAttrFailed;
2113 
2114 	if (storesMoreOptions) {
2115 		// need to sanitize to true or false here, could have picked
2116 		// up garbage from attributes
2117 
2118 		saveMoreOptions.showMoreOptions =
2119 			(saveMoreOptions.showMoreOptions != 0);
2120 
2121 		fLatch->SetValue(saveMoreOptions.showMoreOptions);
2122 		fMoreOptionsPane->SetMode(saveMoreOptions.showMoreOptions);
2123 
2124 		fSearchTrashCheck->SetValue(saveMoreOptions.searchTrash);
2125 		fTemporaryCheck->SetValue(saveMoreOptions.temporary);
2126 
2127 		fQueryName->SetModificationMessage(NULL);
2128 		FillCurrentQueryName(fQueryName, dynamic_cast<FindWindow *>(Window()));
2129 
2130 		// set modification message after checking the temporary check box,
2131 		// and filling out the text control so that we do not
2132 		// always trigger clearing of the temporary check box.
2133 		fQueryName->SetModificationMessage(new BMessage(kNameModifiedMessage));
2134 	}
2135 
2136 	// get volumes to perform query on
2137 	bool searchAllVolumes = true;
2138 
2139 	attr_info info;
2140 	if (node->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) {
2141 		char *buffer = new char[info.size];
2142 		if (node->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size)
2143 			== info.size) {
2144 			BMessage message;
2145 			if (message.Unflatten(buffer) == B_OK) {
2146 				for (int32 index = 0; ;index++) {
2147 					ASSERT(index < 100);
2148 					BVolume volume;
2149 						// match a volume with the info embedded in the message
2150 					status_t result = MatchArchivedVolume(&volume, &message, index);
2151 					if (result == B_OK) {
2152 						char name[256];
2153 						volume.GetName(name);
2154 						SelectItemWithLabel(fVolMenu, name);
2155 						searchAllVolumes = false;
2156 					} else if (result != B_DEV_BAD_DRIVE_NUM)
2157 						// if B_DEV_BAD_DRIVE_NUM, the volume just isn't mounted this
2158 						// time around, keep looking for more
2159 						// if other error, bail
2160 						break;
2161 				}
2162 			}
2163 		}
2164 		delete [] buffer;
2165 	}
2166 	// mark or unmark "All disks"
2167 	fVolMenu->ItemAt(0)->SetMarked(searchAllVolumes);
2168 	ShowVolumeMenuLabel();
2169 
2170 	switch (Mode()) {
2171 		case kByAttributeItem:
2172 			{
2173 				int32 count = InitialAttrCount(node);
2174 
2175 				attr_info info;
2176 				if (node->GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK)
2177 					break;
2178 				char *buffer = new char[info.size];
2179 				if (node->ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size)
2180 					== info.size) {
2181 					BMessage message;
2182 					if (message.Unflatten(buffer) == B_OK)
2183 						for (int32 index = 0; index < count; index++)
2184 							fAttrViewList.ItemAt(index)->RestoreState(message, index);
2185 				}
2186 				delete [] buffer;
2187 				break;
2188 			}
2189 
2190 		case kByNameItem:
2191 		case kByFormulaItem:
2192 			{
2193 				BString buffer;
2194 				if (node->ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) {
2195 					BTextControl *textControl = dynamic_cast<BTextControl *>
2196 						(FindView("TextControl"));
2197 					ASSERT(textControl);
2198 
2199 					textControl->TextView()->SetText(buffer.String());
2200 				}
2201 			}
2202 			break;
2203 	}
2204 
2205 	// try to restore focus and possibly text selection
2206 	BString focusedView;
2207 	if (node->ReadAttrString("_trk/focusedView", &focusedView) == B_OK) {
2208 		BView *view = FindView(focusedView.String());
2209 		if (view) {
2210 			view->MakeFocus();
2211 			BTextControl *textControl = dynamic_cast<BTextControl *>(view);
2212 			if (textControl) {
2213 				int32 selStart = 0, selEnd = LONG_MAX;
2214 				node->ReadAttr("_trk/focusedSelStart", B_INT32_TYPE, 0,
2215 					&selStart, sizeof(selStart));
2216 				node->ReadAttr("_trk/focusedSelEnd", B_INT32_TYPE, 0,
2217 					&selEnd, sizeof(selEnd));
2218 				textControl->TextView()->Select(selStart, selEnd);
2219 			}
2220 		}
2221 	}
2222 }
2223 
2224 
2225 void
2226 FindPanel::ResizeAttributeBox(const BNode *node)
2227 {
2228 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2229 	BRect bounds(box->Bounds());
2230 	int32 count = InitialAttrCount(node);
2231 
2232 	bounds.bottom = count * 30 + 40;
2233 	box->ResizeTo(bounds.Width(), bounds.Height());
2234 }
2235 
2236 
2237 void
2238 FindPanel::AddByAttributeItems(const BNode *node)
2239 {
2240 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2241 	ASSERT(box);
2242 	BRect bounds(box->Bounds());
2243 
2244 	int32 numAttributes = InitialAttrCount(node);
2245 	if (numAttributes < 1)
2246 		numAttributes = 1;
2247 
2248 	BRect rect(bounds);
2249 	rect.InsetBy(5, 5);
2250 	rect.bottom = rect.top + 25;
2251 
2252 	for (int32 index = 0; index < numAttributes; index ++) {
2253 		AddOneAttributeItem(box, rect);
2254 		rect.OffsetBy(0, 30);
2255 	}
2256 	SetUpAddRemoveButtons(box);
2257 }
2258 
2259 
2260 void
2261 FindPanel::AddByNameOrFormulaItems()
2262 {
2263 	BBox *box = dynamic_cast<BBox *>(FindView("Box"));
2264 
2265 	BRect bounds(box->Bounds());
2266 	bounds.InsetBy(10, 10);
2267 	BTextControl *textControl = new BTextControl(bounds, "TextControl", "", "", NULL);
2268 	textControl->SetDivider(0.0f);
2269 	box->AddChild(textControl);
2270 	textControl->MakeFocus();
2271 }
2272 
2273 
2274 void
2275 FindPanel::RemoveAttrViewItems()
2276 {
2277 	for (;;) {
2278 		BView *view = FindView("AttrView");
2279 		if (view == NULL)
2280 			break;
2281 		view->RemoveSelf();
2282 		delete view;
2283 	}
2284 
2285 	fAttrViewList.MakeEmpty();
2286 }
2287 
2288 
2289 void
2290 FindPanel::RemoveByAttributeItems()
2291 {
2292 	RemoveAttrViewItems();
2293 	BView *view = FindView("add");
2294 	if (view) {
2295 		view->RemoveSelf();
2296 		delete view;
2297 	}
2298 
2299 	view = FindView("remove");
2300 	if (view) {
2301 		view->RemoveSelf();
2302 		delete view;
2303 	}
2304 
2305 	view = dynamic_cast<BTextControl *>(FindView("TextControl"));
2306 	if (view) {
2307 		view->RemoveSelf();
2308 		delete view;
2309 	}
2310 }
2311 
2312 
2313 void
2314 FindPanel::ShowOrHideMimeTypeMenu()
2315 {
2316 	BMenuField *menuField = dynamic_cast<BMenuField *>(FindView("MimeTypeMenu"));
2317 	if (Mode() == (int32)kByFormulaItem && !menuField->IsHidden())
2318 		menuField->Hide();
2319 	else if (menuField->IsHidden())
2320 		menuField->Show();
2321 }
2322 
2323 
2324 // #pragma mark -
2325 
2326 
2327 TAttrView::TAttrView(BRect frame, int32 index)
2328 	:	BView(frame, "AttrView", B_FOLLOW_NONE, B_WILL_DRAW)
2329 {
2330 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2331 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2332 
2333 	BPopUpMenu *menu = new BPopUpMenu("PopUp");
2334 
2335 	// add NAME attribute to popup
2336 	BMenu *submenu = new BMenu("Name");
2337 	submenu->SetRadioMode(true);
2338 	submenu->SetFont(be_plain_font);
2339 	BMessage *message = new BMessage(kAttributeItemMain);
2340 	message->AddString("name", "name");
2341 	message->AddInt32("type", B_STRING_TYPE);
2342 	BMenuItem *item = new BMenuItem(submenu, message);
2343 	menu->AddItem(item);
2344 
2345 	const int32 operators[] = {B_CONTAINS, B_EQ, B_NE, B_BEGINS_WITH, B_ENDS_WITH};
2346 	const char *operatorLabels[] = {"contains", "is", "is not", "starts with", "ends with"};
2347 
2348 	for (int32 i = 0;i < 5;i++) {
2349 		message = new BMessage(kAttributeItem);
2350 		message->AddInt32("operator", operators[i]);
2351 		submenu->AddItem(new BMenuItem(operatorLabels[i], message));
2352 	}
2353 
2354 	// mark first item
2355 	menu->ItemAt(0)->SetMarked(true);
2356 	submenu->ItemAt(0)->SetMarked(true);
2357 
2358 	// add SIZE attribute
2359 	submenu = new BMenu("Size");
2360 	submenu->SetRadioMode(true);
2361 	submenu->SetFont(be_plain_font);
2362 	message = new BMessage(kAttributeItemMain);
2363 	message->AddString("name", "size");
2364 	message->AddInt32("type", B_OFF_T_TYPE);
2365 	item = new BMenuItem(submenu, message);
2366 	menu->AddItem(item);
2367 
2368 	message = new BMessage(kAttributeItem);
2369 	message->AddInt32("operator", B_GE);
2370 	submenu->AddItem(new BMenuItem("greater than", message));
2371 
2372 	message = new BMessage(kAttributeItem);
2373 	message->AddInt32("operator", B_LE);
2374 	submenu->AddItem(new BMenuItem("less than", message));
2375 
2376 	message = new BMessage(kAttributeItem);
2377 	message->AddInt32("operator", B_EQ);
2378 	submenu->AddItem(new BMenuItem("is", message));
2379 
2380 	// add "modified" field
2381 	submenu = new BMenu("Modified");
2382 	submenu->SetRadioMode(true);
2383 	submenu->SetFont(be_plain_font);
2384 	message = new BMessage(kAttributeItemMain);
2385 	message->AddString("name", "last_modified");
2386 	message->AddInt32("type", B_TIME_TYPE);
2387 	item = new BMenuItem(submenu, message);
2388 	menu->AddItem(item);
2389 
2390 	message = new BMessage(kAttributeItem);
2391 	message->AddInt32("operator", B_LE);
2392 	submenu->AddItem(new BMenuItem("before", message));
2393 
2394 	message = new BMessage(kAttributeItem);
2395 	message->AddInt32("operator", B_GE);
2396 	submenu->AddItem(new BMenuItem("after", message));
2397 
2398 	BRect bounds(Bounds());
2399 	bounds.right = bounds.left + 100;
2400 	bounds.bottom = bounds.top + 15;
2401 	fMenuField = new BMenuField(bounds, "MenuField", "", menu);
2402 	fMenuField->SetDivider(0.0f);
2403 
2404 	// add text entry box
2405 	bounds = Bounds();
2406 	bounds.left += bounds.right - 180;
2407 	bounds.top += 2;
2408 	bounds.right -= 42;
2409 	BString title("TextEntry");
2410 	title << index;
2411 	fTextControl = new BTextControl(bounds, title.String(), "", "", NULL);
2412 	fTextControl->SetDivider(0.0f);
2413 	AddChild(fTextControl);
2414 
2415 	AddChild(fMenuField);
2416 	// add attributes from currently selected mimetype
2417 }
2418 
2419 
2420 TAttrView::~TAttrView()
2421 {
2422 }
2423 
2424 
2425 void
2426 TAttrView::AttachedToWindow()
2427 {
2428 	BMenu *menu = fMenuField->Menu();
2429 	// target everything
2430 	menu->SetTargetForItems(this);
2431 
2432 	for (int32 index = menu->CountItems() - 1; index >= 0; index--)
2433 		menu->SubmenuAt(index)->SetTargetForItems(this);
2434 }
2435 
2436 
2437 void
2438 TAttrView::MakeTextViewFocus()
2439 {
2440 	fTextControl->MakeFocus();
2441 }
2442 
2443 
2444 void
2445 TAttrView::RestoreState(const BMessage &message, int32 index)
2446 {
2447 	BMenu *menu = fMenuField->Menu();
2448 	// decode menu selections
2449 
2450 	AddMimeTypeAttrs(menu);
2451 
2452 	const char *label;
2453 	if (message.FindString("menuSelection", index, &label) == B_OK) {
2454 		int32 itemIndex = SelectItemWithLabel(menu, label);
2455 		if (itemIndex >=0) {
2456 			menu = menu->SubmenuAt(itemIndex);
2457 			if (menu && message.FindString("subMenuSelection", index, &label)
2458 					== B_OK)
2459 				SelectItemWithLabel(menu, label);
2460 		}
2461 	}
2462 
2463 	// decode attribute text
2464 	ASSERT(fTextControl);
2465 	const char *string;
2466 	if (message.FindString("attrViewText", index, &string) == B_OK)
2467 		fTextControl->TextView()->SetText(string);
2468 
2469 	int32 logicMenuSelectedIndex;
2470 	BMenuField *field = dynamic_cast<BMenuField *>(FindView("Logic"));
2471 	if (message.FindInt32("logicalRelation", index,
2472 		&logicMenuSelectedIndex) == B_OK) {
2473 		if (field)
2474 			field->Menu()->ItemAt(logicMenuSelectedIndex)->SetMarked(true);
2475 		else
2476 			AddLogicMenu(logicMenuSelectedIndex == 0);
2477 	}
2478 }
2479 
2480 
2481 void
2482 TAttrView::SaveState(BMessage *message, int32)
2483 {
2484 	BMenu *menu = fMenuField->Menu();
2485 
2486 	// encode main attribute menu selection
2487 	BMenuItem *item = menu->FindMarked();
2488 	message->AddString("menuSelection", item ? item->Label() : "");
2489 
2490 	// encode submenu selection
2491 	const char *label = "";
2492 	if (item) {
2493 		BMenu *submenu = menu->SubmenuAt(menu->IndexOf(item));
2494 		if (submenu) {
2495 			item = submenu->FindMarked();
2496 			if (item)
2497 				label = item->Label();
2498 		}
2499 	}
2500 	message->AddString("subMenuSelection", label);
2501 
2502 	// encode attribute text
2503 	ASSERT(fTextControl);
2504 	message->AddString("attrViewText", fTextControl->TextView()->Text());
2505 
2506 	BMenuField *field = dynamic_cast<BMenuField *>(FindView("Logic"));
2507 	if (field) {
2508 		BMenuItem *item = field->Menu()->FindMarked();
2509 		ASSERT(item);
2510 		message->AddInt32("logicalRelation", item ? field->Menu()->IndexOf(item) : 0);
2511 	}
2512 }
2513 
2514 void
2515 TAttrView::AddLogicMenu(bool selectAnd)
2516 {
2517 	// add "AND/OR" menu
2518 	BPopUpMenu *menu = new BPopUpMenu("");
2519 	BMessage *message = new BMessage();
2520 	message->AddInt32("combine", B_AND);
2521 	BMenuItem *item = new BMenuItem("And", message);
2522 	menu->AddItem(item);
2523 	if (selectAnd)
2524 		item->SetMarked(true);
2525 
2526 	message = new BMessage();
2527 	message->AddInt32("combine", B_OR);
2528 	item = new BMenuItem("Or", message);
2529 	menu->AddItem(item);
2530 	if (!selectAnd)
2531 		item->SetMarked(true);
2532 
2533 	menu->SetTargetForItems(this);
2534 
2535 	BRect bounds(Bounds());
2536 	bounds.left = bounds.right - 40;
2537 	bounds.bottom = bounds.top + 15;
2538 	BMenuField *menufield = new BMenuField(bounds, "Logic", "", menu);
2539 	menufield->SetDivider(0.0f);
2540 	menufield->HidePopUpMarker();
2541 	AddChild(menufield);
2542 }
2543 
2544 
2545 void
2546 TAttrView::RemoveLogicMenu()
2547 {
2548 	BMenuField *menufield = dynamic_cast<BMenuField *>(FindView("Logic"));
2549 	if (menufield) {
2550 		menufield->RemoveSelf();
2551 		delete menufield;
2552 	}
2553 }
2554 
2555 
2556 void
2557 TAttrView::Draw(BRect)
2558 {
2559 	BMenuItem *item = fMenuField->Menu()->FindMarked();
2560 	if (!item)
2561 		return;
2562 
2563 	if (item->Submenu()->FindMarked()) {
2564 		float width = StringWidth(item->Submenu()->FindMarked()->Label());
2565 		BRect bounds(fTextControl->Frame());
2566 
2567 		// draws the is/contains, etc. string
2568 		bounds.left -= (width + 10);
2569 		bounds.bottom -= 6;
2570 		DrawString(item->Submenu()->FindMarked()->Label(), bounds.LeftBottom());
2571 	}
2572 }
2573 
2574 
2575 void
2576 TAttrView::MessageReceived(BMessage *message)
2577 {
2578 	BMenuItem *item;
2579 
2580 	switch (message->what) {
2581 		case kAttributeItem:
2582 			if (message->FindPointer("source", (void **)&item) != B_OK)
2583 				return;
2584 
2585 			item->Menu()->Superitem()->SetMarked(true);
2586 			Invalidate();
2587 			break;
2588 
2589 		case kAttributeItemMain:
2590 			// in case someone selected just and attribute without the
2591 			// comparator
2592 			if (message->FindPointer("source", (void **)&item) != B_OK)
2593 				return;
2594 
2595 			if (item->Submenu()->ItemAt(0))
2596 				item->Submenu()->ItemAt(0)->SetMarked(true);
2597 			Invalidate();
2598 			break;
2599 
2600 		default:
2601 			_inherited::MessageReceived(message);
2602 			break;
2603 	}
2604 }
2605 
2606 
2607 void
2608 TAttrView::AddMimeTypeAttrs()
2609 {
2610 	BMenu *menu = fMenuField->Menu();
2611 	AddMimeTypeAttrs(menu);
2612 }
2613 
2614 
2615 void
2616 TAttrView::AddAttributes(BMenu *menu, const BMimeType &mimeType)
2617 {
2618 	// only add things to menu which have "user-visible" data
2619 	BMessage attributeMessage;
2620 	if (mimeType.GetAttrInfo(&attributeMessage) != B_OK)
2621 		return;
2622 
2623 	char desc[B_MIME_TYPE_LENGTH];
2624 	mimeType.GetShortDescription(desc);
2625 
2626 	// go through each field in meta mime and add it to a menu
2627 	for (int32 index = 0; ; index++) {
2628 		const char *publicName;
2629 		if (attributeMessage.FindString("attr:public_name", index, &publicName) != B_OK)
2630 			break;
2631 
2632 		if (!attributeMessage.FindBool("attr:viewable"))
2633 			continue;
2634 
2635 		const char *attributeName;
2636 		if (attributeMessage.FindString("attr:name", index, &attributeName) != B_OK)
2637 			continue;
2638 
2639 		int32 type;
2640 		if (attributeMessage.FindInt32("attr:type", index, &type) != B_OK)
2641 			continue;
2642 
2643 		BMenu *submenu = new BMenu(publicName);
2644 		submenu->SetRadioMode(true);
2645 		submenu->SetFont(be_plain_font);
2646 		BMessage *message = new BMessage(kAttributeItemMain);
2647 		message->AddString("name", attributeName);
2648 		message->AddInt32("type", type);
2649 		BMenuItem *item = new BMenuItem(submenu, message);
2650 		menu->AddItem(item);
2651 		menu->SetTargetForItems(this);
2652 
2653 		switch (type) {
2654 			case B_STRING_TYPE:
2655 				message = new BMessage(kAttributeItem);
2656 				message->AddInt32("operator", B_CONTAINS);
2657 				submenu->AddItem(new BMenuItem("contains", message));
2658 
2659 				message = new BMessage(kAttributeItem);
2660 				message->AddInt32("operator", B_EQ);
2661 				submenu->AddItem(new BMenuItem("is", message));
2662 
2663 				message = new BMessage(kAttributeItem);
2664 				message->AddInt32("operator", B_NE);
2665 				submenu->AddItem(new BMenuItem("is not", message));
2666 				submenu->SetTargetForItems(this);
2667 
2668 				message = new BMessage(kAttributeItem);
2669 				message->AddInt32("operator", B_BEGINS_WITH);
2670 				submenu->AddItem(new BMenuItem("starts with", message));
2671 				submenu->SetTargetForItems(this);
2672 
2673 				message = new BMessage(kAttributeItem);
2674 				message->AddInt32("operator", B_ENDS_WITH);
2675 				submenu->AddItem(new BMenuItem("ends with", message));
2676 				break;
2677 
2678 			case B_BOOL_TYPE:
2679 			case B_INT16_TYPE:
2680 			case B_UINT8_TYPE:
2681 			case B_INT8_TYPE:
2682 			case B_UINT16_TYPE:
2683 			case B_INT32_TYPE:
2684 			case B_UINT32_TYPE:
2685 			case B_INT64_TYPE:
2686 			case B_UINT64_TYPE:
2687 			case B_OFF_T_TYPE:
2688 			case B_FLOAT_TYPE:
2689 			case B_DOUBLE_TYPE:
2690 				message = new BMessage(kAttributeItem);
2691 				message->AddInt32("operator", B_EQ);
2692 				submenu->AddItem(new BMenuItem("is", message));
2693 
2694 				message = new BMessage(kAttributeItem);
2695 				message->AddInt32("operator", B_GE);
2696 				submenu->AddItem(new BMenuItem("greater than", message));
2697 
2698 				message = new BMessage(kAttributeItem);
2699 				message->AddInt32("operator", B_LE);
2700 				submenu->AddItem(new BMenuItem("less than", message));
2701 				break;
2702 
2703 			case B_TIME_TYPE:
2704 				message = new BMessage(kAttributeItem);
2705 				message->AddInt32("operator", B_LE);
2706 				submenu->AddItem(new BMenuItem("before", message));
2707 
2708 				message = new BMessage(kAttributeItem);
2709 				message->AddInt32("operator", B_GE);
2710 				submenu->AddItem(new BMenuItem("after", message));
2711 				break;
2712 		}
2713 		submenu->SetTargetForItems(this);
2714 	}
2715 }
2716 
2717 
2718 void
2719 TAttrView::AddMimeTypeAttrs(BMenu *menu)
2720 {
2721 	FindPanel *mainView = dynamic_cast<FindPanel *>(Parent()->
2722 		Parent()->FindView("MainView"));
2723 	if (!mainView)
2724 		return;
2725 
2726 	const char *typeName;
2727 	if (mainView->CurrentMimeType(&typeName) == NULL)
2728 		return;
2729 
2730 	BMimeType mimeType(typeName);
2731 	if (!mimeType.IsInstalled())
2732 		return;
2733 
2734 	if (!mimeType.IsSupertypeOnly()) {
2735 		// add supertype attributes
2736 		BMimeType supertype;
2737 		mimeType.GetSupertype(&supertype);
2738 		AddAttributes(menu, supertype);
2739 	}
2740 
2741 	AddAttributes(menu, mimeType);
2742 }
2743 
2744 
2745 void
2746 TAttrView::GetDefaultName(BString &result) const
2747 {
2748 	BMenuItem *item = NULL;
2749 	if (fMenuField->Menu() != NULL)
2750 		item = fMenuField->Menu()->FindMarked();
2751 	if (item != NULL)
2752 		result << item->Label();
2753 	else
2754 		result << "Name";
2755 
2756 	if (item->Submenu() != NULL)
2757 		item = item->Submenu()->FindMarked();
2758 	else
2759 		item = NULL;
2760 
2761 	if (item != NULL)
2762 		result << " " << item->Label() << " ";
2763 	else
2764 		result << " = ";
2765 
2766 	result << fTextControl->Text();
2767 }
2768 
2769 
2770 // #pragma mark -
2771 
2772 
2773 DeleteTransientQueriesTask::DeleteTransientQueriesTask()
2774 	:	state(kInitial),
2775 		fWalker(NULL)
2776 {
2777 }
2778 
2779 
2780 DeleteTransientQueriesTask::~DeleteTransientQueriesTask()
2781 {
2782 	delete fWalker;
2783 }
2784 
2785 
2786 bool
2787 DeleteTransientQueriesTask::DoSomeWork()
2788 {
2789 	switch (state) {
2790 		case kInitial:
2791 			Initialize();
2792 			break;
2793 
2794 		case kAllocatedWalker:
2795 		case kTraversing:
2796 			if (GetSome()) {
2797 				PRINT(("transient query killer done\n"));
2798 				return true;
2799 			}
2800 			break;
2801 
2802 		case kError:
2803 			return true;
2804 
2805 	}
2806 	return false;
2807 }
2808 
2809 
2810 void
2811 DeleteTransientQueriesTask::Initialize()
2812 {
2813 	PRINT(("starting up transient query killer\n"));
2814 	BPath path;
2815 	status_t result = find_directory(B_USER_DIRECTORY, &path, false);
2816 	if (result != B_OK) {
2817 		state = kError;
2818 		return;
2819 	}
2820 	fWalker = new WALKER_NS::TNodeWalker(path.Path());
2821 	state = kAllocatedWalker;
2822 }
2823 
2824 
2825 const int32 kBatchCount = 100;
2826 
2827 bool
2828 DeleteTransientQueriesTask::GetSome()
2829 {
2830 	state = kTraversing;
2831 	for (int32 count = kBatchCount; count > 0; count--) {
2832 		entry_ref ref;
2833 		if (fWalker->GetNextRef(&ref) != B_OK) {
2834 			state = kError;
2835 			return true;
2836 		}
2837 		Model model(&ref);
2838 		if (model.IsQuery())
2839 			ProcessOneRef(&model);
2840 #if xDEBUG
2841 		else
2842 			PRINT(("transient query killer: %s not a query\n", model.Name()));
2843 #endif
2844 	}
2845 	return false;
2846 }
2847 
2848 
2849 const int32 kDaysToExpire = 7;
2850 
2851 static bool
2852 QueryOldEnough(Model *model)
2853 {
2854 	// check if it is old and ready to be deleted
2855 	time_t now = time(0);
2856 
2857 	tm nowTimeData;
2858 	tm fileModData;
2859 
2860 	localtime_r(&now, &nowTimeData);
2861 	localtime_r(&model->StatBuf()->st_ctime, &fileModData);
2862 
2863 	if ((nowTimeData.tm_mday - fileModData.tm_mday) < kDaysToExpire
2864 		&& (nowTimeData.tm_mday - fileModData.tm_mday) > -kDaysToExpire) {
2865 		PRINT(("query %s, not old enough\n", model->Name()));
2866 		return false;
2867 	}
2868 	return true;
2869 }
2870 
2871 
2872 bool
2873 DeleteTransientQueriesTask::ProcessOneRef(Model *model)
2874 {
2875 	BModelOpener opener(model);
2876 
2877 	// is this a temporary query
2878 	if (!MoreOptionsStruct::QueryTemporary(model->Node())) {
2879 		PRINT(("query %s, not temporary\n", model->Name()));
2880 		return false;
2881 	}
2882 
2883 	if (!QueryOldEnough(model))
2884 		return false;
2885 
2886 	ASSERT(dynamic_cast<TTracker *>(be_app));
2887 
2888 	// check that it is not showing
2889 	if (dynamic_cast<TTracker *>(be_app)->EntryHasWindowOpen(model->EntryRef())) {
2890 		PRINT(("query %s, showing, can't delete\n", model->Name()));
2891 		return false;
2892 	}
2893 
2894 	PRINT(("query %s, old, temporary, not shownig - deleting\n", model->Name()));
2895 	BEntry entry(model->EntryRef());
2896 	entry.Remove();
2897 
2898 	return true;
2899 }
2900 
2901 
2902 class DeleteTransientQueriesFunctor : public FunctionObjectWithResult<bool> {
2903 public:
2904 	DeleteTransientQueriesFunctor(DeleteTransientQueriesTask *task)
2905 		:	task(task)
2906 		{}
2907 
2908 	virtual ~DeleteTransientQueriesFunctor()
2909 		{
2910 			delete task;
2911 		}
2912 
2913 	virtual void operator()()
2914 		{ result = task->DoSomeWork(); }
2915 
2916 private:
2917 	DeleteTransientQueriesTask *task;
2918 };
2919 
2920 
2921 void
2922 DeleteTransientQueriesTask::StartUpTransientQueryCleaner()
2923 {
2924 	// set up a task that wakes up when the machine is idle and starts
2925 	// killing off old transient queries
2926 	DeleteTransientQueriesFunctor *worker
2927 		= new DeleteTransientQueriesFunctor(new DeleteTransientQueriesTask());
2928 	TTracker *tracker = dynamic_cast<TTracker *>(be_app);
2929 	ASSERT(tracker);
2930 	tracker->MainTaskLoop()->RunWhenIdle(worker,
2931 		30 * 60 * 1000000,	// half an hour initial delay
2932 		5 * 60 * 1000000,	// idle for five minutes
2933 		10 * 1000000);
2934 }
2935 
2936 
2937 //	#pragma mark -
2938 
2939 
2940 RecentFindItemsMenu::RecentFindItemsMenu(const char *title, const BMessenger *target,
2941 	uint32 what)
2942 	:	BMenu(title, B_ITEMS_IN_COLUMN),
2943 		fTarget(*target),
2944 		fWhat(what)
2945 {
2946 }
2947 
2948 
2949 void
2950 RecentFindItemsMenu::AttachedToWindow()
2951 {
2952 	// re-populate the menu with fresh items
2953 	for (int32 index = CountItems() - 1; index >= 0; index--)
2954 		delete RemoveItem(index);
2955 
2956 	FindPanel::AddRecentQueries(this, false, &fTarget, fWhat);
2957 	BMenu::AttachedToWindow();
2958 }
2959 
2960 
2961 #if !B_BEOS_VERSION_DANO
2962 _IMPEXP_TRACKER
2963 #endif
2964 BMenu *
2965 TrackerBuildRecentFindItemsMenu(const char *title)
2966 {
2967 	BMessenger tracker(kTrackerSignature);
2968 	return new RecentFindItemsMenu(title, &tracker, B_REFS_RECEIVED);
2969 }
2970 
2971 
2972 //	#pragma mark -
2973 
2974 
2975 DraggableQueryIcon::DraggableQueryIcon(BRect frame, const char *name,
2976 	const BMessage *message, BMessenger messenger, uint32 resizeFlags, uint32 flags)
2977 	:	DraggableIcon(frame, name, B_QUERY_MIMETYPE, B_LARGE_ICON,
2978 			message, messenger, resizeFlags, flags)
2979 {
2980 }
2981 
2982 
2983 bool
2984 DraggableQueryIcon::DragStarted(BMessage *dragMessage)
2985 {
2986 	// override to substitute the user-specified query name
2987 	dragMessage->RemoveData("be:clip_name");
2988 
2989 	FindWindow *window = dynamic_cast<FindWindow *>(Window());
2990 	ASSERT(window);
2991 	dragMessage->AddString("be:clip_name",
2992 		window->BackgroundView()->UserSpecifiedName() ?
2993 			window->BackgroundView()->UserSpecifiedName() : "New Query");
2994 
2995 	return true;
2996 }
2997 
2998 
2999 //	#pragma mark -
3000 
3001 
3002 MostUsedNames::MostUsedNames(const char *fileName, const char *directory, int32 maxCount)
3003 	:
3004 	fFileName(fileName),
3005 	fDirectory(directory),
3006 	fLoaded(false),
3007 	fCount(maxCount)
3008 {
3009 }
3010 
3011 
3012 MostUsedNames::~MostUsedNames()
3013 {
3014 	// only write back settings when we've been used
3015 	if (!fLoaded)
3016 		return;
3017 
3018 	// write most used list to file
3019 
3020 	BPath path;
3021 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
3022 		return;
3023 
3024 	path.Append(fDirectory);
3025 	path.Append(fFileName);
3026 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
3027 	if (file.InitCheck() == B_OK) {
3028 		for (int32 i = 0; i < fList.CountItems(); i++) {
3029 			list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3030 
3031 			char line[B_FILE_NAME_LENGTH + 5];
3032 
3033 			// limit upper bound to react more dynamically to changes
3034 			if (--entry->count > 20)
3035 				entry->count = 20;
3036 
3037 			// if the item hasn't been chosen in a while, remove it
3038 			// (but leave at least one item in the list)
3039 			if (entry->count < -10 && i > 0)
3040 				continue;
3041 
3042 			sprintf(line, "%ld %s\n", entry->count, entry->name);
3043 			if (file.Write(line, strlen(line)) < B_OK)
3044 				break;
3045 		}
3046 	}
3047 	file.Unset();
3048 
3049 	// free data
3050 
3051 	for (int32 i = fList.CountItems(); i-- > 0;) {
3052 		list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3053 		free(entry->name);
3054 		delete entry;
3055 	}
3056 }
3057 
3058 
3059 bool
3060 MostUsedNames::ObtainList(BList *list)
3061 {
3062 	if (!list)
3063 		return false;
3064 
3065 	if (!fLoaded)
3066 		UpdateList();
3067 
3068 	fLock.Lock();
3069 
3070 	list->MakeEmpty();
3071 	for (int32 i = 0; i < fCount; i++) {
3072 		list_entry *entry = static_cast<list_entry *>(fList.ItemAt(i));
3073 		if (entry == NULL)
3074 			return true;
3075 
3076 		list->AddItem(entry->name);
3077 	}
3078 	return true;
3079 }
3080 
3081 
3082 void
3083 MostUsedNames::ReleaseList()
3084 {
3085 	fLock.Unlock();
3086 }
3087 
3088 
3089 void
3090 MostUsedNames::AddName(const char *name)
3091 {
3092 	fLock.Lock();
3093 
3094 	if (!fLoaded)
3095 		LoadList();
3096 
3097 	// remove last entry if there are more than
3098 	// 2*fCount entries in the list
3099 
3100 	list_entry *entry = NULL;
3101 
3102 	if (fList.CountItems() > fCount * 2) {
3103 		entry = static_cast<list_entry *>(fList.RemoveItem(fList.CountItems() - 1));
3104 
3105 		// is this the name we want to add here?
3106 		if (strcmp(name, entry->name)) {
3107 			free(entry->name);
3108 			delete entry;
3109 			entry = NULL;
3110 		} else
3111 			fList.AddItem(entry);
3112 	}
3113 
3114 	if (entry == NULL) {
3115 		for (int32 i = 0; (entry = static_cast<list_entry *>(fList.ItemAt(i))) != NULL; i++)
3116 			if (!strcmp(entry->name, name))
3117 				break;
3118 	}
3119 
3120 	if (entry == NULL) {
3121 		entry = new list_entry;
3122 		entry->name = strdup(name);
3123 		entry->count = 1;
3124 
3125 		fList.AddItem(entry);
3126 	} else if (entry->count < 0)
3127 		entry->count = 1;
3128 	else
3129 		entry->count++;
3130 
3131 	fLock.Unlock();
3132 	UpdateList();
3133 }
3134 
3135 
3136 int
3137 MostUsedNames::CompareNames(const void *a,const void *b)
3138 {
3139 	list_entry *entryA = *(list_entry **)a;
3140 	list_entry *entryB = *(list_entry **)b;
3141 
3142 	if (entryA->count == entryB->count)
3143 		return strcasecmp(entryA->name,entryB->name);
3144 
3145 	return entryB->count - entryA->count;
3146 }
3147 
3148 
3149 void
3150 MostUsedNames::LoadList()
3151 {
3152 	if (fLoaded)
3153 		return;
3154 	fLoaded = true;
3155 
3156 	// load the most used names list
3157 
3158 	BPath path;
3159 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
3160 		return;
3161 
3162 	path.Append(fDirectory);
3163 	path.Append(fFileName);
3164 
3165 	FILE *file = fopen(path.Path(), "r");
3166 	if (file == NULL)
3167 		return;
3168 
3169 	char line[B_FILE_NAME_LENGTH + 5];
3170 	while (fgets(line, sizeof(line), file) != NULL) {
3171 		int32 length = (int32)strlen(line) - 1;
3172 		if (length >= 0 && line[length] == '\n')
3173 			line[length] = '\0';
3174 
3175 		int32 count = atoi(line);
3176 
3177 		char *name = strchr(line, ' ');
3178 		if (name == NULL || *(++name) == '\0')
3179 			continue;
3180 
3181 		list_entry *entry = new list_entry;
3182 		entry->name = strdup(name);
3183 		entry->count = count;
3184 
3185 		fList.AddItem(entry);
3186 	}
3187 	fclose(file);
3188 }
3189 
3190 
3191 void
3192 MostUsedNames::UpdateList()
3193 {
3194 	AutoLock<Benaphore> locker(fLock);
3195 
3196 	if (!fLoaded)
3197 		LoadList();
3198 
3199 	// sort list items
3200 
3201 	fList.SortItems(MostUsedNames::CompareNames);
3202 }
3203 
3204 }	// namespace BPrivate
3205 
3206