xref: /haiku/src/apps/deskbar/ResourceSet.cpp (revision 2f470aec1c92ce6917b8a903e343795dc77af41f)
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 #define USE_RESOURCES 1
36 
37 #include "ResourceSet.h"
38 
39 #include <Application.h>
40 #include <Autolock.h>
41 #include <Bitmap.h>
42 #include <DataIO.h>
43 #include <Debug.h>
44 #include <Entry.h>
45 #include <File.h>
46 #include <Path.h>
47 #include <String.h>
48 
49 
50 #if USE_RESOURCES
51 #include <Resources.h>
52 #endif
53 
54 
55 #include <stdlib.h>
56 #include <unistd.h>
57 #include <ctype.h>
58 
59 #if USE_RESOURCES
60 #define RESOURCES_ONLY(x) x
61 #else
62 #define RESOURCES_ONLY(x)
63 #endif
64 
65 namespace TResourcePrivate {
66 
67 	class TypeObject {
68 	public:
69 		TypeObject()
70 			:	fDeleteOK(false)
71 		{
72 		}
73 
74 		virtual ~TypeObject()
75 		{
76 			if (!fDeleteOK)
77 				debugger("deleting object owned by BResourceSet");
78 		}
79 
80 		void Delete()
81 		{
82 			fDeleteOK = true;
83 		}
84 
85 	private:
86 		TypeObject(const TypeObject &);
87 		TypeObject &operator=(const TypeObject &);
88 		bool operator==(const TypeObject &);
89 		bool operator!=(const TypeObject &);
90 
91 		bool fDeleteOK;
92 	};
93 
94 	class BitmapTypeItem : public BBitmap, public TypeObject {
95 	public:
96 		BitmapTypeItem(BRect bounds, uint32 flags, color_space depth,
97 			int32 bytesPerRow=B_ANY_BYTES_PER_ROW, screen_id screenID = B_MAIN_SCREEN_ID)
98 			:	BBitmap(bounds, flags, depth, bytesPerRow, screenID)
99 		{
100 		}
101 
102 		BitmapTypeItem(const BBitmap* source, bool accepts_views = false,
103 			bool need_contiguous = false)
104 			:	BBitmap(source, accepts_views, need_contiguous)
105 		{
106 		}
107 
108 		BitmapTypeItem(BMessage *data)
109 			:	BBitmap(data)
110 		{
111 		}
112 
113 		virtual ~BitmapTypeItem()
114 		{
115 		}
116 	};
117 
118 	class StringBlockTypeItem : public TStringBlock, public TypeObject {
119 	public:
120 		StringBlockTypeItem(BDataIO *data)
121 			:	TStringBlock(data)
122 		{
123 		}
124 
125 		StringBlockTypeItem(const void *block, size_t size)
126 			:	TStringBlock(block, size)
127 		{
128 		}
129 
130 		virtual ~StringBlockTypeItem()
131 		{
132 		}
133 	};
134 
135 	class TypeItem {
136 	public:
137 		TypeItem(int32 id, const char *name, const void *data, size_t size)
138 			: fID(id), fName(name),
139 			  fData(const_cast<void*>(data)), fSize(size), fObject(0),
140 			  fOwnData(false), fSourceIsFile(false)
141 		{
142 		}
143 
144 		TypeItem(int32 id, const char *name, BFile *file)
145 			:	fID(id),
146 				fName(name),
147 				fData(NULL),
148 				fSize(0),
149 				fObject(NULL),
150 				fOwnData(true),
151 				fSourceIsFile(false)
152 		{
153 			off_t size;
154 			if (file->GetSize(&size) == B_OK) {
155 				fSize = (size_t)size;
156 				fData = malloc(fSize);
157 				if (file->ReadAt(0, fData, fSize) < B_OK ) {
158 					free(fData);
159 					fData = NULL;
160 					fSize = 0;
161 				}
162 			}
163 		}
164 
165 		virtual ~TypeItem()
166 		{
167 			if (fOwnData) {
168 				free(fData);
169 				fData = NULL;
170 				fSize = 0;
171 			}
172 			SetObject(NULL);
173 		}
174 
175 		int32 ID() const
176 			{ return fID; }
177 
178 		const char *Name() const
179 			{ return fName.String(); }
180 
181 		const void *Data() const
182 			{ return fData; }
183 
184 		size_t Size() const
185 			{ return fSize; }
186 
187 		void SetObject(TypeObject *object)
188 		{
189 			if (object == fObject)
190 				return;
191 			if (fObject)
192 				fObject->Delete();
193 			fObject = object;
194 		}
195 
196 		TypeObject *Object() const
197 			{ return fObject; }
198 
199 		void SetSourceIsFile(bool state)
200 			{ fSourceIsFile = state; }
201 
202 		bool SourceIsFile() const
203 			{ return fSourceIsFile; }
204 
205 	private:
206 		int32 fID;
207 		BString fName;
208 		void *fData;
209 		size_t fSize;
210 		TypeObject *fObject;
211 		bool fOwnData;
212 		bool fSourceIsFile;
213 	};
214 
215 	static bool FreeTypeItemFunc(void *item)
216 	{
217 		delete reinterpret_cast<TypeItem *>(item);
218 		return false;
219 	}
220 
221 	class TypeList {
222 	public:
223 		TypeList(type_code type)
224 			:	fType(type)
225 		{
226 		}
227 
228 		virtual ~TypeList()
229 		{
230 			fItems.DoForEach(FreeTypeItemFunc);
231 			fItems.MakeEmpty();
232 		}
233 
234 		type_code Type() const
235 			{ return fType; }
236 
237 		TypeItem *FindItemByID(int32 id)
238 		{
239 			for (int32 i = 0; i < fItems.CountItems(); i++ ) {
240 				TypeItem *it = (TypeItem *)fItems.ItemAt(i);
241 				if (it->ID() == id)
242 					return it;
243 			}
244 			return NULL;
245 		}
246 
247 		TypeItem *FindItemByName(const char *name)
248 		{
249 			for (int32 i = 0; i < fItems.CountItems(); i++ ) {
250 				TypeItem *it = (TypeItem*)fItems.ItemAt(i);
251 				if (strcmp(it->Name(), name) == 0)
252 					return it;
253 			}
254 			return NULL;
255 		}
256 
257 		void AddItem(TypeItem *item)
258 		{
259 			fItems.AddItem(item);
260 		}
261 
262 	private:
263 		type_code fType;
264 		BList fItems;
265 	};
266 
267 }
268 
269 using namespace TResourcePrivate;
270 
271 //	#pragma mark -
272 // ----------------------------- TStringBlock -----------------------------
273 
274 
275 TStringBlock::TStringBlock(BDataIO *data)
276 	:	fNumEntries(0),
277 		fIndex(0),
278 		fStrings(NULL),
279 		fOwnData(true)
280 {
281 	fStrings = (char*)malloc(1024);
282 	size_t pos = 0;
283 	ssize_t amount;
284 	while ((amount=data->Read(fStrings + pos, 1024)) == 1024) {
285 		pos += amount;
286 		fStrings = (char *)realloc(fStrings, pos + 1024);
287 	}
288 	if (amount > 0)
289 		pos += amount;
290 
291 	fNumEntries = PreIndex(fStrings, amount);
292 	fIndex = (size_t *)malloc(sizeof(size_t) * fNumEntries);
293 	MakeIndex(fStrings, amount, fNumEntries, fIndex);
294 }
295 
296 
297 TStringBlock::TStringBlock(const void *block, size_t size)
298 	:
299 	fNumEntries(0),
300 	fIndex(0),
301 	fStrings(NULL),
302 	fOwnData(false)
303 {
304 	fIndex = (size_t *)const_cast<void *>(block);
305 	fStrings = (char *)const_cast<void *>(block);
306 
307 	// Figure out how many entries there are.
308 	size_t last_off = 0;
309 	while (fIndex[fNumEntries] > last_off && fIndex[fNumEntries] < size ) {
310 		last_off = fIndex[fNumEntries];
311 		fNumEntries++;
312 	}
313 }
314 
315 
316 TStringBlock::~TStringBlock()
317 {
318 	if (fOwnData) {
319 		free(fIndex);
320 		free(fStrings);
321 	}
322 	fIndex = 0;
323 	fStrings = NULL;
324 }
325 
326 
327 const char *
328 TStringBlock::String(size_t index) const
329 {
330 	if (index >= fNumEntries)
331 		return NULL;
332 
333 	return fStrings + fIndex[index];
334 }
335 
336 
337 size_t
338 TStringBlock::PreIndex(char *strings, ssize_t len)
339 {
340 	size_t count = 0;
341 
342 	char *orig = strings;
343 	char *end = strings + len;
344 	bool in_cr = false;
345 	bool first = true;
346 	bool skipping = false;
347 	while (orig < end) {
348 		if (*orig == '\n' || *orig == '\r' || *orig == 0) {
349 			if (!in_cr && *orig == '\r')
350 				in_cr = true;
351 			if (in_cr && *orig == '\n') {
352 				orig++;
353 				continue;
354 			}
355 			first = true;
356 			skipping = false;
357 			*strings = 0;
358 			count++;
359 		} else if (first && *orig == '#') {
360 			skipping = true;
361 			first = false;
362 			orig++;
363 			continue;
364 		} else if (skipping) {
365 			orig++;
366 			continue;
367 		} else if (*orig == '\\' && (orig + 1) < end) {
368 			orig++;
369 			switch (*orig) {
370 				case '\\':
371 					*strings = '\\';
372 					break;
373 
374 				case '\n':
375 					*strings = '\n';
376 					break;
377 
378 				case '\r':
379 					*strings = '\r';
380 					break;
381 
382 				case '\t':
383 					*strings = '\t';
384 					break;
385 
386 				default:
387 					*strings = *orig;
388 					break;
389 			}
390 		} else
391 			*strings = *orig;
392 
393 		orig++;
394 		strings++;
395 	}
396 
397 	return count;
398 }
399 
400 
401 void
402 TStringBlock::MakeIndex(const char *strings, ssize_t len,
403 	size_t indexLength, size_t *resultingIndex)
404 {
405 	*resultingIndex++ = 0;
406 	indexLength--;
407 
408 	ssize_t pos = 0;
409 	while (pos < len && indexLength > 0) {
410 		if (strings[pos] == 0 ) {
411 			*resultingIndex++ = (size_t)pos + 1;
412 			indexLength--;
413 		}
414 		pos++;
415 	}
416 }
417 
418 
419 //	#pragma mark -
420 
421 
422 #if USE_RESOURCES
423 static bool
424 FreeResourcesFunc(void *item)
425 {
426 	delete reinterpret_cast<BResources *>(item);
427 	return false;
428 }
429 #endif
430 
431 
432 static bool
433 FreePathFunc(void *item)
434 {
435 	delete reinterpret_cast<BPath *>(item);
436 	return false;
437 }
438 
439 
440 static bool
441 FreeTypeFunc(void *item)
442 {
443 	delete reinterpret_cast<TypeList*>(item);
444 	return false;
445 }
446 
447 
448 //	#pragma mark -
449 // ----------------------------- TResourceSet -----------------------------
450 
451 
452 TResourceSet::TResourceSet()
453 {
454 }
455 
456 
457 TResourceSet::~TResourceSet()
458 {
459 	BAutolock lock(&fLock);
460 #if USE_RESOURCES
461 	fResources.DoForEach(FreeResourcesFunc);
462 	fResources.MakeEmpty();
463 #endif
464 	fDirectories.DoForEach(FreePathFunc);
465 	fDirectories.MakeEmpty();
466 	fTypes.DoForEach(FreeTypeFunc);
467 	fTypes.MakeEmpty();
468 }
469 
470 
471 status_t
472 TResourceSet::AddResources(BResources *RESOURCES_ONLY(resources))
473 {
474 #if USE_RESOURCES
475 	if (!resources)
476 		return B_BAD_VALUE;
477 
478 	BAutolock lock(&fLock);
479 	status_t err = fResources.AddItem(resources) ? B_OK : B_ERROR;
480 	if (err != B_OK)
481 		delete resources;
482 	return err;
483 #else
484 	return B_ERROR;
485 #endif
486 }
487 
488 
489 status_t
490 TResourceSet::AddDirectory(const char *fullPath)
491 {
492 	if (!fullPath)
493 		return B_BAD_VALUE;
494 
495 	BPath *path = new BPath(fullPath);
496 	status_t err = path->InitCheck();
497 	if (err != B_OK) {
498 		delete path;
499 		return err;
500 	}
501 
502 	BAutolock lock(&fLock);
503 	err = fDirectories.AddItem(path) ? B_OK : B_ERROR;
504 	if (err != B_OK)
505 		delete path;
506 
507 	return err;
508 }
509 
510 
511 status_t
512 TResourceSet::AddEnvDirectory(const char *in, const char *defaultValue)
513 {
514 	BString buf;
515 	status_t err = ExpandString(&buf, in);
516 
517 	if (err != B_OK) {
518 		if (defaultValue)
519 			return AddDirectory(defaultValue);
520 		return err;
521 	}
522 
523 	return AddDirectory(buf.String());
524 }
525 
526 
527 status_t
528 TResourceSet::ExpandString(BString *out, const char *in)
529 {
530 	const char *start = in;
531 	while (*in) {
532 		if (*in == '$') {
533 			if (start < in)
534 				 out->Append(start, (int32)(in - start));
535 
536 			in++;
537 			char variableName[1024];
538 			size_t i = 0;
539 			if (*in == '{') {
540 				in++;
541 				while (*in && *in != '}' && i < sizeof(variableName) - 1)
542 					variableName[i++] = *in++;
543 
544 				if (*in)
545 					in++;
546 
547 			} else
548 				while (isalnum(*in) || *in == '_' && i < sizeof(variableName) - 1)
549 					variableName[i++] = *in++;
550 
551 			start = in;
552 
553 			variableName[i] = '\0';
554 
555 			const char *val = getenv(variableName);
556 			if (!val) {
557 				PRINT(("Error: env var %s not found.\n", &variableName[0]));
558 				return B_NAME_NOT_FOUND;
559 			}
560 
561 			status_t err = ExpandString(out, val);
562 			if (err != B_OK)
563 				return err;
564 
565 		} else if (*in == '\\') {
566 			if (start < in)
567 				out->Append(start, (int32)(in - start));
568 			in++;
569 			start = in;
570 			in++;
571 		} else
572 			in++;
573 	}
574 
575 	if (start < in)
576 		out->Append(start, (int32)(in - start));
577 
578 	return B_OK;
579 }
580 
581 
582 const void *
583 TResourceSet::FindResource(type_code type, int32 id, size_t *outSize)
584 {
585 	TypeItem *item = FindItemID(type, id);
586 
587 	if (outSize)
588 		*outSize = item ? item->Size() : 0;
589 
590 	return item ? item->Data() : NULL;
591 }
592 
593 
594 const void *
595 TResourceSet::FindResource(type_code type, const char *name, size_t *outSize)
596 {
597 	TypeItem *item = FindItemName(type, name);
598 
599 	if (outSize)
600 		*outSize = item ? item->Size() : 0;
601 
602 	return item ? item->Data() : NULL;
603 }
604 
605 
606 const BBitmap *
607 TResourceSet::FindBitmap(type_code type, int32 id)
608 {
609 	return ReturnBitmapItem(type, FindItemID(type, id));
610 }
611 
612 
613 const BBitmap *
614 TResourceSet::FindBitmap(type_code type, const char *name)
615 {
616 	return ReturnBitmapItem(type, FindItemName(type, name));
617 }
618 
619 
620 const TStringBlock *
621 TResourceSet::FindStringBlock(type_code type, int32 id)
622 {
623 	return ReturnStringBlockItem(FindItemID(type, id));
624 }
625 
626 
627 const TStringBlock *
628 TResourceSet::FindStringBlock(type_code type, const char *name)
629 {
630 	return ReturnStringBlockItem(FindItemName(type, name));
631 }
632 
633 
634 const char *
635 TResourceSet::FindString(type_code type, int32 id, uint32 index)
636 {
637 	const TStringBlock *stringBlock = FindStringBlock(type, id);
638 	if (!stringBlock)
639 		return NULL;
640 
641 	return stringBlock->String(index);
642 }
643 
644 
645 const char *
646 TResourceSet::FindString(type_code type, const char *name, uint32 index)
647 {
648 	const TStringBlock *stringBlock = FindStringBlock(type, name);
649 	if (!stringBlock)
650 		return NULL;
651 
652 	return stringBlock->String(index);
653 }
654 
655 
656 TypeList *
657 TResourceSet::FindTypeList(type_code type)
658 {
659 	BAutolock lock(&fLock);
660 
661 	int32 count = fTypes.CountItems();
662 	for (int32 i = 0; i < count; i++ ) {
663 		TypeList *list = (TypeList *)fTypes.ItemAt(i);
664 		if (list && list->Type() == type)
665 			return list;
666 	}
667 
668 	return NULL;
669 }
670 
671 
672 TypeItem *
673 TResourceSet::FindItemID(type_code type, int32 id)
674 {
675 	TypeList *list = FindTypeList(type);
676 	TypeItem *item = NULL;
677 
678 	if (list) item = list->FindItemByID(id);
679 
680 	if (!item)
681 		item = LoadResource(type, id, 0, &list);
682 
683 	return item;
684 }
685 
686 
687 TypeItem *
688 TResourceSet::FindItemName(type_code type, const char *name)
689 {
690 	TypeList *list = FindTypeList(type);
691 	TypeItem *item = NULL;
692 
693 	if (list)
694 		item = list->FindItemByName(name);
695 
696 	if (!item)
697 		item = LoadResource(type, -1, name, &list);
698 
699 	return item;
700 }
701 
702 
703 TypeItem *
704 TResourceSet::LoadResource(type_code type, int32 id, const char *name,
705 	TypeList **inOutList)
706 {
707 	TypeItem *item = NULL;
708 
709 	if (name) {
710 		BEntry entry;
711 
712 		// If a named resource, first look in directories.
713 		fLock.Lock();
714 		int32 count = fDirectories.CountItems();
715 		for (int32 i = 0; item == 0 && i < count; i++) {
716 			BPath *dir = (BPath *)fDirectories.ItemAt(i);
717 			if (dir) {
718 				fLock.Unlock();
719 				BPath path(dir->Path(), name);
720 				if (entry.SetTo(path.Path(), true) == B_OK ) {
721 					BFile file(&entry, B_READ_ONLY);
722 					if (file.InitCheck() == B_OK ) {
723 						item = new TypeItem(id, name, &file);
724 						item->SetSourceIsFile(true);
725 					}
726 				}
727 				fLock.Lock();
728 			}
729 		}
730 		fLock.Unlock();
731 	}
732 
733 #if USE_RESOURCES
734 	if (!item) {
735 		// Look through resource objects for data.
736 		fLock.Lock();
737 		int32 count = fResources.CountItems();
738 		for (int32 i = 0; item == 0 && i < count; i++ ) {
739 			BResources *resource = (BResources *)fResources.ItemAt(i);
740 			if (resource) {
741 				const void *data = NULL;
742 				size_t size = 0;
743 				if (id >= 0)
744 					data = resource->LoadResource(type, id, &size);
745 				else if (name != NULL)
746 					data = resource->LoadResource(type, name, &size);
747 
748 				if (data && size) {
749 					item = new TypeItem(id, name, data, size);
750 					item->SetSourceIsFile(false);
751 				}
752 			}
753 		}
754 		fLock.Unlock();
755 	}
756 #endif
757 
758 	if (item) {
759 		TypeList *list = inOutList ? *inOutList : NULL;
760 		if (!list) {
761 			// Don't currently have a list for this type -- check if there is
762 			// already one.
763 			list = FindTypeList(type);
764 		}
765 
766 		BAutolock lock(&fLock);
767 
768 		if (!list) {
769 			// Need to make a new list for this type.
770 			list = new TypeList(type);
771 			fTypes.AddItem(list);
772 		}
773 		if (inOutList)
774 			*inOutList = list;
775 
776 		list->AddItem(item);
777 	}
778 
779 	return item;
780 }
781 
782 
783 BBitmap *
784 TResourceSet::ReturnBitmapItem(type_code, TypeItem *from)
785 {
786 	if (!from)
787 		return NULL;
788 
789 	TypeObject *obj = from->Object();
790 	BitmapTypeItem *bitmap = dynamic_cast<BitmapTypeItem *>(obj);
791 	if (bitmap)
792 		return bitmap;
793 
794 	// Can't change an existing object.
795 	if (obj)
796 		return NULL;
797 
798 	// Don't have a bitmap in the item -- we'll try to create one.
799 	BMemoryIO stream(from->Data(), from->Size());
800 
801 	// Try to read as an archived bitmap.
802 	stream.Seek(0, SEEK_SET);
803 	BMessage archive;
804 	if (archive.Unflatten(&stream) == B_OK ) {
805 		bitmap = new BitmapTypeItem(&archive);
806 		if (bitmap && bitmap->InitCheck() != B_OK) {
807 			bitmap->Delete();	// allows us to delete this bitmap...
808 			delete bitmap;
809 			bitmap = NULL;
810 		}
811 	}
812 
813 	if (bitmap) {
814 		BAutolock lock(&fLock);
815 		if (from->Object() != NULL) {
816 			// Whoops!  Someone snuck in under us.
817 			bitmap->Delete();
818 			delete bitmap;
819 			bitmap = dynamic_cast<BitmapTypeItem *>(from->Object());
820 		} else
821 			from->SetObject(bitmap);
822 	}
823 
824 	return bitmap;
825 }
826 
827 
828 TStringBlock *
829 TResourceSet::ReturnStringBlockItem(TypeItem *from)
830 {
831 	if (!from)
832 		return NULL;
833 
834 	TypeObject *obj = from->Object();
835 	StringBlockTypeItem *stringBlock = dynamic_cast<StringBlockTypeItem *>(obj);
836 	if (stringBlock)
837 		return stringBlock;
838 
839 	// Can't change an existing object.
840 	if (obj)
841 		return NULL;
842 
843 	// Don't have a string block in the item -- we'll create one.
844 	if (from->SourceIsFile() ) {
845 		BMemoryIO stream(from->Data(), from->Size());
846 		stringBlock = new StringBlockTypeItem(&stream);
847 	} else
848 		stringBlock = new StringBlockTypeItem(from->Data(), from->Size());
849 
850 	if (stringBlock) {
851 		BAutolock lock(&fLock);
852 		if (from->Object() != NULL) {
853 			// Whoops!  Someone snuck in under us.
854 			delete stringBlock;
855 			stringBlock = dynamic_cast<StringBlockTypeItem*>(from->Object());
856 		} else
857 			from->SetObject(stringBlock);
858 	}
859 
860 	return stringBlock;
861 }
862 
863 
864 //	#pragma mark -
865 
866 
867 namespace TResourcePrivate {
868 
869 	TResourceSet *gResources = NULL;
870 	BLocker gResourceLocker;
871 
872 }
873 
874 
875 TResourceSet *
876 AppResSet()
877 {
878 	// If already have it, return immediately.
879 	if (gResources)
880 		return gResources;
881 
882 	// Don't have 'em, lock access to make 'em.
883 	if (!gResourceLocker.Lock())
884 		return NULL;
885 	if (gResources) {
886 		// Whoops, somebody else already did.  Oh well.
887 		gResourceLocker.Unlock();
888 		return gResources;
889 	}
890 
891 	// Make 'em.
892 	gResources = new TResourceSet;
893 	gResources->AddResources(BApplication::AppResources());
894 	gResourceLocker.Unlock();
895 	return gResources;
896 }
897