diff options
author | Jacek Antonelli | 2008-08-15 23:44:56 -0500 |
---|---|---|
committer | Jacek Antonelli | 2008-08-15 23:44:56 -0500 |
commit | c07901e29ed545bbb02e3bddf148fe1104b94e9f (patch) | |
tree | f1ada64ce834acd7d92a425efb96c4b86bcf16b1 /linden/indra/newview/llviewermenufile.cpp | |
parent | Second Life viewer sources 1.15.0.2 (diff) | |
download | meta-impy-c07901e29ed545bbb02e3bddf148fe1104b94e9f.zip meta-impy-c07901e29ed545bbb02e3bddf148fe1104b94e9f.tar.gz meta-impy-c07901e29ed545bbb02e3bddf148fe1104b94e9f.tar.bz2 meta-impy-c07901e29ed545bbb02e3bddf148fe1104b94e9f.tar.xz |
Second Life viewer sources 1.15.1.3
Diffstat (limited to 'linden/indra/newview/llviewermenufile.cpp')
-rw-r--r-- | linden/indra/newview/llviewermenufile.cpp | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/linden/indra/newview/llviewermenufile.cpp b/linden/indra/newview/llviewermenufile.cpp new file mode 100644 index 0000000..6954b1c --- /dev/null +++ b/linden/indra/newview/llviewermenufile.cpp | |||
@@ -0,0 +1,1022 @@ | |||
1 | /** | ||
2 | * @file llviewermenufile.cpp | ||
3 | * @brief "File" menu in the main menu bar. | ||
4 | * | ||
5 | * Copyright (c) 2002-2007, Linden Research, Inc. | ||
6 | * | ||
7 | * Second Life Viewer Source Code | ||
8 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
9 | * to you under the terms of the GNU General Public License, version 2.0 | ||
10 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
11 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
12 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
13 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
14 | * | ||
15 | * There are special exceptions to the terms and conditions of the GPL as | ||
16 | * it is applied to this Source Code. View the full text of the exception | ||
17 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
18 | * online at http://secondlife.com/developers/opensource/flossexception | ||
19 | * | ||
20 | * By copying, modifying or distributing this software, you acknowledge | ||
21 | * that you have read and understood your obligations described above, | ||
22 | * and agree to abide by those obligations. | ||
23 | * | ||
24 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
25 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | * COMPLETENESS OR PERFORMANCE. | ||
27 | */ | ||
28 | |||
29 | #include "llviewerprecompiledheaders.h" | ||
30 | |||
31 | #include "llviewermenufile.h" | ||
32 | |||
33 | // project includes | ||
34 | #include "llagent.h" | ||
35 | #include "llfilepicker.h" | ||
36 | #include "llfloateranimpreview.h" | ||
37 | #include "llfloaterbuycurrency.h" | ||
38 | #include "llfloaterimagepreview.h" | ||
39 | #include "llfloaterimport.h" | ||
40 | #include "llfloaternamedesc.h" | ||
41 | #include "llfloatersnapshot.h" | ||
42 | #include "llinventorymodel.h" // gInventory | ||
43 | #include "llresourcedata.h" | ||
44 | #include "llstatusbar.h" | ||
45 | #include "llviewercontrol.h" // gSavedSettings | ||
46 | #include "llviewerimagelist.h" | ||
47 | #include "llvieweruictrlfactory.h" | ||
48 | #include "llviewermenu.h" // gMenuHolder | ||
49 | #include "llviewerregion.h" | ||
50 | #include "llviewerstats.h" | ||
51 | #include "llviewerwindow.h" | ||
52 | #include "viewer.h" // app_request_quit() | ||
53 | |||
54 | // linden libraries | ||
55 | #include "llassetuploadresponders.h" | ||
56 | #include "lleconomy.h" | ||
57 | #include "llhttpclient.h" | ||
58 | #include "llmemberlistener.h" | ||
59 | #include "llsdserialize.h" | ||
60 | #include "llstring.h" | ||
61 | #include "lltransactiontypes.h" | ||
62 | #include "lluuid.h" | ||
63 | #include "vorbisencode.h" | ||
64 | |||
65 | // system libraries | ||
66 | #include <boost/tokenizer.hpp> | ||
67 | |||
68 | typedef LLMemberListener<LLView> view_listener_t; | ||
69 | |||
70 | |||
71 | class LLFileEnableSaveAs : public view_listener_t | ||
72 | { | ||
73 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
74 | { | ||
75 | bool new_value = gFloaterView->getFrontmost() && gFloaterView->getFrontmost()->canSaveAs(); | ||
76 | gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); | ||
77 | return true; | ||
78 | } | ||
79 | }; | ||
80 | |||
81 | class LLFileEnableUpload : public view_listener_t | ||
82 | { | ||
83 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
84 | { | ||
85 | bool new_value = gStatusBar && gGlobalEconomy && (gStatusBar->getBalance() >= gGlobalEconomy->getPriceUpload()); | ||
86 | gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); | ||
87 | return true; | ||
88 | } | ||
89 | }; | ||
90 | |||
91 | /** | ||
92 | char* upload_pick(void* data) | ||
93 | |||
94 | If applicable, brings up a file chooser in which the user selects a file | ||
95 | to upload for a particular task. If the file is valid for the given action, | ||
96 | returns the string to the full path filename, else returns NULL. | ||
97 | Data is the load filter for the type of file as defined in LLFilePicker. | ||
98 | **/ | ||
99 | const char* upload_pick(void* data) | ||
100 | { | ||
101 | if( gAgent.cameraMouselook() ) | ||
102 | { | ||
103 | gAgent.changeCameraToDefault(); | ||
104 | // This doesn't seem necessary. JC | ||
105 | // display(); | ||
106 | } | ||
107 | |||
108 | LLFilePicker::ELoadFilter type; | ||
109 | if(data) | ||
110 | { | ||
111 | type = (LLFilePicker::ELoadFilter)((intptr_t)data); | ||
112 | } | ||
113 | else | ||
114 | { | ||
115 | type = LLFilePicker::FFLOAD_ALL; | ||
116 | } | ||
117 | |||
118 | LLFilePicker& picker = LLFilePicker::instance(); | ||
119 | if (!picker.getOpenFile(type)) | ||
120 | { | ||
121 | llinfos << "Couldn't import objects from file" << llendl; | ||
122 | return NULL; | ||
123 | } | ||
124 | |||
125 | const char* filename = picker.getFirstFile(); | ||
126 | const char* ext = strrchr(filename, '.'); | ||
127 | |||
128 | //strincmp doesn't like NULL pointers | ||
129 | if (ext == NULL) | ||
130 | { | ||
131 | const char* short_name = strrchr(filename, | ||
132 | *gDirUtilp->getDirDelimiter().c_str()); | ||
133 | |||
134 | // No extension | ||
135 | LLStringBase<char>::format_map_t args; | ||
136 | args["[FILE]"] = LLString(short_name + 1); | ||
137 | gViewerWindow->alertXml("NoFileExtension", args); | ||
138 | return NULL; | ||
139 | } | ||
140 | else | ||
141 | { | ||
142 | //so there is an extension | ||
143 | //loop over the valid extensions and compare to see | ||
144 | //if the extension is valid | ||
145 | |||
146 | //now grab the set of valid file extensions | ||
147 | const char* valids = build_extensions_string(type); | ||
148 | std::string valid_extensions = std::string(valids); | ||
149 | |||
150 | BOOL ext_valid = FALSE; | ||
151 | |||
152 | typedef boost::tokenizer<boost::char_separator<char> > tokenizer; | ||
153 | boost::char_separator<char> sep(" "); | ||
154 | tokenizer tokens(valid_extensions, sep); | ||
155 | tokenizer::iterator token_iter; | ||
156 | |||
157 | //now loop over all valid file extensions | ||
158 | //and compare them to the extension of the file | ||
159 | //to be uploaded | ||
160 | for( token_iter = tokens.begin(); | ||
161 | token_iter != tokens.end() && ext_valid != TRUE; | ||
162 | ++token_iter) | ||
163 | { | ||
164 | const char* cur_token = token_iter->c_str(); | ||
165 | |||
166 | if (0 == strnicmp(cur_token, ext, strlen(cur_token)) || /* Flawfinder: ignore */ | ||
167 | 0 == strnicmp(cur_token, "*.*", strlen(cur_token))) /* Flawfinder: ignore */ | ||
168 | { | ||
169 | //valid extension | ||
170 | //or the acceptable extension is any | ||
171 | ext_valid = TRUE; | ||
172 | } | ||
173 | }//end for (loop over all tokens) | ||
174 | |||
175 | if (ext_valid == FALSE) | ||
176 | { | ||
177 | //should only get here if the extension exists | ||
178 | //but is invalid | ||
179 | LLStringBase<char>::format_map_t args; | ||
180 | args["[EXTENSION]"] = ext; | ||
181 | args["[VALIDS]"] = valids; | ||
182 | gViewerWindow->alertXml("InvalidFileExtension", args); | ||
183 | return NULL; | ||
184 | } | ||
185 | }//end else (non-null extension) | ||
186 | |||
187 | //valid file extension | ||
188 | |||
189 | //now we check to see | ||
190 | //if the file is actually a valid image/sound/etc. | ||
191 | if (type == LLFilePicker::FFLOAD_WAV) | ||
192 | { | ||
193 | // pre-qualify wavs to make sure the format is acceptable | ||
194 | char error_msg[MAX_STRING]; /* Flawfinder: ignore */ | ||
195 | if (check_for_invalid_wav_formats(filename,error_msg)) | ||
196 | { | ||
197 | llinfos << error_msg << ": " << filename << llendl; | ||
198 | LLStringBase<char>::format_map_t args; | ||
199 | args["[FILE]"] = filename; | ||
200 | gViewerWindow->alertXml( error_msg, args ); | ||
201 | return NULL; | ||
202 | } | ||
203 | }//end if a wave/sound file | ||
204 | |||
205 | |||
206 | return filename; | ||
207 | } | ||
208 | |||
209 | void handle_upload_object(void* data) | ||
210 | { | ||
211 | const char* filename = upload_pick(data); | ||
212 | if (filename) | ||
213 | { | ||
214 | // start the import | ||
215 | LLFloaterImport* floaterp = new LLFloaterImport(filename); | ||
216 | gUICtrlFactory->buildFloater(floaterp, "floater_import.xml"); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | class LLFileUploadImage : public view_listener_t | ||
221 | { | ||
222 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
223 | { | ||
224 | const char* filename = upload_pick((void *)(S32)LLFilePicker::FFLOAD_IMAGE); | ||
225 | if (filename) | ||
226 | { | ||
227 | LLFloaterImagePreview* floaterp = new LLFloaterImagePreview(filename); | ||
228 | gUICtrlFactory->buildFloater(floaterp, "floater_image_preview.xml"); | ||
229 | } | ||
230 | return TRUE; | ||
231 | } | ||
232 | }; | ||
233 | |||
234 | class LLFileUploadSound : public view_listener_t | ||
235 | { | ||
236 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
237 | { | ||
238 | const char* filename = upload_pick((void*)((S32)LLFilePicker::FFLOAD_WAV)); | ||
239 | if (filename) | ||
240 | { | ||
241 | LLFloaterNameDesc* floaterp = new LLFloaterNameDesc(filename); | ||
242 | gUICtrlFactory->buildFloater(floaterp, "floater_sound_preview.xml"); | ||
243 | } | ||
244 | return true; | ||
245 | } | ||
246 | }; | ||
247 | |||
248 | class LLFileUploadAnim : public view_listener_t | ||
249 | { | ||
250 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
251 | { | ||
252 | const char* filename = upload_pick((void*)((S32)LLFilePicker::FFLOAD_ANIM)); | ||
253 | if (filename) | ||
254 | { | ||
255 | LLFloaterAnimPreview* floaterp = new LLFloaterAnimPreview(filename); | ||
256 | gUICtrlFactory->buildFloater(floaterp, "floater_animation_preview.xml"); | ||
257 | } | ||
258 | return true; | ||
259 | } | ||
260 | }; | ||
261 | |||
262 | class LLFileUploadBulk : public view_listener_t | ||
263 | { | ||
264 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
265 | { | ||
266 | if( gAgent.cameraMouselook() ) | ||
267 | { | ||
268 | gAgent.changeCameraToDefault(); | ||
269 | } | ||
270 | |||
271 | // TODO: | ||
272 | // Iterate over all files | ||
273 | // Check extensions for uploadability, cost | ||
274 | // Check user balance for entire cost | ||
275 | // Charge user entire cost | ||
276 | // Loop, uploading | ||
277 | // If an upload fails, refund the user for that one | ||
278 | // | ||
279 | // Also fix single upload to charge first, then refund | ||
280 | |||
281 | LLFilePicker& picker = LLFilePicker::instance(); | ||
282 | if (picker.getMultipleOpenFiles()) | ||
283 | { | ||
284 | const char* filename = picker.getFirstFile(); | ||
285 | const char* name = picker.getDirname(); | ||
286 | |||
287 | LLString asset_name = name; | ||
288 | LLString::replaceNonstandardASCII( asset_name, '?' ); | ||
289 | LLString::replaceChar(asset_name, '|', '?'); | ||
290 | LLString::stripNonprintable(asset_name); | ||
291 | LLString::trim(asset_name); | ||
292 | |||
293 | char* asset_name_str = (char*)asset_name.c_str(); | ||
294 | char* end_p = strrchr(asset_name_str, '.'); // strip extension if exists | ||
295 | if( !end_p ) | ||
296 | { | ||
297 | end_p = asset_name_str + strlen( asset_name_str ); /* Flawfinder: ignore */ | ||
298 | } | ||
299 | |||
300 | S32 len = llmin( (S32) (DB_INV_ITEM_NAME_STR_LEN), (S32) (end_p - asset_name_str) ); | ||
301 | |||
302 | asset_name = asset_name.substr( 0, len ); | ||
303 | |||
304 | upload_new_resource(filename, asset_name, asset_name, 0, LLAssetType::AT_NONE, LLInventoryType::IT_NONE); // file | ||
305 | } | ||
306 | else | ||
307 | { | ||
308 | llinfos << "Couldn't import objects from file" << llendl; | ||
309 | } | ||
310 | return true; | ||
311 | } | ||
312 | }; | ||
313 | |||
314 | void upload_error(const char* error_message, const char* label, const std::string filename, const LLStringBase<char>::format_map_t args) | ||
315 | { | ||
316 | llwarns << error_message << llendl; | ||
317 | gViewerWindow->alertXml(label, args); | ||
318 | if(remove(filename.c_str()) == -1) | ||
319 | { | ||
320 | lldebugs << "unable to remove temp file" << llendl; | ||
321 | } | ||
322 | LLFilePicker::instance().reset(); | ||
323 | } | ||
324 | |||
325 | class LLFileEnableCloseWindow : public view_listener_t | ||
326 | { | ||
327 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
328 | { | ||
329 | bool new_value = gFloaterView->getFocusedFloater() != NULL || gSnapshotFloaterView->getFocusedFloater() != NULL; | ||
330 | // horrendously opaque, this code | ||
331 | gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); | ||
332 | return true; | ||
333 | } | ||
334 | }; | ||
335 | |||
336 | class LLFileCloseWindow : public view_listener_t | ||
337 | { | ||
338 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
339 | { | ||
340 | LLFloater::closeFocusedFloater(); | ||
341 | |||
342 | return true; | ||
343 | } | ||
344 | }; | ||
345 | |||
346 | class LLFileCloseAllWindows : public view_listener_t | ||
347 | { | ||
348 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
349 | { | ||
350 | bool app_quitting = false; | ||
351 | gFloaterView->closeAllChildren(app_quitting); | ||
352 | |||
353 | return true; | ||
354 | } | ||
355 | }; | ||
356 | |||
357 | class LLFileSaveTexture : public view_listener_t | ||
358 | { | ||
359 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
360 | { | ||
361 | LLFloater* top = gFloaterView->getFrontmost(); | ||
362 | if (top) | ||
363 | { | ||
364 | top->saveAs(); | ||
365 | } | ||
366 | return true; | ||
367 | } | ||
368 | }; | ||
369 | |||
370 | class LLFileTakeSnapshot : public view_listener_t | ||
371 | { | ||
372 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
373 | { | ||
374 | LLFloaterSnapshot::show(NULL); | ||
375 | return true; | ||
376 | } | ||
377 | }; | ||
378 | |||
379 | class LLFileTakeSnapshotToDisk : public view_listener_t | ||
380 | { | ||
381 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
382 | { | ||
383 | LLPointer<LLImageRaw> raw = new LLImageRaw; | ||
384 | |||
385 | S32 width = gViewerWindow->getWindowDisplayWidth(); | ||
386 | S32 height = gViewerWindow->getWindowDisplayHeight(); | ||
387 | |||
388 | if (gSavedSettings.getBOOL("HighResSnapshot")) | ||
389 | { | ||
390 | width *= 2; | ||
391 | height *= 2; | ||
392 | } | ||
393 | |||
394 | if (gViewerWindow->rawSnapshot(raw, | ||
395 | width, | ||
396 | height, | ||
397 | TRUE, | ||
398 | gSavedSettings.getBOOL("RenderUIInSnapshot"), | ||
399 | FALSE)) | ||
400 | { | ||
401 | if (!gQuietSnapshot) | ||
402 | { | ||
403 | gViewerWindow->playSnapshotAnimAndSound(); | ||
404 | } | ||
405 | LLImageBase::setSizeOverride(TRUE); | ||
406 | gViewerWindow->saveImageNumbered(raw); | ||
407 | LLImageBase::setSizeOverride(FALSE); | ||
408 | } | ||
409 | return true; | ||
410 | } | ||
411 | }; | ||
412 | |||
413 | class LLFileSaveMovie : public view_listener_t | ||
414 | { | ||
415 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
416 | { | ||
417 | LLViewerWindow::saveMovieNumbered(NULL); | ||
418 | return true; | ||
419 | } | ||
420 | }; | ||
421 | |||
422 | class LLFileSetWindowSize : public view_listener_t | ||
423 | { | ||
424 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
425 | { | ||
426 | LLString size = userdata.asString(); | ||
427 | S32 width, height; | ||
428 | sscanf(size.c_str(), "%d,%d", &width, &height); | ||
429 | LLViewerWindow::movieSize(width, height); | ||
430 | return true; | ||
431 | } | ||
432 | }; | ||
433 | |||
434 | class LLFileQuit : public view_listener_t | ||
435 | { | ||
436 | bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) | ||
437 | { | ||
438 | app_request_quit(); | ||
439 | return true; | ||
440 | } | ||
441 | }; | ||
442 | |||
443 | void handle_upload(void* data) | ||
444 | { | ||
445 | const char* filename = upload_pick(data); | ||
446 | if (filename) | ||
447 | { | ||
448 | LLFloaterNameDesc* floaterp = new LLFloaterNameDesc(filename); | ||
449 | gUICtrlFactory->buildFloater(floaterp, "floater_name_description.xml"); | ||
450 | } | ||
451 | } | ||
452 | |||
453 | void handle_compress_image(void*) | ||
454 | { | ||
455 | LLFilePicker& picker = LLFilePicker::instance(); | ||
456 | if (picker.getOpenFile(LLFilePicker::FFLOAD_IMAGE)) | ||
457 | { | ||
458 | std::string infile(picker.getFirstFile()); | ||
459 | std::string outfile = infile + ".j2c"; | ||
460 | |||
461 | llinfos << "Input: " << infile << llendl; | ||
462 | llinfos << "Output: " << outfile << llendl; | ||
463 | |||
464 | BOOL success; | ||
465 | |||
466 | success = LLViewerImageList::createUploadFile(infile, outfile, IMG_CODEC_TGA); | ||
467 | |||
468 | if (success) | ||
469 | { | ||
470 | llinfos << "Compression complete" << llendl; | ||
471 | } | ||
472 | else | ||
473 | { | ||
474 | llinfos << "Compression failed: " << LLImageBase::getLastError() << llendl; | ||
475 | } | ||
476 | } | ||
477 | } | ||
478 | |||
479 | void upload_new_resource(const LLString& src_filename, std::string name, | ||
480 | std::string desc, S32 compression_info, | ||
481 | LLAssetType::EType destination_folder_type, | ||
482 | LLInventoryType::EType inv_type, | ||
483 | U32 next_owner_perm, | ||
484 | const LLString& display_name, | ||
485 | LLAssetStorage::LLStoreAssetCallback callback, | ||
486 | void *userdata) | ||
487 | { | ||
488 | // Generate the temporary UUID. | ||
489 | LLString filename = gDirUtilp->getTempFilename(); | ||
490 | LLTransactionID tid; | ||
491 | LLAssetID uuid; | ||
492 | |||
493 | LLStringBase<char>::format_map_t args; | ||
494 | |||
495 | LLString ext = src_filename.substr(src_filename.find_last_of('.')); | ||
496 | LLAssetType::EType asset_type = LLAssetType::AT_NONE; | ||
497 | char error_message[MAX_STRING]; /* Flawfinder: ignore */ | ||
498 | error_message[0] = '\0'; | ||
499 | LLString temp_str; | ||
500 | |||
501 | BOOL error = FALSE; | ||
502 | |||
503 | if (ext.empty()) | ||
504 | { | ||
505 | LLString::size_type offset = filename.find_last_of(gDirUtilp->getDirDelimiter()); | ||
506 | if (offset != LLString::npos) | ||
507 | offset++; | ||
508 | LLString short_name = filename.substr(offset); | ||
509 | |||
510 | // No extension | ||
511 | snprintf(error_message, /* Flawfinder: ignore */ | ||
512 | MAX_STRING, | ||
513 | "No file extension for the file: '%s'\nPlease make sure the file has a correct file extension", | ||
514 | short_name.c_str()); | ||
515 | args["[FILE]"] = short_name; | ||
516 | upload_error(error_message, "NofileExtension", filename, args); | ||
517 | return; | ||
518 | } | ||
519 | else if( LLString::compareInsensitive(ext.c_str(),".bmp") == 0 ) | ||
520 | { | ||
521 | asset_type = LLAssetType::AT_TEXTURE; | ||
522 | if (!LLViewerImageList::createUploadFile(src_filename, | ||
523 | filename, | ||
524 | IMG_CODEC_BMP )) | ||
525 | { | ||
526 | snprintf(error_message, MAX_STRING, "Problem with file %s:\n\n%s\n", /* Flawfinder: ignore */ | ||
527 | src_filename.c_str(), LLImageBase::getLastError().c_str()); | ||
528 | args["[FILE]"] = src_filename; | ||
529 | args["[ERROR]"] = LLImageBase::getLastError(); | ||
530 | upload_error(error_message, "ProblemWithFile", filename, args); | ||
531 | return; | ||
532 | } | ||
533 | } | ||
534 | else if( LLString::compareInsensitive(ext.c_str(),".tga") == 0 ) | ||
535 | { | ||
536 | asset_type = LLAssetType::AT_TEXTURE; | ||
537 | if (!LLViewerImageList::createUploadFile(src_filename, | ||
538 | filename, | ||
539 | IMG_CODEC_TGA )) | ||
540 | { | ||
541 | snprintf(error_message, MAX_STRING, "Problem with file %s:\n\n%s\n", /* Flawfinder: ignore */ | ||
542 | src_filename.c_str(), LLImageBase::getLastError().c_str()); | ||
543 | args["[FILE]"] = src_filename; | ||
544 | args["[ERROR]"] = LLImageBase::getLastError(); | ||
545 | upload_error(error_message, "ProblemWithFile", filename, args); | ||
546 | return; | ||
547 | } | ||
548 | } | ||
549 | else if( LLString::compareInsensitive(ext.c_str(),".jpg") == 0 || LLString::compareInsensitive(ext.c_str(),".jpeg") == 0) | ||
550 | { | ||
551 | asset_type = LLAssetType::AT_TEXTURE; | ||
552 | if (!LLViewerImageList::createUploadFile(src_filename, | ||
553 | filename, | ||
554 | IMG_CODEC_JPEG )) | ||
555 | { | ||
556 | snprintf(error_message, MAX_STRING, "Problem with file %s:\n\n%s\n", /* Flawfinder: ignore */ | ||
557 | src_filename.c_str(), LLImageBase::getLastError().c_str()); | ||
558 | args["[FILE]"] = src_filename; | ||
559 | args["[ERROR]"] = LLImageBase::getLastError(); | ||
560 | upload_error(error_message, "ProblemWithFile", filename, args); | ||
561 | return; | ||
562 | } | ||
563 | } | ||
564 | else if(LLString::compareInsensitive(ext.c_str(),".wav") == 0) | ||
565 | { | ||
566 | asset_type = LLAssetType::AT_SOUND; // tag it as audio | ||
567 | S32 encode_result = 0; | ||
568 | |||
569 | S32 bitrate = 128; | ||
570 | |||
571 | if (compression_info) | ||
572 | { | ||
573 | bitrate = compression_info; | ||
574 | } | ||
575 | llinfos << "Attempting to encode wav as an ogg file at " << bitrate << "kbps" << llendl; | ||
576 | |||
577 | encode_result = encode_vorbis_file_at(src_filename.c_str(), filename.c_str(), bitrate*1000); | ||
578 | |||
579 | if (LLVORBISENC_NOERR != encode_result) | ||
580 | { | ||
581 | switch(encode_result) | ||
582 | { | ||
583 | case LLVORBISENC_DEST_OPEN_ERR: | ||
584 | snprintf(error_message, MAX_STRING, "Couldn't open temporary compressed sound file for writing: %s\n", filename.c_str()); /* Flawfinder: ignore */ | ||
585 | args["[FILE]"] = filename; | ||
586 | upload_error(error_message, "CannotOpenTemporarySoundFile", filename, args); | ||
587 | break; | ||
588 | |||
589 | default: | ||
590 | snprintf(error_message, MAX_STRING, "Unknown vorbis encode failure on: %s\n", src_filename.c_str()); /* Flawfinder: ignore */ | ||
591 | args["[FILE]"] = src_filename; | ||
592 | upload_error(error_message, "UnknownVorbisEncodeFailure", filename, args); | ||
593 | break; | ||
594 | } | ||
595 | return; | ||
596 | } | ||
597 | } | ||
598 | else if(LLString::compareInsensitive(ext.c_str(),".tmp") == 0) | ||
599 | { | ||
600 | // This is a generic .lin resource file | ||
601 | asset_type = LLAssetType::AT_OBJECT; | ||
602 | FILE* in = LLFile::fopen(src_filename.c_str(), "rb"); /* Flawfinder: ignore */ | ||
603 | if (in) | ||
604 | { | ||
605 | // read in the file header | ||
606 | char buf[16384]; /* Flawfinder: ignore */ | ||
607 | S32 read; /* Flawfinder: ignore */ | ||
608 | S32 version; | ||
609 | if (fscanf(in, "LindenResource\nversion %d\n", &version)) | ||
610 | { | ||
611 | if (2 == version) | ||
612 | { | ||
613 | // *NOTE: This buffer size is hard coded into scanf() below. | ||
614 | char label[MAX_STRING]; /* Flawfinder: ignore */ | ||
615 | char value[MAX_STRING]; /* Flawfinder: ignore */ | ||
616 | S32 tokens_read; | ||
617 | while (fgets(buf, 1024, in)) | ||
618 | { | ||
619 | label[0] = '\0'; | ||
620 | value[0] = '\0'; | ||
621 | tokens_read = sscanf( /* Flawfinder: ignore */ | ||
622 | buf, | ||
623 | "%254s %254s\n", | ||
624 | label, value); | ||
625 | |||
626 | llinfos << "got: " << label << " = " << value | ||
627 | << llendl; | ||
628 | |||
629 | if (EOF == tokens_read) | ||
630 | { | ||
631 | fclose(in); | ||
632 | snprintf(error_message, MAX_STRING, "corrupt resource file: %s", src_filename.c_str()); /* Flawfinder: ignore */ | ||
633 | args["[FILE]"] = src_filename; | ||
634 | upload_error(error_message, "CorruptResourceFile", filename, args); | ||
635 | return; | ||
636 | } | ||
637 | |||
638 | if (2 == tokens_read) | ||
639 | { | ||
640 | if (! strcmp("type", label)) | ||
641 | { | ||
642 | asset_type = (LLAssetType::EType)(atoi(value)); | ||
643 | } | ||
644 | } | ||
645 | else | ||
646 | { | ||
647 | if (! strcmp("_DATA_", label)) | ||
648 | { | ||
649 | // below is the data section | ||
650 | break; | ||
651 | } | ||
652 | } | ||
653 | // other values are currently discarded | ||
654 | } | ||
655 | |||
656 | } | ||
657 | else | ||
658 | { | ||
659 | fclose(in); | ||
660 | snprintf(error_message, MAX_STRING, "unknown linden resource file version in file: %s", src_filename.c_str()); /* Flawfinder: ignore */ | ||
661 | args["[FILE]"] = src_filename; | ||
662 | upload_error(error_message, "UnknownResourceFileVersion", filename, args); | ||
663 | return; | ||
664 | } | ||
665 | } | ||
666 | else | ||
667 | { | ||
668 | // this is an original binary formatted .lin file | ||
669 | // start over at the beginning of the file | ||
670 | fseek(in, 0, SEEK_SET); | ||
671 | |||
672 | const S32 MAX_ASSET_DESCRIPTION_LENGTH = 256; | ||
673 | const S32 MAX_ASSET_NAME_LENGTH = 64; | ||
674 | S32 header_size = 34 + MAX_ASSET_DESCRIPTION_LENGTH + MAX_ASSET_NAME_LENGTH; | ||
675 | S16 type_num; | ||
676 | |||
677 | // read in and throw out most of the header except for the type | ||
678 | fread(buf, header_size, 1, in); | ||
679 | memcpy(&type_num, buf + 16, sizeof(S16)); /* Flawfinder: ignore */ | ||
680 | asset_type = (LLAssetType::EType)type_num; | ||
681 | } | ||
682 | |||
683 | // copy the file's data segment into another file for uploading | ||
684 | FILE* out = LLFile::fopen(filename.c_str(), "wb"); /* Flawfinder: ignore */ | ||
685 | if (out) | ||
686 | { | ||
687 | while((read = fread(buf, 1, 16384, in))) /* Flawfinder: ignore */ | ||
688 | { | ||
689 | fwrite(buf, 1, read, out); /* Flawfinder: ignore */ | ||
690 | } | ||
691 | fclose(out); | ||
692 | } | ||
693 | else | ||
694 | { | ||
695 | fclose(in); | ||
696 | snprintf(error_message, MAX_STRING, "Unable to create output file: %s", filename.c_str()); /* Flawfinder: ignore */ | ||
697 | args["[FILE]"] = filename; | ||
698 | upload_error(error_message, "UnableToCreateOutputFile", filename, args); | ||
699 | return; | ||
700 | } | ||
701 | |||
702 | fclose(in); | ||
703 | } | ||
704 | else | ||
705 | { | ||
706 | llinfos << "Couldn't open .lin file " << src_filename << llendl; | ||
707 | } | ||
708 | } | ||
709 | else if (LLString::compareInsensitive(ext.c_str(),".bvh") == 0) | ||
710 | { | ||
711 | snprintf(error_message, MAX_STRING, "We do not currently support bulk upload of animation files\n"); /* Flawfinder: ignore */ | ||
712 | upload_error(error_message, "DoNotSupportBulkAnimationUpload", filename, args); | ||
713 | return; | ||
714 | } | ||
715 | else | ||
716 | { | ||
717 | // Unknown extension | ||
718 | snprintf(error_message, MAX_STRING, "Unknown file extension %s\nExpected .wav, .tga, .bmp, .jpg, .jpeg, or .bvh", ext.c_str()); /* Flawfinder: ignore */ | ||
719 | error = TRUE;; | ||
720 | } | ||
721 | |||
722 | // gen a new transaction ID for this asset | ||
723 | tid.generate(); | ||
724 | |||
725 | if (!error) | ||
726 | { | ||
727 | uuid = tid.makeAssetID(gAgent.getSecureSessionID()); | ||
728 | // copy this file into the vfs for upload | ||
729 | S32 file_size; | ||
730 | apr_file_t* fp = ll_apr_file_open(filename, LL_APR_RB, &file_size); | ||
731 | if (fp) | ||
732 | { | ||
733 | LLVFile file(gVFS, uuid, asset_type, LLVFile::WRITE); | ||
734 | |||
735 | file.setMaxSize(file_size); | ||
736 | |||
737 | const S32 buf_size = 65536; | ||
738 | U8 copy_buf[buf_size]; | ||
739 | while ((file_size = ll_apr_file_read(fp, copy_buf, buf_size))) | ||
740 | { | ||
741 | file.write(copy_buf, file_size); | ||
742 | } | ||
743 | apr_file_close(fp); | ||
744 | } | ||
745 | else | ||
746 | { | ||
747 | snprintf(error_message, MAX_STRING, "Unable to access output file: %s", filename.c_str()); /* Flawfinder: ignore */ | ||
748 | error = TRUE; | ||
749 | } | ||
750 | } | ||
751 | |||
752 | if (!error) | ||
753 | { | ||
754 | LLString t_disp_name = display_name; | ||
755 | if (t_disp_name.empty()) | ||
756 | { | ||
757 | t_disp_name = src_filename; | ||
758 | } | ||
759 | upload_new_resource(tid, asset_type, name, desc, compression_info, // tid | ||
760 | destination_folder_type, inv_type, next_owner_perm, | ||
761 | display_name, callback, userdata); | ||
762 | } | ||
763 | else | ||
764 | { | ||
765 | llwarns << error_message << llendl; | ||
766 | LLStringBase<char>::format_map_t args; | ||
767 | args["[ERROR_MESSAGE]"] = error_message; | ||
768 | gViewerWindow->alertXml("ErrorMessage", args); | ||
769 | if(LLFile::remove(filename.c_str()) == -1) | ||
770 | { | ||
771 | lldebugs << "unable to remove temp file" << llendl; | ||
772 | } | ||
773 | LLFilePicker::instance().reset(); | ||
774 | } | ||
775 | } | ||
776 | |||
777 | void upload_done_callback(const LLUUID& uuid, void* user_data, S32 result) // StoreAssetData callback (fixed) | ||
778 | { | ||
779 | LLResourceData* data = (LLResourceData*)user_data; | ||
780 | //LLAssetType::EType pref_loc = data->mPreferredLocation; | ||
781 | BOOL is_balance_sufficient = TRUE; | ||
782 | if(result >= 0) | ||
783 | { | ||
784 | LLAssetType::EType dest_loc = (data->mPreferredLocation == LLAssetType::AT_NONE) ? data->mAssetInfo.mType : data->mPreferredLocation; | ||
785 | |||
786 | if (LLAssetType::AT_SOUND == data->mAssetInfo.mType || | ||
787 | LLAssetType::AT_TEXTURE == data->mAssetInfo.mType || | ||
788 | LLAssetType::AT_ANIMATION == data->mAssetInfo.mType) | ||
789 | { | ||
790 | // Charge the user for the upload. | ||
791 | LLViewerRegion* region = gAgent.getRegion(); | ||
792 | S32 upload_cost = gGlobalEconomy->getPriceUpload(); | ||
793 | |||
794 | if(!(can_afford_transaction(upload_cost))) | ||
795 | { | ||
796 | LLFloaterBuyCurrency::buyCurrency( | ||
797 | llformat("Uploading %s costs", | ||
798 | data->mAssetInfo.getName().c_str()), | ||
799 | upload_cost); | ||
800 | is_balance_sufficient = FALSE; | ||
801 | } | ||
802 | else if(region) | ||
803 | { | ||
804 | // Charge user for upload | ||
805 | gStatusBar->debitBalance(upload_cost); | ||
806 | |||
807 | LLMessageSystem* msg = gMessageSystem; | ||
808 | msg->newMessageFast(_PREHASH_MoneyTransferRequest); | ||
809 | msg->nextBlockFast(_PREHASH_AgentData); | ||
810 | msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); | ||
811 | msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); | ||
812 | msg->nextBlockFast(_PREHASH_MoneyData); | ||
813 | msg->addUUIDFast(_PREHASH_SourceID, gAgent.getID()); | ||
814 | msg->addUUIDFast(_PREHASH_DestID, LLUUID::null); | ||
815 | msg->addU8("Flags", 0); | ||
816 | msg->addS32Fast(_PREHASH_Amount, upload_cost); | ||
817 | msg->addU8Fast(_PREHASH_AggregatePermNextOwner, (U8)LLAggregatePermissions::AP_EMPTY); | ||
818 | msg->addU8Fast(_PREHASH_AggregatePermInventory, (U8)LLAggregatePermissions::AP_EMPTY); | ||
819 | msg->addS32Fast(_PREHASH_TransactionType, TRANS_UPLOAD_CHARGE); | ||
820 | msg->addStringFast(_PREHASH_Description, NULL); | ||
821 | msg->sendReliable(region->getHost()); | ||
822 | } | ||
823 | } | ||
824 | |||
825 | if(is_balance_sufficient) | ||
826 | { | ||
827 | // Actually add the upload to inventory | ||
828 | llinfos << "Adding " << uuid << " to inventory." << llendl; | ||
829 | LLUUID folder_id(gInventory.findCategoryUUIDForType(dest_loc)); | ||
830 | if(folder_id.notNull()) | ||
831 | { | ||
832 | U32 next_owner_perm = data->mNextOwnerPerm; | ||
833 | if(PERM_NONE == next_owner_perm) | ||
834 | { | ||
835 | next_owner_perm = PERM_MOVE | PERM_TRANSFER; | ||
836 | } | ||
837 | create_inventory_item(gAgent.getID(), gAgent.getSessionID(), | ||
838 | folder_id, data->mAssetInfo.mTransactionID, data->mAssetInfo.getName(), | ||
839 | data->mAssetInfo.getDescription(), data->mAssetInfo.mType, | ||
840 | data->mInventoryType, NOT_WEARABLE, next_owner_perm, | ||
841 | LLPointer<LLInventoryCallback>(NULL)); | ||
842 | } | ||
843 | else | ||
844 | { | ||
845 | llwarns << "Can't find a folder to put it in" << llendl; | ||
846 | } | ||
847 | } | ||
848 | } | ||
849 | else // if(result >= 0) | ||
850 | { | ||
851 | LLStringBase<char>::format_map_t args; | ||
852 | args["[FILE]"] = LLInventoryType::lookupHumanReadable(data->mInventoryType); | ||
853 | args["[REASON]"] = LLString(LLAssetStorage::getErrorString(result)); | ||
854 | gViewerWindow->alertXml("CannotUploadReason", args); | ||
855 | } | ||
856 | |||
857 | LLUploadDialog::modalUploadFinished(); | ||
858 | delete data; | ||
859 | |||
860 | // *NOTE: This is a pretty big hack. What this does is check the | ||
861 | // file picker if there are any more pending uploads. If so, | ||
862 | // upload that file. | ||
863 | const char* next_file = LLFilePicker::instance().getNextFile(); | ||
864 | if(is_balance_sufficient && next_file) | ||
865 | { | ||
866 | const char* name = LLFilePicker::instance().getDirname(); | ||
867 | |||
868 | LLString asset_name = name; | ||
869 | LLString::replaceNonstandardASCII( asset_name, '?' ); | ||
870 | LLString::replaceChar(asset_name, '|', '?'); | ||
871 | LLString::stripNonprintable(asset_name); | ||
872 | LLString::trim(asset_name); | ||
873 | |||
874 | char* asset_name_str = (char*)asset_name.c_str(); | ||
875 | char* end_p = strrchr(asset_name_str, '.'); // strip extension if exists | ||
876 | if( !end_p ) | ||
877 | { | ||
878 | end_p = asset_name_str + strlen( asset_name_str ); /* Flawfinder: ignore */ | ||
879 | } | ||
880 | |||
881 | S32 len = llmin( (S32) (DB_INV_ITEM_NAME_STR_LEN), (S32) (end_p - asset_name_str) ); | ||
882 | |||
883 | asset_name = asset_name.substr( 0, len ); | ||
884 | |||
885 | upload_new_resource(next_file, asset_name, asset_name, // file | ||
886 | 0, LLAssetType::AT_NONE, LLInventoryType::IT_NONE); | ||
887 | } | ||
888 | } | ||
889 | |||
890 | void upload_new_resource(const LLTransactionID &tid, LLAssetType::EType asset_type, | ||
891 | std::string name, | ||
892 | std::string desc, S32 compression_info, | ||
893 | LLAssetType::EType destination_folder_type, | ||
894 | LLInventoryType::EType inv_type, | ||
895 | U32 next_owner_perm, | ||
896 | const LLString& display_name, | ||
897 | LLAssetStorage::LLStoreAssetCallback callback, | ||
898 | void *userdata) | ||
899 | { | ||
900 | LLAssetID uuid = tid.makeAssetID(gAgent.getSecureSessionID()); | ||
901 | |||
902 | if( LLAssetType::AT_SOUND == asset_type ) | ||
903 | { | ||
904 | gViewerStats->incStat(LLViewerStats::ST_UPLOAD_SOUND_COUNT ); | ||
905 | } | ||
906 | else | ||
907 | if( LLAssetType::AT_TEXTURE == asset_type ) | ||
908 | { | ||
909 | gViewerStats->incStat(LLViewerStats::ST_UPLOAD_TEXTURE_COUNT ); | ||
910 | } | ||
911 | else | ||
912 | if( LLAssetType::AT_ANIMATION == asset_type) | ||
913 | { | ||
914 | gViewerStats->incStat(LLViewerStats::ST_UPLOAD_ANIM_COUNT ); | ||
915 | } | ||
916 | |||
917 | if(LLInventoryType::IT_NONE == inv_type) | ||
918 | { | ||
919 | inv_type = LLInventoryType::defaultForAssetType(asset_type); | ||
920 | } | ||
921 | LLString::stripNonprintable(name); | ||
922 | LLString::stripNonprintable(desc); | ||
923 | if(name.empty()) | ||
924 | { | ||
925 | name = "(No Name)"; | ||
926 | } | ||
927 | if(desc.empty()) | ||
928 | { | ||
929 | desc = "(No Description)"; | ||
930 | } | ||
931 | |||
932 | // At this point, we're ready for the upload. | ||
933 | LLString upload_message = "Uploading...\n\n"; | ||
934 | upload_message.append(display_name); | ||
935 | LLUploadDialog::modalUploadDialog(upload_message); | ||
936 | |||
937 | llinfos << "*** Uploading: " << llendl; | ||
938 | llinfos << "Type: " << LLAssetType::lookup(asset_type) << llendl; | ||
939 | llinfos << "UUID: " << uuid << llendl; | ||
940 | llinfos << "Name: " << name << llendl; | ||
941 | llinfos << "Desc: " << desc << llendl; | ||
942 | lldebugs << "Folder: " << gInventory.findCategoryUUIDForType((destination_folder_type == LLAssetType::AT_NONE) ? asset_type : destination_folder_type) << llendl; | ||
943 | lldebugs << "Asset Type: " << LLAssetType::lookup(asset_type) << llendl; | ||
944 | std::string url = gAgent.getRegion()->getCapability("NewFileAgentInventory"); | ||
945 | if (!url.empty()) | ||
946 | { | ||
947 | llinfos << "New Agent Inventory via capability" << llendl; | ||
948 | LLSD body; | ||
949 | body["folder_id"] = gInventory.findCategoryUUIDForType((destination_folder_type == LLAssetType::AT_NONE) ? asset_type : destination_folder_type); | ||
950 | body["asset_type"] = LLAssetType::lookup(asset_type); | ||
951 | body["inventory_type"] = LLInventoryType::lookup(inv_type); | ||
952 | body["name"] = name; | ||
953 | body["description"] = desc; | ||
954 | |||
955 | std::ostringstream llsdxml; | ||
956 | LLSDSerialize::toXML(body, llsdxml); | ||
957 | lldebugs << "posting body to capability: " << llsdxml.str() << llendl; | ||
958 | LLHTTPClient::post(url, body, new LLNewAgentInventoryResponder(body, uuid, asset_type)); | ||
959 | } | ||
960 | else | ||
961 | { | ||
962 | llinfos << "NewAgentInventory capability not found, new agent inventory via asset system." << llendl; | ||
963 | // check for adequate funds | ||
964 | // TODO: do this check on the sim | ||
965 | if (LLAssetType::AT_SOUND == asset_type || | ||
966 | LLAssetType::AT_TEXTURE == asset_type || | ||
967 | LLAssetType::AT_ANIMATION == asset_type) | ||
968 | { | ||
969 | S32 upload_cost = gGlobalEconomy->getPriceUpload(); | ||
970 | S32 balance = gStatusBar->getBalance(); | ||
971 | if (balance < upload_cost) | ||
972 | { | ||
973 | // insufficient funds, bail on this upload | ||
974 | LLFloaterBuyCurrency::buyCurrency("Uploading costs", upload_cost); | ||
975 | return; | ||
976 | } | ||
977 | } | ||
978 | |||
979 | LLResourceData* data = new LLResourceData; | ||
980 | data->mAssetInfo.mTransactionID = tid; | ||
981 | data->mAssetInfo.mUuid = uuid; | ||
982 | data->mAssetInfo.mType = asset_type; | ||
983 | data->mAssetInfo.mCreatorID = gAgentID; | ||
984 | data->mInventoryType = inv_type; | ||
985 | data->mNextOwnerPerm = next_owner_perm; | ||
986 | data->mUserData = userdata; | ||
987 | data->mAssetInfo.setName(name); | ||
988 | data->mAssetInfo.setDescription(desc); | ||
989 | data->mPreferredLocation = destination_folder_type; | ||
990 | |||
991 | LLAssetStorage::LLStoreAssetCallback asset_callback = &upload_done_callback; | ||
992 | if (callback) | ||
993 | { | ||
994 | asset_callback = callback; | ||
995 | } | ||
996 | gAssetStorage->storeAssetData(data->mAssetInfo.mTransactionID, data->mAssetInfo.mType, | ||
997 | asset_callback, | ||
998 | (void*)data, | ||
999 | FALSE); | ||
1000 | } | ||
1001 | } | ||
1002 | |||
1003 | |||
1004 | void init_menu_file() | ||
1005 | { | ||
1006 | (new LLFileUploadImage())->registerListener(gMenuHolder, "File.UploadImage"); | ||
1007 | (new LLFileUploadSound())->registerListener(gMenuHolder, "File.UploadSound"); | ||
1008 | (new LLFileUploadAnim())->registerListener(gMenuHolder, "File.UploadAnim"); | ||
1009 | (new LLFileUploadBulk())->registerListener(gMenuHolder, "File.UploadBulk"); | ||
1010 | (new LLFileCloseWindow())->registerListener(gMenuHolder, "File.CloseWindow"); | ||
1011 | (new LLFileCloseAllWindows())->registerListener(gMenuHolder, "File.CloseAllWindows"); | ||
1012 | (new LLFileEnableCloseWindow())->registerListener(gMenuHolder, "File.EnableCloseWindow"); | ||
1013 | (new LLFileSaveTexture())->registerListener(gMenuHolder, "File.SaveTexture"); | ||
1014 | (new LLFileTakeSnapshot())->registerListener(gMenuHolder, "File.TakeSnapshot"); | ||
1015 | (new LLFileTakeSnapshotToDisk())->registerListener(gMenuHolder, "File.TakeSnapshotToDisk"); | ||
1016 | (new LLFileSaveMovie())->registerListener(gMenuHolder, "File.SaveMovie"); | ||
1017 | (new LLFileSetWindowSize())->registerListener(gMenuHolder, "File.SetWindowSize"); | ||
1018 | (new LLFileQuit())->registerListener(gMenuHolder, "File.Quit"); | ||
1019 | |||
1020 | (new LLFileEnableUpload())->registerListener(gMenuHolder, "File.EnableUpload"); | ||
1021 | (new LLFileEnableSaveAs())->registerListener(gMenuHolder, "File.EnableSaveAs"); | ||
1022 | } | ||