Files
SingularityViewer/indra/newview/statemachine/aifilepicker.cpp
2014-08-25 17:58:45 +02:00

573 lines
15 KiB
C++

/**
* @file aifilepicker.cpp
* @brief Implementation of AIFilePicker
*
* Copyright (c) 2010, Aleric Inglewood.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 04/12/2010
* Initial version, written by Aleric Inglewood @ SL
*/
#include "linden_common.h"
#if LL_WINDOWS
#include <winsock2.h>
#include <windows.h>
#include <shlobj.h>
#endif
#include "lltrans.h"
#include "llpluginclassmedia.h"
#include "llpluginmessageclasses.h"
#include "llsdserialize.h"
#include "aifilepicker.h"
#include "lldir.h"
#if LL_WINDOWS
#include "llviewerwindow.h"
#endif
#if LL_GTK && LL_X11
#include "llwindowsdl.h"
#endif
char const* AIFilePicker::state_str_impl(state_type run_state) const
{
switch(run_state)
{
AI_CASE_RETURN(AIFilePicker_initialize_plugin);
AI_CASE_RETURN(AIFilePicker_plugin_running);
AI_CASE_RETURN(AIFilePicker_canceled);
AI_CASE_RETURN(AIFilePicker_done);
}
llassert(false);
return "UNKNOWN STATE";
}
AIFilePicker::AIFilePicker(CWD_ONLY(bool debug)) :
#ifdef CWDEBUG
AIStateMachine(debug),
#endif
mPluginManager(NULL), mCanceled(false)
{
}
// static
AIThreadSafeSimpleDC<AIFilePicker::context_map_type> AIFilePicker::sContextMap;
// static
void AIFilePicker::store_folder(std::string const& context, std::string const& folder)
{
if (!folder.empty())
{
LL_DEBUGS("Plugin") << "AIFilePicker::mContextMap[\"" << context << "\"] = \"" << folder << '"' << LL_ENDL;
AIAccess<context_map_type>(sContextMap)->operator[](context) = folder;
}
}
// static
std::string AIFilePicker::get_folder(std::string const& default_path, std::string const& context)
{
AIAccess<context_map_type> wContextMap(sContextMap);
context_map_type::iterator iter = wContextMap->find(context);
if (iter != wContextMap->end() && !iter->second.empty())
{
return iter->second;
}
else if (!default_path.empty())
{
LL_DEBUGS("Plugin") << "context \"" << context << "\" not found. Returning default_path \"" << default_path << "\"." << LL_ENDL;
return default_path;
}
else if ((iter = wContextMap->find("savefile")) != wContextMap->end() && !iter->second.empty())
{
LL_DEBUGS("Plugin") << "context \"" << context << "\" not found and default_path empty. Returning context \"savefile\": \"" << iter->second << "\"." << LL_ENDL;
return iter->second;
}
else
{
// This is the last resort when all else failed. Open the file chooser in directory 'home'.
std::string home;
#if LL_WINDOWS
WCHAR w_home[MAX_PATH];
HRESULT hr = ::SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, w_home);
if(hr == S_OK) {
home = utf16str_to_utf8str(w_home);
}
#else
char const* envname = "HOME";
char const* t_home = getenv(envname);
if (t_home)
{
home.assign(t_home);
}
#endif
if(home.empty())
{
#if LL_WINDOWS
// On windows, the filepicker window won't pop up at all if we pass an empty string.
home = "C:\\";
#else
home = "/";
#endif
LL_DEBUGS("Plugin") << "context \"" << context << "\" not found, default_path empty, context \"savefile\" not found "
"and $HOME or CSIDL_PERSONAL is empty! Returning \"" << home << "\"." << LL_ENDL;
}
else
{
LL_DEBUGS("Plugin") << "context \"" << context << "\" not found, default_path empty and context \"savefile\" not found. "
"Returning $HOME or CSIDL_PERSONAL (" << home << ")." << LL_ENDL;
}
return home;
}
}
void AIFilePicker::open(ELoadFilter filter, std::string const& default_path, std::string const& context, bool multiple)
{
mContext = context;
mFolder = AIFilePicker::get_folder(default_path, context);
mOpenType = multiple ? load_multiple : load;
switch(filter)
{
case DF_DIRECTORY:
mFilter = "directory";
break;
case FFLOAD_ALL:
mFilter = "all";
break;
case FFLOAD_WAV:
mFilter = "wav";
break;
case FFLOAD_IMAGE:
mFilter = "image";
break;
case FFLOAD_ANIM:
mFilter = "anim";
break;
#ifdef _CORY_TESTING
case FFLOAD_GEOMETRY:
mFilter = "geometry";
break;
#endif
case FFLOAD_XML:
mFilter = "xml";
break;
case FFLOAD_SLOBJECT:
mFilter = "slobject";
break;
case FFLOAD_RAW:
mFilter = "raw";
break;
case FFLOAD_MODEL:
mFilter = "model";
break;
case FFLOAD_COLLADA:
mFilter = "collada";
break;
case FFLOAD_SCRIPT:
mFilter = "script";
break;
case FFLOAD_DICTIONARY:
mFilter = "dictionary";
break;
case FFLOAD_INVGZ:
mFilter = "invgz";
break;
case FFLOAD_AO:
mFilter = "ao";
break;
case FFLOAD_BLACKLIST:
mFilter = "blacklist";
break;
}
}
void AIFilePicker::open(std::string const& filename, ESaveFilter filter, std::string const& default_path, std::string const& context)
{
mFilename = LLDir::getScrubbedFileName(filename);
mContext = context;
mFolder = AIFilePicker::get_folder(default_path, context);
// If the basename of filename ends on "?000", then check if a file with that name exists and if so, increment the number.
std::string basename = gDirUtilp->getBaseFileName(filename, true);
size_t len = basename.size();
if (len >= 4 && basename.substr(len - 4) == "?000")
{
basename = LLDir::getScrubbedFileName(basename.substr(0, len - 4));
std::string extension = gDirUtilp->getExtension(mFilename);
for (int n = 0;; ++n)
{
mFilename = llformat("%s_%03u.%s", basename.c_str(), n, extension.c_str());
std::string fullpath = mFolder + gDirUtilp->getDirDelimiter() + mFilename;
if (n == 999 || !gDirUtilp->fileExists(fullpath))
{
break;
}
}
}
mOpenType = save;
switch(filter)
{
case FFSAVE_ALL:
mFilter = "all";
break;
case FFSAVE_WAV:
mFilter = "wav";
break;
case FFSAVE_TGA:
mFilter = "tga";
break;
case FFSAVE_BMP:
mFilter = "bmp";
break;
case FFSAVE_AVI:
mFilter = "avi";
break;
case FFSAVE_ANIM:
mFilter = "anim";
break;
#ifdef _CORY_TESTING
case FFSAVE_GEOMETRY:
mFilter = "geometry";
break;
#endif
case FFSAVE_XML:
mFilter = "xml";
break;
case FFSAVE_COLLADA:
mFilter = "collada";
break;
case FFSAVE_RAW:
mFilter = "raw";
break;
case FFSAVE_J2C:
mFilter = "j2c";
break;
case FFSAVE_PNG:
mFilter = "png";
break;
case FFSAVE_JPEG:
mFilter = "jpeg";
break;
case FFSAVE_ANIMATN:
mFilter = "animatn";
break;
case FFSAVE_OGG:
mFilter = "ogg";
break;
case FFSAVE_NOTECARD:
mFilter = "notecard";
break;
case FFSAVE_GESTURE:
mFilter = "gesture";
break;
case FFSAVE_SCRIPT:
mFilter = "script";
break;
case FFSAVE_SHAPE:
mFilter = "shape";
break;
case FFSAVE_SKIN:
mFilter = "skin";
break;
case FFSAVE_HAIR:
mFilter = "hair";
break;
case FFSAVE_EYES:
mFilter = "eyes";
break;
case FFSAVE_SHIRT:
mFilter = "shirt";
break;
case FFSAVE_PANTS:
mFilter = "pants";
break;
case FFSAVE_SHOES:
mFilter = "shoes";
break;
case FFSAVE_SOCKS:
mFilter = "socks";
break;
case FFSAVE_JACKET:
mFilter = "jacket";
break;
case FFSAVE_GLOVES:
mFilter = "gloves";
break;
case FFSAVE_UNDERSHIRT:
mFilter = "undershirt";
break;
case FFSAVE_UNDERPANTS:
mFilter = "underpants";
break;
case FFSAVE_SKIRT:
mFilter = "skirt";
break;
case FFSAVE_INVGZ:
mFilter = "invgz";
break;
case FFSAVE_LANDMARK:
mFilter = "landmark";
break;
case FFSAVE_AO:
mFilter = "ao";
break;
case FFSAVE_BLACKLIST:
mFilter = "blacklist";
break;
case FFSAVE_PHYSICS:
mFilter = "physics";
break;
case FFSAVE_IMAGE:
mFilter = "image";
break;
}
}
void AIFilePicker::initialize_impl(void)
{
mCanceled = false;
if (mFilter.empty())
{
llwarns << "Calling AIFilePicker::initialize_impl() with empty mFilter. Call open before calling run!" << llendl;
abort();
return;
}
mPluginManager = new LLViewerPluginManager;
if (!mPluginManager)
{
abort();
return;
}
LLPluginClassBasic* plugin = mPluginManager->createPlugin<AIPluginFilePicker>(this);
if (!plugin)
{
abort();
return;
}
set_state(AIFilePicker_initialize_plugin);
}
void AIFilePicker::multiplex_impl(state_type run_state)
{
mPluginManager->update(); // Give the plugin some CPU for it's messages.
LLPluginClassBasic* plugin = mPluginManager->getPlugin();
if (!plugin || plugin->isPluginExited())
{
// This happens when there was a problem with the plugin (ie, it crashed).
abort();
return;
}
switch (run_state)
{
case AIFilePicker_initialize_plugin:
{
if (!plugin->isPluginRunning())
{
yield();
break; // Still initializing.
}
// Send initialization message.
LLPluginMessage initialization_message(LLPLUGIN_MESSAGE_CLASS_BASIC, "initialization");
static char const* key_str[] = {
"all_files", "sound_files", "animation_files", "image_files", "save_file_verb",
"targa_image_files", "bitmap_image_files", "avi_movie_file", "xaf_animation_file",
"xml_file", "raw_file", "compressed_image_files", "load_file_verb", "load_files",
"choose_the_directory"
};
LLSD dictionary;
for (int key = 0; key < sizeof(key_str) / sizeof(key_str[0]); ++key)
{
dictionary[key_str[key]] = LLTrans::getString(key_str[key]);
}
initialization_message.setValueLLSD("dictionary", dictionary);
#if LL_WINDOWS || (LL_GTK && LL_X11)
std::ostringstream window_id_str;
#if LL_WINDOWS
unsigned long window_id = (unsigned long)gViewerWindow->getPlatformWindow();
#else
unsigned long window_id = LLWindowSDL::get_SDL_XWindowID();
#endif
if (window_id != 0)
{
window_id_str << std::hex << "0x" << window_id;
initialization_message.setValue("window_id", window_id_str.str());
}
else
{
LL_WARNS("Plugin") << "Couldn't get xwid to use for transient." << LL_ENDL;
}
#endif // LL_WINDOWS || (LL_GTK && LL_X11)
plugin->sendMessage(initialization_message);
// Send open message.
LLPluginMessage open_message(LLPLUGIN_MESSAGE_CLASS_BASIC, "open");
open_message.setValue("type", (mOpenType == save) ? "save" : (mOpenType == load) ? "load" : "load_multiple");
open_message.setValue("filter", mFilter);
if (mOpenType == save) open_message.setValue("default", mFilename);
open_message.setValue("folder", mFolder);
open_message.setValue("gorgon", "block"); // Don't expect HeartBeat messages after an "open".
plugin->sendMessage(open_message);
set_state(AIFilePicker_plugin_running);
break;
}
case AIFilePicker_plugin_running:
{
// Users are slow, no need to look for new messages from the plugin all too often.
yield_ms(250);
break;
}
case AIFilePicker_canceled:
{
mCanceled = true;
finish();
break;
}
case AIFilePicker_done:
{
// Store folder of first filename as context.
AIFilePicker::store_folder(mContext, getFolder());
finish();
break;
}
}
}
void AIFilePicker::finish_impl(void)
{
if (mPluginManager)
{
mPluginManager->destroyPlugin();
mPluginManager = NULL;
}
mFilter.clear(); // Check that open is called before calling run (again).
}
// This function is called when a new message is received from the plugin.
// We get here when calling mPluginManager->update() in the first line of
// AIFilePicker::multiplex_impl.
//
// Note that we can't call finish() or abort() directly in this function,
// as that deletes mPluginManager and we're using the plugin manager
// right now (to receive this message)!
void AIFilePicker::receivePluginMessage(const LLPluginMessage &message)
{
std::string message_class = message.getClass();
if (message_class == LLPLUGIN_MESSAGE_CLASS_BASIC)
{
std::string message_name = message.getName();
if (message_name == "canceled")
{
LL_DEBUGS("Plugin") << "received message \"canceled\"" << LL_ENDL;
set_state(AIFilePicker_canceled);
}
else if (message_name == "done")
{
LL_DEBUGS("Plugin") << "received message \"done\"" << LL_ENDL;
LLSD filenames = message.getValueLLSD("filenames");
mFilenames.clear();
for(LLSD::array_iterator filename = filenames.beginArray(); filename != filenames.endArray(); ++filename)
{
mFilenames.push_back(*filename);
}
set_state(AIFilePicker_done);
}
else
{
LL_WARNS("Plugin") << "Unknown " << message_class << " class message: " << message_name << LL_ENDL;
}
}
}
std::string const& AIFilePicker::getFilename(void) const
{
// Only call this function after the AIFilePicker finished successfully without being canceled.
llassert_always(!mFilenames.empty());
// This function returns the first filename in the case that more than one was selected.
return mFilenames[0];
}
std::string AIFilePicker::getFolder(void) const
{
// Return the folder of the first filename.
return gDirUtilp->getDirName(getFilename());
}
// static
bool AIFilePicker::loadFile(std::string const& filename)
{
LLSD data;
std::string filepath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
llifstream file(filepath);
if (file.is_open())
{
llinfos << "Loading filepicker context file at \"" << filepath << "\"." << llendl;
LLSDSerialize::fromXML(data, file);
}
if (!data.isMap())
{
llinfos << "File missing, ill-formed, or simply undefined; not changing the file (" << filepath << ")." << llendl;
return false;
}
AIAccess<context_map_type> wContextMap(sContextMap);
for (LLSD::map_const_iterator iter = data.beginMap(); iter != data.endMap(); ++iter)
{
wContextMap->insert(context_map_type::value_type(iter->first, iter->second.asString()));
}
return true;
}
// static
bool AIFilePicker::saveFile(std::string const& filename)
{
AIAccess<context_map_type> wContextMap(sContextMap);
if (wContextMap->empty())
return false;
LLSD context;
for (context_map_type::iterator iter = wContextMap->begin(); iter != wContextMap->end(); ++iter)
{
context[iter->first] = iter->second;
}
std::string filepath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
llofstream file;
file.open(filepath.c_str());
if (!file.is_open())
{
llwarns << "Unable to open filepicker context file for save: \"" << filepath << "\"." << llendl;
return false;
}
LLSDSerialize::toPrettyXML(context, file);
file.close();
llinfos << "Saved default paths to \"" << filepath << "\"." << llendl;
return true;
}