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