xref: /haiku/src/apps/resedit/ResView.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
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 	SetViewColor(ui_color(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 
101 ResView::~ResView(void)
102 {
103 	EmptyDataList();
104 	delete fRef;
105 	delete fOpenPanel;
106 	delete fSavePanel;
107 }
108 
109 
110 void
111 ResView::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 
129 void
130 ResView::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 
218 status_t
219 ResView::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 
239 void
240 ResView::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 
281 void
282 ResView::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 
306 void
307 ResView::SaveAndQuit(void)
308 {
309 	SetSaveStatus(FILE_QUIT_AFTER_SAVE);
310 	if (!fRef) {
311 		fSavePanel->Show();
312 		return;
313 	}
314 
315 	SaveFile();
316 }
317 
318 
319 void
320 ResView::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 
339 void
340 ResView::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 
350 void
351 ResView::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 
374 void
375 ResView::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 
427 void
428 ResView::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 
446 void
447 ResView::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 
467 ResDataRow::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 
484 ResourceData *
485 ResDataRow::GetData(void) const
486 {
487 	return fResData;
488 }
489