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