xref: /haiku/src/apps/resedit/ResView.cpp (revision 125183f9e5c136781f71c879faaeab43fdc3ea7b)
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 	strField->SetString(resData->GetName());
362 
363 	PreviewField *preField = (PreviewField*)row->GetField(3);
364 	preField->SetData(resData->GetData(), resData->GetLength());
365 
366 	BSizeField *sizeField = (BSizeField*)row->GetField(4);
367 	sizeField->SetSize(resData->GetLength());
368 }
369 
370 
371 void
372 ResView::AddResource(const entry_ref &ref)
373 {
374 	BFile file(&ref, B_READ_ONLY);
375 	if (file.InitCheck() != B_OK)
376 		return;
377 
378 	BString mime;
379 	file.ReadAttrString("BEOS:TYPE", &mime);
380 
381 	if (mime == "application/x-be-resource") {
382 		BMessage msg(B_REFS_RECEIVED);
383 		msg.AddRef("refs", &ref);
384 		be_app->PostMessage(&msg);
385 		return;
386 	}
387 
388 	type_code fileType = 0;
389 
390 	BTranslatorRoster *roster = BTranslatorRoster::Default();
391 	translator_info info;
392 	if (roster->Identify(&file, NULL, &info, 0, mime.String()) == B_OK)
393 		fileType = info.type;
394 	else
395 		fileType = B_RAW_TYPE;
396 
397 	int32 lastID = -1;
398 	for (int32 i = 0; i < fDataList.CountItems(); i++) {
399 		ResourceData *resData = (ResourceData*)fDataList.ItemAt(i);
400 		if (resData->GetType() == fileType && resData->GetID() > lastID)
401 			lastID = resData->GetID();
402 	}
403 
404 	off_t fileSize;
405 	file.GetSize(&fileSize);
406 
407 	if (fileSize < 1)
408 		return;
409 
410 	char *fileData = (char *)malloc(fileSize);
411 	file.Read(fileData, fileSize);
412 
413 	ResourceData *resData = new ResourceData(fileType, lastID + 1, ref.name,
414 											fileData, fileSize);
415 	fDataList.AddItem(resData);
416 
417 	ResDataRow *row = new ResDataRow(resData);
418 	fListView->AddRow(row);
419 
420 	SetSaveStatus(FILE_DIRTY);
421 }
422 
423 
424 void
425 ResView::DeleteSelectedResources(void)
426 {
427 	ResDataRow *selection = (ResDataRow*)fListView->CurrentSelection();
428 	if (!selection)
429 		return;
430 
431 	SetSaveStatus(FILE_DIRTY);
432 
433 	while (selection) {
434 		ResourceData *data = selection->GetData();
435 		fListView->RemoveRow(selection);
436 		fDataList.RemoveItem(data);
437 		delete data;
438 		selection = (ResDataRow*)fListView->CurrentSelection();
439 	}
440 }
441 
442 
443 void
444 ResView::SetSaveStatus(uint8 value)
445 {
446 	if (value == fSaveStatus)
447 		return;
448 
449 	fSaveStatus = value;
450 
451 	BString title("ResEdit: ");
452 	title << fFileName;
453 	if (fSaveStatus == FILE_DIRTY)
454 		title << "*";
455 
456 	if (Window()) {
457 		Window()->Lock();
458 		Window()->SetTitle(title.String());
459 		Window()->Unlock();
460 	}
461 }
462 
463 
464 ResDataRow::ResDataRow(ResourceData *data)
465   :	fResData(data)
466 {
467 	if (data) {
468 		SetField(new BStringField(fResData->GetIDString()), 0);
469 		SetField(new TypeCodeField(fResData->GetType(), fResData), 1);
470 		SetField(new BStringField(fResData->GetName()), 2);
471 		BField *field = gResRoster.MakeFieldForType(fResData->GetType(),
472 													fResData->GetData(),
473 													fResData->GetLength());
474 		if (field)
475 			SetField(field, 3);
476 		SetField(new BSizeField(fResData->GetLength()), 4);
477 	}
478 }
479 
480 
481 ResourceData *
482 ResDataRow::GetData(void) const
483 {
484 	return fResData;
485 }
486