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