| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- //
- // Urho3D Engine
- // Copyright (c) 2008-2011 Lasse Öörni
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- #include "Precompiled.h"
- #include "DropDownList.h"
- #include "File.h"
- #include "FileSelector.h"
- #include "InputEvents.h"
- #include "LineEdit.h"
- #include "ListView.h"
- #include "ProcessUtils.h"
- #include "Text.h"
- #include "UI.h"
- #include "UIEvents.h"
- #include "Window.h"
- #include <algorithm>
- #include "DebugNew.h"
- static bool compareEntries(const FileSelectorEntry& lhs, const FileSelectorEntry& rhs)
- {
- if ((lhs.mDirectory) && (!rhs.mDirectory))
- return true;
- if ((!lhs.mDirectory) && (rhs.mDirectory))
- return false;
- return lhs.mName < rhs.mName;
- }
- FileSelector::FileSelector(UI* ui) :
- mUI(ui),
- mIgnoreEvents(false),
- mDirectoryMode(false)
- {
- if (!mUI)
- EXCEPTION("Null UI for FileSelector");
-
- mWindow = new Window();
- mWindow->setLayout(LM_VERTICAL);
-
- mTitleLayout = new UIElement();
- mTitleLayout->setLayout(LM_HORIZONTAL);
- mWindow->addChild(mTitleLayout);
-
- mTitleText = new Text();
- mTitleLayout->addChild(mTitleText);
-
- mCloseButton = new Button();
- mTitleLayout->addChild(mCloseButton);
-
- mPathEdit = new LineEdit();
- mWindow->addChild(mPathEdit);
-
- mFileList = new ListView();
- mWindow->addChild(mFileList);
-
- mFileNameLayout = new UIElement();
- mFileNameLayout->setLayout(LM_HORIZONTAL);
-
- mFileNameEdit = new LineEdit();
- mFileNameLayout->addChild(mFileNameEdit);
-
- mFilterList = new DropDownList();
- mFileNameLayout->addChild(mFilterList);
-
- mWindow->addChild(mFileNameLayout);
-
- mButtonLayout = new UIElement();
- mButtonLayout->setLayout(LM_HORIZONTAL);
-
- mButtonLayout->addChild(new UIElement()); // Add spacer
-
- mOKButton = new Button();
- mOKButtonText = new Text();
- mOKButtonText->setAlignment(HA_CENTER, VA_CENTER);
- mOKButton->addChild(mOKButtonText);
- mButtonLayout->addChild(mOKButton);
-
- mButtonLayout->addChild(new UIElement()); // Add spacer
-
- mCancelButton = new Button();
- mCancelButtonText = new Text();
- mCancelButtonText->setAlignment(HA_CENTER, VA_CENTER);
- mCancelButton->addChild(mCancelButtonText);
- mButtonLayout->addChild(mCancelButton);
-
- mButtonLayout->addChild(new UIElement()); // Add spacer
-
- mWindow->addChild(mButtonLayout);
-
- mUI->getRootElement()->addChild(mWindow);
-
- std::vector<std::string> defaultFilters;
- defaultFilters.push_back("*.*");
- setFilters(defaultFilters, 0);
- setPath(getCurrentDirectory());
-
- // Focus the fileselector's filelist initially when created, and bring to front
- mUI->setFocusElement(mFileList);
- mWindow->bringToFront();
-
- subscribeToEvent(mFilterList, EVENT_ITEMSELECTED, EVENT_HANDLER(FileSelector, handleFilterChanged));
- subscribeToEvent(mPathEdit, EVENT_TEXTFINISHED, EVENT_HANDLER(FileSelector, handlePathChanged));
- subscribeToEvent(mFileNameEdit, EVENT_TEXTFINISHED, EVENT_HANDLER(FileSelector, handleOKPressed));
- subscribeToEvent(mFileList, EVENT_ITEMSELECTED, EVENT_HANDLER(FileSelector, handleFileSelected));
- subscribeToEvent(mFileList, EVENT_ITEMDOUBLECLICKED, EVENT_HANDLER(FileSelector, handleFileDoubleClicked));
- subscribeToEvent(mFileList, EVENT_UNHANDLEDKEY, EVENT_HANDLER(FileSelector, handleFileListKey));
- subscribeToEvent(mOKButton, EVENT_RELEASED, EVENT_HANDLER(FileSelector, handleOKPressed));
- subscribeToEvent(mCancelButton, EVENT_RELEASED, EVENT_HANDLER(FileSelector, handleCancelPressed));
- subscribeToEvent(mCloseButton, EVENT_RELEASED, EVENT_HANDLER(FileSelector, handleCancelPressed));
- }
- FileSelector::~FileSelector()
- {
- UIElement* root = mUI->getRootElement();
- //! \todo This should not be necessary
- root->removeChild(mFilterList->getPopup());
- root->removeChild(mWindow);
- }
- void FileSelector::setStyle(XMLFile* style)
- {
- if (!style)
- return;
-
- mStyle = style;
- ResourceCache* cache = mUI->getResourceCache();
-
- mWindow->setStyleAuto(style, cache);
- mWindow->setStyle(style, "FileSelector", cache);
-
- mTitleText->setStyle(style, "FileSelectorTitleText", cache);
- mCloseButton->setStyle(style, "CloseButton", cache);
-
- mOKButtonText->setStyle(style, "FileSelectorButtonText", cache);
- mCancelButtonText->setStyle(style, "FileSelectorButtonText", cache);
-
- mTitleLayout->setStyle(style, "FileSelectorTitleLayout", cache);
- mFileNameLayout->setStyle(style, "FileSelectorLayout", cache);
- mButtonLayout->setStyle(style, "FileSelectorLayout", cache);
-
- mFileList->setStyleAuto(style, cache);
- mFileNameEdit->setStyleAuto(style, cache);
- mPathEdit->setStyleAuto(style, cache);
-
- mFilterList->setStyleAuto(style, cache);
- mFilterList->setStyle(style, "FileSelectorFilterList", cache);
-
- mOKButton->setStyleAuto(style, cache);
- mCancelButton->setStyleAuto(style, cache);
- mOKButton->setStyle(style, "FileSelectorButton", cache);
- mCancelButton->setStyle(style, "FileSelectorButton", cache);
-
- std::vector<UIElement*> filterTexts = mFilterList->getListView()->getContentElement()->getChildren();
- for (unsigned i = 0; i < filterTexts.size(); ++i)
- filterTexts[i]->setStyle(style, "FileSelectorFilterText", cache);
-
- std::vector<UIElement*> listTexts = mFileList->getContentElement()->getChildren();
- for (unsigned i = 0; i < listTexts.size(); ++i)
- listTexts[i]->setStyle(style, "FileSelectorListText", cache);
-
- updateElements();
- }
- void FileSelector::setTitle(const std::string& text)
- {
- mTitleText->setText(text);
- }
- void FileSelector::setButtonTexts(const std::string& okText, const std::string& cancelText)
- {
- mOKButtonText->setText(okText);
- mCancelButtonText->setText(cancelText);
- }
- void FileSelector::setPath(const std::string& path)
- {
- if (directoryExists(path))
- {
- mPath = fixPath(path);
- mIgnoreEvents = true;
- mPathEdit->setText(mPath);
- mIgnoreEvents = false;
- refreshFiles();
- }
- else
- {
- // If path was invalid, restore the old path to the line edit
- if (mPathEdit->getText() != mPath)
- {
- mIgnoreEvents = true;
- mPathEdit->setText(mPath);
- mIgnoreEvents = false;
- }
- }
- }
- void FileSelector::setFileName(const std::string& fileName)
- {
- mIgnoreEvents = true;
- mFileNameEdit->setText(fileName);
- mIgnoreEvents = false;
- }
- void FileSelector::setFilters(const std::vector<std::string>& filters, unsigned defaultIndex)
- {
- if (filters.empty())
- return;
-
- mIgnoreEvents = true;
- mFilters = filters;
- mFilterList->removeAllItems();
- for (unsigned i = 0; i < mFilters.size(); ++i)
- {
- Text* filterText = new Text();
- filterText->setText(mFilters[i]);
- filterText->setStyle(mStyle, "FileSelectorFilterText", mUI->getResourceCache());
- mFilterList->addItem(filterText);
- }
- if (defaultIndex > filters.size())
- defaultIndex = 0;
- mFilterList->setSelection(defaultIndex);
- mIgnoreEvents = false;
- if (getFilter() != mLastUsedFilter)
- refreshFiles();
- }
- void FileSelector::setDirectoryMode(bool enable)
- {
- mDirectoryMode = enable;
- }
- void FileSelector::updateElements()
- {
- {
- const IntRect& clipBorder = mPathEdit->getClipBorder();
- mPathEdit->setFixedHeight(mPathEdit->getTextElement()->getRowHeight() + clipBorder.mTop + clipBorder.mBottom);
- }
-
- {
- const IntRect& clipBorder = mFileNameEdit->getClipBorder();
- int fileNameHeight = mFileNameEdit->getTextElement()->getRowHeight() + clipBorder.mTop + clipBorder.mBottom;
- mFileNameEdit->setFixedHeight(fileNameHeight);
- mFilterList->setFixedHeight(fileNameHeight);
- mFileNameLayout->setFixedHeight(fileNameHeight);
- }
-
- mButtonLayout->setFixedHeight(max(mOKButton->getHeight(), mCancelButton->getHeight()));
- }
- const std::string& FileSelector::getFileName() const
- {
- return mFileNameEdit->getText();
- }
- const std::string& FileSelector::getFilter() const
- {
- static std::string emptyFilter;
-
- Text* selectedFilter = static_cast<Text*>(mFilterList->getSelectedItem());
- if (selectedFilter)
- return selectedFilter->getText();
-
- return emptyFilter;
- }
- unsigned FileSelector::getFilterIndex() const
- {
- return mFilterList->getSelection();
- }
- void FileSelector::refreshFiles()
- {
- mIgnoreEvents = true;
-
- mFileList->removeAllItems();
- mFileEntries.clear();
- std::vector<std::string> directories;
- std::vector<std::string> files;
- scanDirectory(directories, mPath, "*.*", SCAN_DIRECTORIES, false);
- scanDirectory(files, mPath, getFilter(), SCAN_FILES, false);
-
- for (unsigned i = 0; i < directories.size(); ++i)
- {
- FileSelectorEntry newEntry;
- newEntry.mName = directories[i];
- newEntry.mDirectory = true;
- mFileEntries.push_back(newEntry);
- }
-
- for (unsigned i = 0; i < files.size(); ++i)
- {
- FileSelectorEntry newEntry;
- newEntry.mName = files[i];
- newEntry.mDirectory = false;
- mFileEntries.push_back(newEntry);
- }
-
- // Sort and add to the list view
- // While items are being added, disable layout update for performance optimization
- std::sort(mFileEntries.begin(), mFileEntries.end(), compareEntries);
- UIElement* listContent = mFileList->getContentElement();
- listContent->disableLayoutUpdate();
- for (unsigned i = 0; i < mFileEntries.size(); ++i)
- {
- std::string displayName;
- if (mFileEntries[i].mDirectory)
- displayName = "<DIR> " + mFileEntries[i].mName;
- else
- displayName = mFileEntries[i].mName;
-
- Text* entryText = new Text();
- entryText->setText(displayName);
- entryText->setStyle(mStyle, "FileSelectorListText", mUI->getResourceCache());
- mFileList->addItem(entryText);
- }
- listContent->enableLayoutUpdate();
- listContent->updateLayout();
-
- mIgnoreEvents = false;
-
- // Clear filename from the previous dir so that there is no confusion
- setFileName(std::string());
- mLastUsedFilter = getFilter();
- }
- bool FileSelector::enterFile()
- {
- unsigned index = mFileList->getSelection();
- if (index >= mFileEntries.size())
- return false;
-
- if (mFileEntries[index].mDirectory)
- {
- // If a directory doubleclicked, enter it. Recognize . and .. as a special case
- const std::string& newPath = mFileEntries[index].mName;
- if ((newPath != ".") && (newPath != ".."))
- setPath(mPath + newPath);
- else if (newPath == "..")
- {
- std::string parentPath = getParentPath(mPath);
- setPath(parentPath);
- }
-
- return true;
- }
- else
- {
- // Doubleclicking a file is the same as pressing OK
- if (!mDirectoryMode)
- {
- using namespace FileSelected;
-
- VariantMap eventData;
- eventData[P_FILENAME] = mPath + mFileEntries[index].mName;
- eventData[P_OK] = true;
- sendEvent(EVENT_FILESELECTED, eventData);
- }
- }
-
- return false;
- }
- void FileSelector::handleFilterChanged(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- if (getFilter() != mLastUsedFilter)
- refreshFiles();
- }
- void FileSelector::handlePathChanged(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- // Attempt to set path. Restores old if does not exist
- setPath(mPathEdit->getText());
- }
- void FileSelector::handleFileSelected(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- unsigned index = mFileList->getSelection();
- if (index >= mFileEntries.size())
- return;
- // If a file selected, update the filename edit field
- if (!mFileEntries[index].mDirectory)
- setFileName(mFileEntries[index].mName);
- }
- void FileSelector::handleFileDoubleClicked(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- enterFile();
- }
- void FileSelector::handleFileListKey(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- using namespace UnhandledKey;
-
- if (eventData[P_KEY].getInt() == KEY_RETURN)
- {
- bool entered = enterFile();
- // When a key is used to enter a directory, select the first file if no selection
- if ((entered) && (!mFileList->getSelectedItem()))
- mFileList->setSelection(0);
- }
- }
- void FileSelector::handleOKPressed(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- const std::string& fileName = getFileName();
-
- if (!mDirectoryMode)
- {
- if (!fileName.empty())
- {
- using namespace FileSelected;
-
- VariantMap newEventData;
- newEventData[P_FILENAME] = mPath + getFileName();
- newEventData[P_OK] = true;
- sendEvent(EVENT_FILESELECTED, newEventData);
- }
- }
- else if ((eventType == EVENT_RELEASED) && (!mPath.empty()))
- {
- using namespace FileSelected;
-
- VariantMap newEventData;
- newEventData[P_FILENAME] = mPath;
- newEventData[P_OK] = true;
- sendEvent(EVENT_FILESELECTED, newEventData);
- }
- }
- void FileSelector::handleCancelPressed(StringHash eventType, VariantMap& eventData)
- {
- if (mIgnoreEvents)
- return;
-
- using namespace FileSelected;
-
- VariantMap newEventData;
- newEventData[P_FILENAME] = std::string();
- newEventData[P_OK] = false;
- sendEvent(EVENT_FILESELECTED, newEventData);
- }
|