aseprite/src/app/ui/file_selector.cpp

996 lines
29 KiB
C++

// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/file_selector.h"
#include "app/app.h"
#include "app/console.h"
#include "app/file/file.h"
#include "app/i18n/strings.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/ui/file_list.h"
#include "app/ui/file_list_view.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "app/widget_loader.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/paths.h"
#include "base/string.h"
#include "ui/ui.h"
#include "new_folder_window.xml.h"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <iterator>
#include <list>
#include <set>
#include <string>
#include <vector>
#ifndef MAX_PATH
#define MAX_PATH 4096 // TODO this is needed for Linux, is it correct?
#endif
#define FILESEL_TRACE(...) // TRACE
namespace app {
using namespace app::skin;
using namespace ui;
namespace {
const char* kConfigSection = "FileSelector";
template<class Container>
class NullableIterator {
public:
typedef typename Container::iterator iterator;
NullableIterator() : m_isNull(true) {}
bool is_null() const { return m_isNull; }
bool is_valid() const { return !m_isNull; }
bool exists() const { return (is_valid() && (*m_iterator)->isExistent()); }
iterator get()
{
ASSERT(!m_isNull);
return m_iterator;
}
void reset() { m_isNull = true; }
void set(const iterator& it)
{
m_isNull = false;
m_iterator = it;
}
private:
bool m_isNull;
iterator m_iterator;
};
// Variables used only to maintain the history of navigation.
FileItemList navigation_history; // Set of FileItems navigated
NullableIterator<FileItemList> navigation_position; // Current position in the navigation history
// This map acts like a temporal customization by the user when he/she
// wants to open files. The key (first) is the real "allExtensions"
// parameter given to the FileSelector::show() function where each
// extension is concatenated with each other in one string separated
// by ','. The value (second) is the selected/preferred extension by
// the user. It's used only in FileSelector::Open type of dialogs.
std::map<std::string, base::paths> preferred_open_extensions;
void adjust_navigation_history(IFileItem* item)
{
auto it = navigation_history.begin();
const bool valid = navigation_position.is_valid();
int pos = (valid ? int(navigation_position.get() - it) : 0);
FILESEL_TRACE("FILESEL: Removed item '%s' detected (%p)\n", item->fileName().c_str(), item);
if (valid) {
FILESEL_TRACE("FILESEL: Old navigation pos [%d] = %s\n",
pos,
(*navigation_position.get())->fileName().c_str());
}
while (true) {
it = std::find(it, navigation_history.end(), item);
if (it == navigation_history.end())
break;
FILESEL_TRACE("FILESEL: Erase navigation pos [%d] = %s\n", pos, (*it)->fileName().c_str());
if (pos >= it - navigation_history.begin())
--pos;
it = navigation_history.erase(it);
}
if (valid && !navigation_history.empty()) {
pos = std::clamp(pos, 0, (int)navigation_history.size() - 1);
navigation_position.set(navigation_history.begin() + pos);
FILESEL_TRACE("FILESEL: New navigation pos [%d] = %s\n",
pos,
(*navigation_position.get())->fileName().c_str());
}
else {
navigation_position.reset();
FILESEL_TRACE("FILESEL: Without new navigation pos\n");
}
}
std::string merge_paths(const base::paths& paths)
{
std::string k;
for (const auto& p : paths) {
if (!k.empty())
k.push_back(',');
k += p;
}
return k;
}
} // anonymous namespace
class FileSelector::CustomFileNameEntry : public ComboBox {
public:
CustomFileNameEntry() : m_fileList(nullptr)
{
setEditable(true);
getEntryWidget()->Change.connect(&CustomFileNameEntry::onEntryChange, this);
}
void setAssociatedFileList(FileList* fileList) { m_fileList = fileList; }
protected:
void onEntryChange()
{
// Deselect multiple-selection
if (m_fileList->multipleSelection())
m_fileList->deselectedFileItems();
deleteAllItems();
// String to be autocompleted
std::string left_part = getEntryWidget()->text();
closeListBox();
if (left_part.empty())
return;
for (const IFileItem* child : m_fileList->fileList()) {
std::string child_name = child->displayName();
std::string::const_iterator it1, it2;
for (it1 = child_name.begin(), it2 = left_part.begin();
it1 != child_name.end() && it2 != left_part.end();
++it1, ++it2) {
if (std::tolower(*it1) != std::tolower(*it2))
break;
}
// Is the pattern (left_part) in the child_name's beginning?
if (it1 != child_name.end() && it2 == left_part.end())
addItem(child_name);
}
if (getItemCount() > 0)
openListBox();
}
private:
FileList* m_fileList;
};
class FileSelector::CustomFileNameItem : public ListItem {
public:
CustomFileNameItem(const char* text, IFileItem* fileItem) : ListItem(text), m_fileItem(fileItem)
{
}
IFileItem* getFileItem() { return m_fileItem; }
private:
IFileItem* m_fileItem;
};
class FileSelector::CustomFolderNameItem : public ListItem {
public:
CustomFolderNameItem(const char* text) : ListItem(text) {}
};
class FileSelector::CustomFileExtensionItem : public ListItem {
public:
CustomFileExtensionItem(const std::string& text, const base::paths& exts)
: ListItem(text)
, m_exts(exts)
{
}
const base::paths& extensions() const { return m_exts; }
private:
base::paths m_exts;
};
// We have this dummy/hidden widget only to handle special navigation
// with arrow keys. In the past this code was in the same FileSelector
// itself, but there were problems adding that window as a message
// filter. Mainly there is a special combination of widgets
// (comboboxes) that need to filter Esc key (e.g. to close the
// combobox popup). And we cannot pre-add a filter that send that key
// to the Manager before it's processed by the combobox filter.
class FileSelector::ArrowNavigator : public Widget {
public:
ArrowNavigator(FileSelector* filesel) : Widget(kGenericWidget), m_filesel(filesel)
{
setVisible(false);
}
protected:
bool onProcessMessage(ui::Message* msg) override
{
switch (msg->type()) {
case kOpenMessage: manager()->addMessageFilter(kKeyDownMessage, this); break;
case kCloseMessage: manager()->removeMessageFilter(kKeyDownMessage, this); break;
case kKeyDownMessage: {
KeyMessage* keyMsg = static_cast<KeyMessage*>(msg);
KeyScancode scancode = keyMsg->scancode();
#ifdef __APPLE__
int unicode = keyMsg->unicodeChar();
bool up = (msg->cmdPressed() && scancode == kKeyUp);
bool enter = (msg->cmdPressed() && scancode == kKeyDown);
bool back = (msg->cmdPressed() && (unicode == '[' || scancode == kKeyOpenbrace));
bool forward = (msg->cmdPressed() && (unicode == ']' || scancode == kKeyClosebrace));
#else
bool up = (msg->altPressed() && scancode == kKeyUp);
bool enter = (msg->altPressed() && scancode == kKeyDown);
bool back = (msg->altPressed() && scancode == kKeyLeft);
bool forward = (msg->altPressed() && scancode == kKeyRight);
#endif
bool refresh = (scancode == kKeyF5 || (msg->ctrlPressed() && scancode == kKeyR) ||
(msg->cmdPressed() && scancode == kKeyR));
if (up) {
m_filesel->goUp();
return true;
}
if (enter) {
m_filesel->goInsideFolder();
return true;
}
if (back) {
m_filesel->goBack();
return true;
}
if (forward) {
m_filesel->goForward();
return true;
}
if (refresh) {
m_filesel->refreshCurrentFolder();
}
return false;
}
}
return Widget::onProcessMessage(msg);
}
private:
FileSelector* m_filesel;
};
FileSelector::FileSelector(FileSelectorType type) : m_type(type), m_navigationLocked(false)
{
addChild(new ArrowNavigator(this));
m_fileName = new CustomFileNameEntry;
m_fileName->setFocusMagnet(true);
fileNamePlaceholder()->addChild(m_fileName);
goBackButton()->setFocusStop(false);
goForwardButton()->setFocusStop(false);
goUpButton()->setFocusStop(false);
refreshButton()->setFocusStop(false);
newFolderButton()->setFocusStop(false);
viewType()->setFocusStop(false);
for (auto child : viewType()->children())
child->setFocusStop(false);
showHiddenCheck()->setSelected(Preferences::instance().fileSelector.showHidden());
m_fileList = new FileList();
m_fileList->setId("fileview");
m_fileName->setAssociatedFileList(m_fileList);
m_fileList->setZoom(Preferences::instance().fileSelector.zoom());
m_fileView = new FileListView();
m_fileView->attachToView(m_fileList);
m_fileView->setExpansive(true);
fileViewPlaceholder()->addChild(m_fileView);
goBackButton()->Click.connect([this] { onGoBack(); });
goForwardButton()->Click.connect([this] { onGoForward(); });
goUpButton()->Click.connect([this] { onGoUp(); });
refreshButton()->Click.connect([this] { onRefreshFolder(); });
newFolderButton()->Click.connect([this] { onNewFolder(); });
viewType()->ItemChange.connect([this] { onChangeViewType(); });
location()->CloseListBox.connect([this] { onLocationCloseListBox(); });
fileType()->Change.connect([this] { onFileTypeChange(); });
showHiddenCheck()->Click.connect([this] {
Preferences::instance().fileSelector.showHidden(showHiddenCheck()->isSelected());
m_fileList->setShowHidden(showHiddenCheck()->isSelected());
});
m_fileList->FileSelected.connect([this] { onFileListFileSelected(); });
m_fileList->FileAccepted.connect([this] { onFileListFileAccepted(); });
m_fileList->CurrentFolderChanged.connect([this] { onFileListCurrentFolderChanged(); });
}
void FileSelector::setDefaultExtension(const std::string& extension)
{
m_defExtension = extension;
}
FileSelector::~FileSelector()
{
}
void FileSelector::goBack()
{
onGoBack();
}
void FileSelector::goForward()
{
onGoForward();
}
void FileSelector::goUp()
{
onGoUp();
}
void FileSelector::goInsideFolder()
{
if (m_fileList->selectedFileItem() && m_fileList->selectedFileItem()->isBrowsable()) {
m_fileList->setCurrentFolder(m_fileList->selectedFileItem());
}
}
void FileSelector::refreshCurrentFolder()
{
onRefreshFolder();
}
bool FileSelector::show(const std::string& title,
const std::string& initialPath,
const base::paths& allExtensions,
base::paths& output)
{
FileSystemModule* fs = FileSystemModule::instance();
LockFS lock(fs);
// Connection used to remove items from the navigation history that
// are not found in the file system anymore.
obs::scoped_connection conn = fs->ItemRemoved.connect(&adjust_navigation_history);
fs->refresh();
// We have to find where the user should begin to browse files
std::string start_folder_path = base::get_file_path(
get_initial_path_to_select_filename(initialPath));
IFileItem* start_folder = fs->getFileItemFromPath(start_folder_path);
if (!start_folder) {
// If the directory doesn't exist anymore, get the default FS item
// (root item, or user folder).
start_folder = fs->getFileItemFromPath(std::string());
}
FILESEL_TRACE("FILESEL: Start folder '%s' (%p)\n", start_folder_path.c_str(), start_folder);
{
const gfx::Size workareaSize = ui::Manager::getDefault()->display()->workareaSizeUIScale();
setMinSize(workareaSize * 9 / 10);
}
remapWindow();
centerWindow();
// The minimum size is large, so do not limit the minimum loaded size
load_window_pos(this, kConfigSection, false);
// Change the file formats/extensions to be shown
std::string initialExtension = base::get_file_extension(initialPath);
base::paths exts;
if (m_type == FileSelectorType::Open || m_type == FileSelectorType::OpenMultiple) {
std::string k = merge_paths(allExtensions);
auto it = preferred_open_extensions.find(k);
if (it == preferred_open_extensions.end())
exts = allExtensions;
else
exts = preferred_open_extensions[k];
}
else {
ASSERT(m_type == FileSelectorType::Save);
if (!initialExtension.empty())
exts = base::paths{ initialExtension };
else
exts = allExtensions;
}
m_fileList->setMultipleSelection(m_type == FileSelectorType::OpenMultiple);
m_fileList->setExtensions(exts);
if (start_folder)
m_fileList->setCurrentFolder(start_folder);
// current location
navigation_position.reset();
addInNavigationHistory(m_fileList->currentFolder());
// fill the location combo-box
updateLocation();
updateNavigationButtons();
// fill file-type combo-box
fileType()->deleteAllItems();
// Get the default extension from the given initial file name
if (m_defExtension.empty())
m_defExtension = initialExtension;
// File type for all formats
fileType()->addItem(
new CustomFileExtensionItem(Strings::file_selector_all_formats(), allExtensions));
// One file type for each supported image format
for (const auto& e : allExtensions) {
// If the default extension is empty, use the first filter
if (m_defExtension.empty())
m_defExtension = e;
fileType()->addItem(new CustomFileExtensionItem(e + " files", base::paths{ e }));
}
// All files
fileType()->addItem(new CustomFileExtensionItem(Strings::file_selector_all_files(),
base::paths())); // Empty extensions means "*.*"
// file name entry field
m_fileName->setValue(base::get_file_name(initialPath).c_str());
m_fileName->getEntryWidget()->selectText(0, -1);
for (Widget* wItem : *fileType()) {
auto item = dynamic_cast<CustomFileExtensionItem*>(wItem);
ASSERT(item);
if (item && item->extensions() == exts) {
fileType()->setSelectedItem(item);
break;
}
}
// setup the title of the window
setText(title.c_str());
// get the ok-button
Widget* ok = this->findChild("ok");
// update the view
View::getView(m_fileList)->updateView();
// TODO this loop needs a complete refactor
// Open the window and run... the user press ok?
again:
openWindowInForeground();
if (closer() == ok || closer() == m_fileList) {
IFileItem* folder = m_fileList->currentFolder();
ASSERT(folder);
// File name in the text entry field/combobox
std::string fn = m_fileName->getValue();
std::string buf;
IFileItem* enter_folder = nullptr;
// up a level?
if (fn == "..") {
enter_folder = folder->parent();
if (!enter_folder)
enter_folder = folder;
}
else if (fn.empty()) {
IFileItem* selected = m_fileList->selectedFileItem();
if (selected && selected->isBrowsable())
enter_folder = selected;
else if (m_type != FileSelectorType::OpenMultiple ||
m_fileList->selectedFileItems().empty()) {
// Show the window again
setVisible(true);
goto again;
}
}
else {
// check if the user specified in "fn" a item of "fileview"
const FileItemList& children = m_fileList->fileList();
std::string fn2 = fn;
#ifdef _WIN32
fn2 = base::string_to_lower(fn2);
#endif
for (IFileItem* child : children) {
std::string child_name = child->displayName();
#ifdef _WIN32
child_name = base::string_to_lower(child_name);
#endif
if (child_name == fn2) {
enter_folder = child;
buf = enter_folder->fileName();
break;
}
}
if (!enter_folder) {
// does the file-name entry have separators?
if (base::is_path_separator(*fn.begin())) { // absolute path (UNIX style)
#ifdef _WIN32
// get the drive of the current folder
std::string drive = folder->fileName();
if (drive.size() >= 2 && drive[1] == ':') {
buf += drive[0];
buf += ':';
buf += fn;
}
else
buf = base::join_path("C:", fn);
#else
buf = fn;
#endif
}
#ifdef _WIN32
// does the file-name entry have colon?
else if (fn.find(':') != std::string::npos) { // absolute path on Windows
if (fn.size() == 2 && fn[1] == ':') {
buf = base::join_path(fn, "");
}
else {
buf = fn;
}
}
#endif
else {
buf = folder->fileName();
buf = base::join_path(buf, fn);
}
buf = base::fix_path_separators(buf);
// we can check if 'buf' is a folder, so we have to enter in it
enter_folder = fs->getFileItemFromPath(buf);
}
}
// did we find a folder to enter?
if (enter_folder && enter_folder->isFolder() && enter_folder->isBrowsable()) {
// enter in the folder that was specified in the 'm_fileName'
m_fileList->setCurrentFolder(enter_folder);
// clear the text of the entry widget
m_fileName->setValue("");
// show the window again
setVisible(true);
goto again;
}
// else file-name specified in the entry is really a file to open...
#ifdef _WIN32
// Check that the filename doesn't contain ilegal characters.
// Linux allows all kind of characters, only '/' is disallowed,
// but in that case we consider that a full path was entered in
// the filename and we can enter to the full path folder.
if (!enter_folder) {
const bool has_invalid_char =
(fn.find(':') != std::string::npos || fn.find('*') != std::string::npos ||
fn.find('?') != std::string::npos || fn.find('\"') != std::string::npos ||
fn.find('<') != std::string::npos || fn.find('>') != std::string::npos ||
fn.find('|') != std::string::npos);
if (has_invalid_char) {
const char* invalid_chars = ": * ? \" < > |";
ui::Alert::show(Strings::alerts_invalid_chars_in_filename(invalid_chars));
// show the window again
setVisible(true);
goto again;
}
}
#endif
// Does it not have extension? ...we should add the extension
// selected in the filetype combo-box
if (m_type == FileSelectorType::Save && !buf.empty() && base::get_file_extension(buf).empty()) {
buf += '.';
buf += getSelectedExtension();
}
if (m_type == FileSelectorType::Save && base::is_file(buf)) {
int ret = Alert::show(Strings::alerts_overwrite_existent_file(base::get_file_name(buf)));
if (ret == 2) {
setVisible(true);
goto again;
}
else if (ret == 1) {
// Check for read-only attribute
if (base::has_readonly_attr(buf)) {
ui::Alert::show(Strings::alerts_cannot_save_in_read_only_file());
setVisible(true);
goto again;
}
}
// Cancel
else if (ret != 1) {
return false;
}
}
// Put in output the selected filenames
if (!buf.empty())
output.push_back(buf);
else if (m_type == FileSelectorType::OpenMultiple) {
for (IFileItem* fi : m_fileList->selectedFileItems())
output.push_back(fi->fileName());
}
// save the path in the configuration file
std::string lastpath = folder->keyName();
set_current_dir_for_file_selector(lastpath);
}
Preferences::instance().fileSelector.zoom(m_fileList->zoom());
return (!output.empty());
}
void FileSelector::onOpen(Event& ev)
{
app::gen::FileSelector::onOpen(ev);
onRefreshFolder();
}
bool FileSelector::onProcessMessage(ui::Message* msg)
{
switch (msg->type()) {
case kCloseMessage: save_window_pos(this, kConfigSection); break;
}
return app::gen::FileSelector::onProcessMessage(msg);
}
// Updates the content of the combo-box that shows the current
// location in the file-system.
void FileSelector::updateLocation()
{
IFileItem* currentFolder = m_fileList->currentFolder();
IFileItem* fileItem = currentFolder;
std::list<IFileItem*> locations;
int selected_index = -1;
while (fileItem) {
locations.push_front(fileItem);
fileItem = fileItem->parent();
}
// Clear all the items from the combo-box
location()->deleteAllItems();
// Add item by item (from root to the specific current folder)
int level = 0;
for (auto it = locations.begin(), end = locations.end(); it != end; ++it) {
fileItem = *it;
// Indentation
std::string buf;
for (int c = 0; c < level; ++c)
buf += " ";
// Location name
buf += fileItem->displayName();
// Add the new location to the combo-box
location()->addItem(new CustomFileNameItem(buf.c_str(), fileItem));
if (fileItem == currentFolder)
selected_index = level;
level++;
}
// Add paths from recent files list
auto recent = App::instance()->recentFiles();
if (!recent->pinnedFolders().empty()) {
auto sep = new SeparatorInView(Strings::file_selector_pinned_folders(), HORIZONTAL);
sep->setMinSize(gfx::Size(0, sep->sizeHint().h * 2));
location()->addItem(sep);
for (const auto& fn : recent->pinnedFolders())
location()->addItem(new CustomFolderNameItem(fn.c_str()));
}
if (!recent->recentFolders().empty()) {
auto sep = new SeparatorInView(Strings::file_selector_recent_folders(), HORIZONTAL);
sep->setMinSize(gfx::Size(0, sep->sizeHint().h * 2));
location()->addItem(sep);
for (const auto& fn : recent->recentFolders())
location()->addItem(new CustomFolderNameItem(fn.c_str()));
}
// Select the location
{
location()->setSelectedItemIndex(selected_index);
location()->getEntryWidget()->setText(currentFolder->displayName().c_str());
location()->getEntryWidget()->deselectText();
}
}
void FileSelector::updateNavigationButtons()
{
// Update the state of the go back button: if the navigation-history
// has two elements and the navigation-position isn't the first one.
goBackButton()->setEnabled(
navigation_history.size() > 1 &&
(navigation_position.is_null() || navigation_position.get() != navigation_history.begin()));
// Update the state of the go forward button: if the
// navigation-history has two elements and the navigation-position
// isn't the last one.
goForwardButton()->setEnabled(navigation_history.size() > 1 && navigation_position.is_valid() &&
navigation_position.get() != navigation_history.end() - 1);
// Update the state of the go up button: if the current-folder isn't
// the root-item.
IFileItem* currentFolder = m_fileList->currentFolder();
goUpButton()->setEnabled(currentFolder != FileSystemModule::instance()->getRootFileItem());
}
void FileSelector::addInNavigationHistory(IFileItem* folder)
{
ASSERT(folder);
ASSERT(folder->isFolder());
// Remove the history from the current position
if (navigation_position.is_valid()) {
navigation_history.erase(navigation_position.get() + 1, navigation_history.end());
navigation_position.reset();
}
// If the history is empty or if the last item isn't the folder that
// we are visiting...
if (navigation_history.empty() || navigation_history.back() != folder) {
// We can add the location in the history
navigation_history.push_back(folder);
navigation_position.set(navigation_history.end() - 1);
}
}
void FileSelector::onGoBack()
{
if (navigation_history.size() > 1) {
// The default navigation position is at the end of the history
if (navigation_position.is_null())
navigation_position.set(navigation_history.end() - 1);
if (navigation_position.get() != navigation_history.begin()) {
// Go back to the first existent element
do {
navigation_position.set(navigation_position.get() - 1);
} while (!navigation_position.exists() &&
navigation_position.get() != navigation_history.begin());
if (navigation_position.exists()) {
m_navigationLocked = true;
m_fileList->setCurrentFolder(*navigation_position.get());
m_navigationLocked = false;
}
else {
navigation_position.reset();
}
}
}
}
void FileSelector::onGoForward()
{
if (navigation_history.size() > 1) {
// This should not happen, because the forward button should be
// disabled when the navigation position is null.
if (navigation_position.is_null()) {
ASSERT(false);
navigation_position.set(navigation_history.end() - 1);
}
if (navigation_position.get() != navigation_history.end() - 1) {
// Go forward to the first existent element
do {
navigation_position.set(navigation_position.get() + 1);
} while (!navigation_position.exists() &&
navigation_position.get() != navigation_history.end() - 1);
if (navigation_position.exists()) {
m_navigationLocked = true;
m_fileList->setCurrentFolder(*navigation_position.get());
m_navigationLocked = false;
}
else {
navigation_position.reset();
}
}
}
}
void FileSelector::onGoUp()
{
m_fileList->goUp();
}
void FileSelector::onRefreshFolder()
{
auto fs = FileSystemModule::instance();
fs->refresh();
m_fileList->setCurrentFolder(m_fileList->currentFolder());
}
void FileSelector::onNewFolder()
{
app::gen::NewFolderWindow window;
window.openWindowInForeground();
if (window.closer() == window.ok()) {
IFileItem* currentFolder = m_fileList->currentFolder();
if (currentFolder) {
std::string dirname = window.name()->text();
// Create the new directory
try {
currentFolder->createDirectory(dirname);
// Enter in the new folder
for (auto child : currentFolder->children()) {
if (child->displayName() == dirname) {
m_fileList->setCurrentFolder(child);
break;
}
}
}
catch (const std::exception& e) {
Console::showException(e);
}
}
}
}
void FileSelector::onChangeViewType()
{
double newZoom = m_fileList->zoom();
switch (viewType()->selectedItem()) {
case 0: newZoom = 1.0; break;
case 1: newZoom = 2.0; break;
case 2: newZoom = 8.0; break;
}
m_fileList->animateToZoom(newZoom);
}
// Hook for the 'location' combo-box
void FileSelector::onLocationCloseListBox()
{
// When the user change the location we have to set the
// current-folder in the 'fileview' widget
CustomFileNameItem* comboFileItem = dynamic_cast<CustomFileNameItem*>(
location()->getSelectedItem());
IFileItem* fileItem = (comboFileItem ? comboFileItem->getFileItem() : nullptr);
// Maybe the user selected a recent file path
if (fileItem == nullptr) {
CustomFolderNameItem* comboFolderItem = dynamic_cast<CustomFolderNameItem*>(
location()->getSelectedItem());
if (comboFolderItem) {
std::string path = comboFolderItem->text();
fileItem = FileSystemModule::instance()->getFileItemFromPath(path);
}
}
if (fileItem) {
m_fileList->setCurrentFolder(fileItem);
// Refocus the 'fileview' (the focus in that widget is more
// useful for the user)
m_fileList->requestFocus();
}
}
// When the user selects a new file-type (extension), we have to
// change the file-extension in the 'filename' entry widget
void FileSelector::onFileTypeChange()
{
base::paths exts;
auto* selExtItem = dynamic_cast<CustomFileExtensionItem*>(fileType()->getSelectedItem());
if (selExtItem)
exts = selExtItem->extensions();
if (exts != m_fileList->extensions()) {
m_navigationLocked = true;
m_fileList->setExtensions(exts);
m_navigationLocked = false;
if (m_type == FileSelectorType::Open || m_type == FileSelectorType::OpenMultiple) {
const base::paths& allExtensions =
dynamic_cast<CustomFileExtensionItem*>(fileType()->getItem(0))->extensions();
std::string k = merge_paths(allExtensions);
preferred_open_extensions[k] = exts;
}
}
if (m_type == FileSelectorType::Save) {
std::string newExtension = getSelectedExtension();
std::string fileName = m_fileName->getValue();
std::string currentExtension = base::get_file_extension(fileName);
if (!currentExtension.empty())
m_fileName->setValue(
(fileName.substr(0, fileName.size() - currentExtension.size()) + newExtension).c_str());
}
}
void FileSelector::onFileListFileSelected()
{
IFileItem* fileitem = m_fileList->selectedFileItem();
if (fileitem && !fileitem->isFolder()) {
std::string filename = base::get_file_name(fileitem->fileName());
if (m_type != FileSelectorType::OpenMultiple || m_fileList->selectedFileItems().size() == 1)
m_fileName->setValue(filename.c_str());
else
m_fileName->setValue(std::string());
}
}
void FileSelector::onFileListFileAccepted()
{
closeWindow(m_fileList);
}
void FileSelector::onFileListCurrentFolderChanged()
{
if (!m_navigationLocked)
addInNavigationHistory(m_fileList->currentFolder());
updateLocation();
updateNavigationButtons();
// Close the autocomplete popup just in case it's open.
m_fileName->closeListBox();
}
std::string FileSelector::getSelectedExtension() const
{
auto selExtItem = dynamic_cast<CustomFileExtensionItem*>(fileType()->getSelectedItem());
if (selExtItem && selExtItem->extensions().size() == 1)
return selExtItem->extensions().front();
else
return m_defExtension;
}
} // namespace app