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