1/*
2 * Copyright (c) 2005-2010, Haiku, Inc.
3 * Distributed under the terms of the MIT license.
4 *
5 * Author:
6 *		DarkWyrm <darkwyrm@gmail.com>
7 */
8#include "ResView.h"
9
10#include <Application.h>
11#include <File.h>
12#include <Menu.h>
13#include <MenuItem.h>
14#include <Path.h>
15#include <ScrollView.h>
16#include <TranslatorRoster.h>
17#include <TypeConstants.h>
18
19#include <stdio.h>
20#include <stdlib.h>
21
22#include "App.h"
23#include "ColumnTypes.h"
24#include "ResourceData.h"
25#include "ResFields.h"
26#include "ResListView.h"
27#include "ResWindow.h"
28#include "PreviewColumn.h"
29#include "Editor.h"
30
31static int32 sUntitled = 1;
32
33ResourceRoster gResRoster;
34
35enum {
36	M_NEW_FILE = 'nwfl',
37	M_OPEN_FILE,
38	M_SAVE_FILE,
39	M_QUIT,
40	M_SELECT_FILE,
41	M_DELETE_RESOURCE,
42	M_EDIT_RESOURCE
43};
44
45ResView::ResView(const BRect &frame, const char *name, const int32 &resize,
46				const int32 &flags, const entry_ref *ref)
47  :	BView(frame, name, resize, flags),
48  	fRef(NULL),
49	fSaveStatus(FILE_INIT),
50  	fOpenPanel(NULL),
51  	fSavePanel(NULL)
52{
53	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
54	if (ref) {
55		fRef = new entry_ref;
56		*fRef = *ref;
57		fFileName = fRef->name;
58	} else {
59		fFileName = "Untitled ";
60		fFileName << sUntitled;
61		sUntitled++;
62	}
63
64	BRect r(Bounds());
65	r.bottom = 16;
66	fBar = new BMenuBar(r, "bar");
67	AddChild(fBar);
68
69	BuildMenus(fBar);
70
71	r = Bounds();
72	r.top = fBar->Frame().bottom + 4;
73	fListView = new ResListView(r, "gridview", B_FOLLOW_ALL, B_WILL_DRAW, B_FANCY_BORDER);
74	AddChild(fListView);
75
76	rgb_color white = { 255, 255, 255, 255 };
77	fListView->SetColor(B_COLOR_BACKGROUND, white);
78	fListView->SetColor(B_COLOR_SELECTION, ui_color(B_MENU_BACKGROUND_COLOR));
79
80	float width = be_plain_font->StringWidth("00000") + 20;
81	fListView->AddColumn(new BStringColumn("ID", width, width, 100, B_TRUNCATE_END), 0);
82
83	fListView->AddColumn(new BStringColumn("Type", width, width, 100, B_TRUNCATE_END), 1);
84	fListView->AddColumn(new BStringColumn("Name", 150, 50, 300, B_TRUNCATE_END), 2);
85	fListView->AddColumn(new PreviewColumn("Data", 150, 50, 300), 3);
86
87	// Editing is disabled for now
88	fListView->SetInvocationMessage(new BMessage(M_EDIT_RESOURCE));
89
90	width = be_plain_font->StringWidth("1000 bytes") + 20;
91	fListView->AddColumn(new BSizeColumn("Size", width, 10, 100), 4);
92
93	fOpenPanel = new BFilePanel(B_OPEN_PANEL);
94	if (ref)
95		OpenFile(*ref);
96
97	fSavePanel = new BFilePanel(B_SAVE_PANEL);
98}
99
100
101ResView::~ResView(void)
102{
103	EmptyDataList();
104	delete fRef;
105	delete fOpenPanel;
106	delete fSavePanel;
107}
108
109
110void
111ResView::AttachedToWindow(void)
112{
113	for (int32 i = 0; i < fBar->CountItems(); i++)
114		fBar->SubmenuAt(i)->SetTargetForItems(this);
115	fListView->SetTarget(this);
116
117	BMessenger messenger(this);
118	fOpenPanel->SetTarget(messenger);
119	fSavePanel->SetTarget(messenger);
120
121	Window()->Lock();
122	BString title("ResEdit: ");
123	title << fFileName;
124	Window()->SetTitle(title.String());
125	Window()->Unlock();
126}
127
128
129void
130ResView::MessageReceived(BMessage *msg)
131{
132	switch (msg->what) {
133		case M_NEW_FILE: {
134			BRect r(100, 100, 400, 400);
135			if (Window())
136				r = Window()->Frame().OffsetByCopy(10, 10);
137			ResWindow *win = new ResWindow(r);
138			win->Show();
139			break;
140		}
141		case M_OPEN_FILE: {
142			be_app->PostMessage(M_SHOW_OPEN_PANEL);
143			break;
144		}
145		case B_CANCEL: {
146			if (fSaveStatus == FILE_QUIT_AFTER_SAVE)
147				SetSaveStatus(FILE_DIRTY);
148			break;
149		}
150		case B_SAVE_REQUESTED: {
151			entry_ref saveDir;
152			BString name;
153			if (msg->FindRef("directory",&saveDir) == B_OK &&
154				msg->FindString("name",&name) == B_OK) {
155				SetTo(saveDir,name);
156				SaveFile();
157			}
158			break;
159		}
160		case M_SAVE_FILE: {
161			if (!fRef)
162				fSavePanel->Show();
163			else
164				SaveFile();
165			break;
166		}
167		case M_SHOW_SAVE_PANEL: {
168			fSavePanel->Show();
169			break;
170		}
171		case M_QUIT: {
172			be_app->PostMessage(B_QUIT_REQUESTED);
173			break;
174		}
175		case B_REFS_RECEIVED: {
176			int32 i = 0;
177			entry_ref ref;
178			while (msg->FindRef("refs", i++, &ref) == B_OK)
179				AddResource(ref);
180			break;
181		}
182		case M_SELECT_FILE: {
183			fOpenPanel->Show();
184			break;
185		}
186		case M_DELETE_RESOURCE: {
187			DeleteSelectedResources();
188			break;
189		}
190		case M_EDIT_RESOURCE: {
191			BRow *row = fListView->CurrentSelection();
192			TypeCodeField *field = (TypeCodeField*)row->GetField(1);
193			gResRoster.SpawnEditor(field->GetResourceData(), this);
194			break;
195		}
196		case M_UPDATE_RESOURCE: {
197			ResourceData *item;
198			if (msg->FindPointer("item", (void **)&item) != B_OK)
199				break;
200
201			for (int32 i = 0; i < fListView->CountRows(); i++) {
202				BRow *row = fListView->RowAt(i);
203				TypeCodeField *field = (TypeCodeField*)row->GetField(1);
204				if (!field || field->GetResourceData() != item)
205					continue;
206
207				UpdateRow(row);
208				break;
209			}
210			break;
211		}
212		default:
213			BView::MessageReceived(msg);
214	}
215}
216
217
218status_t
219ResView::SetTo(const entry_ref &dir, const BString &name)
220{
221	entry_ref fileRef;
222
223	BPath path(&dir);
224	path.Append(name.String());
225	BFile file(path.Path(), B_CREATE_FILE | B_READ_WRITE);
226	if (file.InitCheck() != B_OK)
227		return B_ERROR;
228
229	if (!fRef)
230		fRef = new entry_ref();
231
232	BEntry entry(path.Path());
233	entry.GetRef(fRef);
234	fFileName = name;
235	return B_OK;
236}
237
238
239void
240ResView::OpenFile(const entry_ref &ref)
241{
242	// Add all the 133t resources and attributes of the file
243	BFile file(&ref, B_READ_ONLY);
244	BResources resources;
245	if (resources.SetTo(&file) != B_OK)
246		return;
247	file.Unset();
248
249	resources.PreloadResourceType();
250
251	int32 index = 0;
252	ResDataRow *row;
253	ResourceData *resData = new ResourceData();
254	while (resData->SetFromResource(index, resources)) {
255		row = new ResDataRow(resData);
256		fListView->AddRow(row);
257		fDataList.AddItem(resData);
258		resData = new ResourceData();
259		index++;
260	}
261	delete resData;
262
263	BNode node;
264	if (node.SetTo(&ref) == B_OK) {
265		char attrName[B_ATTR_NAME_LENGTH];
266		node.RewindAttrs();
267		resData = new ResourceData();
268		while (node.GetNextAttrName(attrName) == B_OK) {
269			if (resData->SetFromAttribute(attrName, node)) {
270				row = new ResDataRow(resData);
271				fListView->AddRow(row);
272				fDataList.AddItem(resData);
273				resData = new ResourceData();
274			}
275		}
276		delete resData;
277	}
278}
279
280
281void
282ResView::SaveFile(void)
283{
284	if (fSaveStatus == FILE_CLEAN || !fRef)
285		return;
286
287	BFile file(fRef,B_READ_WRITE);
288	BResources res(&file,true);
289	file.Unset();
290
291	for (int32 i = 0; i < fListView->CountRows(); i++) {
292		ResDataRow *row = (ResDataRow*)fListView->RowAt(i);
293		ResourceData *data = row->GetData();
294		res.AddResource(data->GetType(), data->GetID(), data->GetData(),
295						data->GetLength(), data->GetName());
296	}
297
298	res.Sync();
299
300	if (fSaveStatus == FILE_QUIT_AFTER_SAVE && Window())
301		Window()->PostMessage(B_QUIT_REQUESTED);
302	SetSaveStatus(FILE_CLEAN);
303}
304
305
306void
307ResView::SaveAndQuit(void)
308{
309	SetSaveStatus(FILE_QUIT_AFTER_SAVE);
310	if (!fRef) {
311		fSavePanel->Show();
312		return;
313	}
314
315	SaveFile();
316}
317
318
319void
320ResView::BuildMenus(BMenuBar *menuBar)
321{
322	BMenu *menu = new BMenu("File");
323	menu->AddItem(new BMenuItem("New" B_UTF8_ELLIPSIS, new BMessage(M_NEW_FILE), 'N'));
324	menu->AddSeparatorItem();
325	menu->AddItem(new BMenuItem("Open" B_UTF8_ELLIPSIS, new BMessage(M_OPEN_FILE), 'O'));
326	menu->AddSeparatorItem();
327	menu->AddItem(new BMenuItem("Save", new BMessage(M_SAVE_FILE), 'S'));
328	menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, new BMessage(M_SHOW_SAVE_PANEL), 'S',
329								B_COMMAND_KEY | B_SHIFT_KEY));
330	menuBar->AddItem(menu);
331
332	menu = new BMenu("Resource");
333	menu->AddItem(new BMenuItem("Add" B_UTF8_ELLIPSIS, new BMessage(M_SELECT_FILE), 'F'));
334	menu->AddItem(new BMenuItem("Delete", new BMessage(M_DELETE_RESOURCE), 'D'));
335	menuBar->AddItem(menu);
336}
337
338
339void
340ResView::EmptyDataList(void)
341{
342	for (int32 i = 0; i < fDataList.CountItems(); i++) {
343		ResourceData *data = (ResourceData*) fDataList.ItemAt(i);
344		delete data;
345	}
346	fDataList.MakeEmpty();
347}
348
349
350void
351ResView::UpdateRow(BRow *row)
352{
353	TypeCodeField *typeField = (TypeCodeField*) row->GetField(1);
354	ResourceData *resData = typeField->GetResourceData();
355	BStringField *strField = (BStringField *)row->GetField(0);
356
357	if (strcmp("(attr)", strField->String()) != 0)
358		strField->SetString(resData->GetIDString());
359
360	strField = (BStringField *)row->GetField(2);
361	if (strField)
362		strField->SetString(resData->GetName());
363
364	PreviewField *preField = (PreviewField*)row->GetField(3);
365	if (preField)
366		preField->SetData(resData->GetData(), resData->GetLength());
367
368	BSizeField *sizeField = (BSizeField*)row->GetField(4);
369	if (sizeField)
370		sizeField->SetSize(resData->GetLength());
371}
372
373
374void
375ResView::AddResource(const entry_ref &ref)
376{
377	BFile file(&ref, B_READ_ONLY);
378	if (file.InitCheck() != B_OK)
379		return;
380
381	BString mime;
382	file.ReadAttrString("BEOS:TYPE", &mime);
383
384	if (mime == "application/x-be-resource") {
385		BMessage msg(B_REFS_RECEIVED);
386		msg.AddRef("refs", &ref);
387		be_app->PostMessage(&msg);
388		return;
389	}
390
391	type_code fileType = 0;
392
393	BTranslatorRoster *roster = BTranslatorRoster::Default();
394	translator_info info;
395	if (roster->Identify(&file, NULL, &info, 0, mime.String()) == B_OK)
396		fileType = info.type;
397	else
398		fileType = B_RAW_TYPE;
399
400	int32 lastID = -1;
401	for (int32 i = 0; i < fDataList.CountItems(); i++) {
402		ResourceData *resData = (ResourceData*)fDataList.ItemAt(i);
403		if (resData->GetType() == fileType && resData->GetID() > lastID)
404			lastID = resData->GetID();
405	}
406
407	off_t fileSize;
408	file.GetSize(&fileSize);
409
410	if (fileSize < 1)
411		return;
412
413	char *fileData = (char *)malloc(fileSize);
414	file.Read(fileData, fileSize);
415
416	ResourceData *resData = new ResourceData(fileType, lastID + 1, ref.name,
417											fileData, fileSize);
418	fDataList.AddItem(resData);
419
420	ResDataRow *row = new ResDataRow(resData);
421	fListView->AddRow(row);
422
423	SetSaveStatus(FILE_DIRTY);
424}
425
426
427void
428ResView::DeleteSelectedResources(void)
429{
430	ResDataRow *selection = (ResDataRow*)fListView->CurrentSelection();
431	if (!selection)
432		return;
433
434	SetSaveStatus(FILE_DIRTY);
435
436	while (selection) {
437		ResourceData *data = selection->GetData();
438		fListView->RemoveRow(selection);
439		fDataList.RemoveItem(data);
440		delete data;
441		selection = (ResDataRow*)fListView->CurrentSelection();
442	}
443}
444
445
446void
447ResView::SetSaveStatus(uint8 value)
448{
449	if (value == fSaveStatus)
450		return;
451
452	fSaveStatus = value;
453
454	BString title("ResEdit: ");
455	title << fFileName;
456	if (fSaveStatus == FILE_DIRTY)
457		title << "*";
458
459	if (Window()) {
460		Window()->Lock();
461		Window()->SetTitle(title.String());
462		Window()->Unlock();
463	}
464}
465
466
467ResDataRow::ResDataRow(ResourceData *data)
468  :	fResData(data)
469{
470	if (data) {
471		SetField(new BStringField(fResData->GetIDString()), 0);
472		SetField(new TypeCodeField(fResData->GetType(), fResData), 1);
473		SetField(new BStringField(fResData->GetName()), 2);
474		BField *field = gResRoster.MakeFieldForType(fResData->GetType(),
475													fResData->GetData(),
476													fResData->GetLength());
477		if (field)
478			SetField(field, 3);
479		SetField(new BSizeField(fResData->GetLength()), 4);
480	}
481}
482
483
484ResourceData *
485ResDataRow::GetData(void) const
486{
487	return fResData;
488}
489