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