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