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