2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2022-05-04 02:11:47 +08:00
|
|
|
// Copyright (C) 2019-2022 Igara Studio S.A.
|
2018-02-21 21:39:30 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2012-06-16 10:37:59 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/file_selector.h"
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/app.h"
|
2014-06-29 05:01:32 +08:00
|
|
|
#include "app/console.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/file/file.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "app/i18n/strings.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/gfx.h"
|
|
|
|
#include "app/modules/gui.h"
|
2019-03-26 09:09:22 +08:00
|
|
|
#include "app/pref/preferences.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/recent_files.h"
|
|
|
|
#include "app/ui/file_list.h"
|
2016-01-06 03:37:52 +08:00
|
|
|
#include "app/ui/file_list_view.h"
|
2018-12-22 13:08:04 +08:00
|
|
|
#include "app/ui/separator_in_view.h"
|
2015-08-05 06:38:52 +08:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
2012-06-16 10:37:59 +08:00
|
|
|
#include "app/widget_loader.h"
|
2015-06-04 03:34:27 +08:00
|
|
|
#include "base/convert_to.h"
|
2014-09-22 03:49:59 +08:00
|
|
|
#include "base/fs.h"
|
2018-02-21 21:39:30 +08:00
|
|
|
#include "base/paths.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "base/string.h"
|
|
|
|
#include "fmt/format.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "ui/ui.h"
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-09-23 03:33:49 +08:00
|
|
|
#include "new_folder_window.xml.h"
|
2015-06-03 23:20:44 +08:00
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cctype>
|
2014-09-22 03:49:59 +08:00
|
|
|
#include <cerrno>
|
2012-06-16 10:37:59 +08:00
|
|
|
#include <iterator>
|
2018-12-22 13:08:04 +08:00
|
|
|
#include <list>
|
2012-06-16 10:37:59 +08:00
|
|
|
#include <set>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#ifndef MAX_PATH
|
2014-09-22 03:49:59 +08:00
|
|
|
# define MAX_PATH 4096 // TODO this is needed for Linux, is it correct?
|
2012-06-16 10:37:59 +08:00
|
|
|
#endif
|
|
|
|
|
2019-11-15 21:44:43 +08:00
|
|
|
#define FILESEL_TRACE(...) // TRACE
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
|
|
|
using namespace app::skin;
|
|
|
|
using namespace ui;
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
namespace {
|
|
|
|
|
2021-08-19 22:51:21 +08:00
|
|
|
const char* kConfigSection = "FileSelector";
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
template<class Container>
|
2013-08-06 08:20:19 +08:00
|
|
|
class NullableIterator {
|
2012-06-16 10:37:59 +08:00
|
|
|
public:
|
|
|
|
typedef typename Container::iterator iterator;
|
|
|
|
|
|
|
|
NullableIterator() : m_isNull(true) { }
|
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
bool is_null() const { return m_isNull; }
|
|
|
|
bool is_valid() const { return !m_isNull; }
|
2019-11-15 21:44:43 +08:00
|
|
|
bool exists() const {
|
|
|
|
return (is_valid() && (*m_iterator)->isExistent());
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
iterator get() {
|
2012-06-16 10:37:59 +08:00
|
|
|
ASSERT(!m_isNull);
|
|
|
|
return m_iterator;
|
|
|
|
}
|
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
void reset() {
|
|
|
|
m_isNull = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void set(const iterator& it) {
|
2012-06-16 10:37:59 +08:00
|
|
|
m_isNull = false;
|
|
|
|
m_iterator = it;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool m_isNull;
|
2019-11-15 02:36:56 +08:00
|
|
|
iterator m_iterator;
|
2012-06-16 10:37:59 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Variables used only to maintain the history of navigation.
|
2019-11-15 02:36:56 +08:00
|
|
|
FileItemList navigation_history; // Set of FileItems navigated
|
|
|
|
NullableIterator<FileItemList> navigation_position; // Current position in the navigation history
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
// This map acts like a temporal customization by the user when he/she
|
2018-02-21 21:39:30 +08:00
|
|
|
// 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.
|
2019-11-15 02:36:56 +08:00
|
|
|
std::map<std::string, base::paths> preferred_open_extensions;
|
2018-02-21 21:39:30 +08:00
|
|
|
|
2019-11-15 21:44:43 +08:00
|
|
|
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()) {
|
2022-06-10 05:28:06 +08:00
|
|
|
pos = std::clamp(pos, 0, (int)navigation_history.size()-1);
|
2019-11-15 21:44:43 +08:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
std::string merge_paths(const base::paths& paths)
|
2018-02-21 21:39:30 +08:00
|
|
|
{
|
|
|
|
std::string k;
|
|
|
|
for (const auto& p : paths) {
|
|
|
|
if (!k.empty())
|
|
|
|
k.push_back(',');
|
|
|
|
k += p;
|
|
|
|
}
|
|
|
|
return k;
|
|
|
|
}
|
2015-05-29 05:59:12 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
} // anonymous namespace
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
class FileSelector::CustomFileNameEntry : public ComboBox {
|
2012-06-16 10:37:59 +08:00
|
|
|
public:
|
2015-05-05 01:58:24 +08:00
|
|
|
CustomFileNameEntry()
|
|
|
|
: m_fileList(nullptr) {
|
|
|
|
setEditable(true);
|
2015-09-23 03:22:47 +08:00
|
|
|
getEntryWidget()->Change.connect(&CustomFileNameEntry::onEntryChange, this);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void setAssociatedFileList(FileList* fileList) {
|
|
|
|
m_fileList = fileList;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2014-09-10 11:58:25 +08:00
|
|
|
|
2015-05-05 01:58:24 +08:00
|
|
|
void onEntryChange() {
|
2017-04-08 11:06:25 +08:00
|
|
|
// Deselect multiple-selection
|
|
|
|
if (m_fileList->multipleSelection())
|
|
|
|
m_fileList->deselectedFileItems();
|
|
|
|
|
2019-05-07 21:28:37 +08:00
|
|
|
deleteAllItems();
|
2014-09-10 11:58:25 +08:00
|
|
|
|
|
|
|
// String to be autocompleted
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string left_part = getEntryWidget()->text();
|
2015-05-05 01:58:24 +08:00
|
|
|
closeListBox();
|
|
|
|
|
2014-09-10 11:58:25 +08:00
|
|
|
if (left_part.empty())
|
|
|
|
return;
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
for (const IFileItem* child : m_fileList->fileList()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string child_name = child->displayName();
|
2015-05-05 01:58:24 +08:00
|
|
|
std::string::const_iterator it1, it2;
|
2014-09-10 11:58:25 +08:00
|
|
|
|
|
|
|
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?
|
2015-05-05 01:58:24 +08:00
|
|
|
if (it1 != child_name.end() && it2 == left_part.end())
|
|
|
|
addItem(child_name);
|
2014-09-10 11:58:25 +08:00
|
|
|
}
|
2015-05-05 01:58:24 +08:00
|
|
|
|
|
|
|
if (getItemCount() > 0)
|
|
|
|
openListBox();
|
2014-09-10 11:58:25 +08:00
|
|
|
}
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
private:
|
|
|
|
FileList* m_fileList;
|
|
|
|
};
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
class FileSelector::CustomFileNameItem : public ListItem {
|
2013-04-04 07:31:02 +08:00
|
|
|
public:
|
|
|
|
CustomFileNameItem(const char* text, IFileItem* fileItem)
|
|
|
|
: ListItem(text)
|
|
|
|
, m_fileItem(fileItem)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
IFileItem* getFileItem() { return m_fileItem; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
IFileItem* m_fileItem;
|
|
|
|
};
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
class FileSelector::CustomFolderNameItem : public ListItem {
|
2013-04-04 07:31:02 +08:00
|
|
|
public:
|
|
|
|
CustomFolderNameItem(const char* text)
|
|
|
|
: ListItem(text)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-02-21 21:39:30 +08:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2015-05-07 03:27:45 +08:00
|
|
|
// 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.
|
2016-09-17 12:01:28 +08:00
|
|
|
class FileSelector::ArrowNavigator : public Widget {
|
2015-05-07 03:27:45 +08:00
|
|
|
public:
|
|
|
|
ArrowNavigator(FileSelector* filesel)
|
|
|
|
: Widget(kGenericWidget)
|
|
|
|
, m_filesel(filesel) {
|
|
|
|
setVisible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
bool onProcessMessage(ui::Message* msg) override {
|
|
|
|
switch (msg->type()) {
|
|
|
|
case kOpenMessage:
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
manager()->addMessageFilter(kKeyDownMessage, this);
|
2015-05-07 03:27:45 +08:00
|
|
|
break;
|
|
|
|
case kCloseMessage:
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
manager()->removeMessageFilter(kKeyDownMessage, this);
|
2015-05-07 03:27:45 +08:00
|
|
|
break;
|
2015-05-07 03:47:43 +08:00
|
|
|
case kKeyDownMessage: {
|
|
|
|
KeyMessage* keyMsg = static_cast<KeyMessage*>(msg);
|
|
|
|
KeyScancode scancode = keyMsg->scancode();
|
|
|
|
|
|
|
|
#ifdef __APPLE__
|
2015-10-20 03:41:41 +08:00
|
|
|
int unicode = keyMsg->unicodeChar();
|
2015-05-07 03:47:43 +08:00
|
|
|
bool up = (msg->cmdPressed() && scancode == kKeyUp);
|
|
|
|
bool enter = (msg->cmdPressed() && scancode == kKeyDown);
|
2016-11-25 05:47:51 +08:00
|
|
|
bool back = (msg->cmdPressed() && (unicode == '[' || scancode == kKeyOpenbrace));
|
|
|
|
bool forward = (msg->cmdPressed() && (unicode == ']' || scancode == kKeyClosebrace));
|
2015-05-07 03:47:43 +08:00
|
|
|
#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
|
2022-05-04 02:11:47 +08:00
|
|
|
bool refresh = (scancode == kKeyF5 ||
|
|
|
|
(msg->ctrlPressed() && scancode == kKeyR) ||
|
|
|
|
(msg->cmdPressed() && scancode == kKeyR));
|
2015-05-07 03:47:43 +08:00
|
|
|
|
|
|
|
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;
|
2015-05-07 03:27:45 +08:00
|
|
|
}
|
2021-04-26 07:04:06 +08:00
|
|
|
if (refresh) {
|
|
|
|
m_filesel->refreshCurrentFolder();
|
|
|
|
}
|
2015-05-07 03:27:45 +08:00
|
|
|
return false;
|
2015-05-07 03:47:43 +08:00
|
|
|
}
|
2015-05-07 03:27:45 +08:00
|
|
|
}
|
|
|
|
return Widget::onProcessMessage(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
FileSelector* m_filesel;
|
|
|
|
};
|
|
|
|
|
2018-03-16 07:34:01 +08:00
|
|
|
FileSelector::FileSelector(FileSelectorType type)
|
2015-06-03 23:16:25 +08:00
|
|
|
: m_type(type)
|
2014-03-30 05:42:17 +08:00
|
|
|
, m_navigationLocked(false)
|
2012-06-16 10:37:59 +08:00
|
|
|
{
|
2015-05-07 03:27:45 +08:00
|
|
|
addChild(new ArrowNavigator(this));
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-06-03 23:16:25 +08:00
|
|
|
m_fileName = new CustomFileNameEntry;
|
|
|
|
m_fileName->setFocusMagnet(true);
|
|
|
|
fileNamePlaceholder()->addChild(m_fileName);
|
|
|
|
|
|
|
|
goBackButton()->setFocusStop(false);
|
|
|
|
goForwardButton()->setFocusStop(false);
|
|
|
|
goUpButton()->setFocusStop(false);
|
2021-04-26 07:04:06 +08:00
|
|
|
refreshButton()->setFocusStop(false);
|
2015-06-03 23:16:25 +08:00
|
|
|
newFolderButton()->setFocusStop(false);
|
2019-03-26 09:09:22 +08:00
|
|
|
viewType()->setFocusStop(false);
|
|
|
|
for (auto child : viewType()->children())
|
|
|
|
child->setFocusStop(false);
|
2015-06-03 23:16:25 +08:00
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
m_fileList = new FileList();
|
|
|
|
m_fileList->setId("fileview");
|
|
|
|
m_fileName->setAssociatedFileList(m_fileList);
|
2019-03-26 09:09:22 +08:00
|
|
|
m_fileList->setZoom(Preferences::instance().fileSelector.zoom());
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2016-01-06 03:37:52 +08:00
|
|
|
m_fileView = new FileListView();
|
|
|
|
m_fileView->attachToView(m_fileList);
|
|
|
|
m_fileView->setExpansive(true);
|
|
|
|
fileViewPlaceholder()->addChild(m_fileView);
|
|
|
|
|
2020-07-04 08:51:46 +08:00
|
|
|
goBackButton()->Click.connect([this]{ onGoBack(); });
|
|
|
|
goForwardButton()->Click.connect([this]{ onGoForward(); });
|
|
|
|
goUpButton()->Click.connect([this]{ onGoUp(); });
|
2021-04-26 07:04:06 +08:00
|
|
|
refreshButton()->Click.connect([this] { onRefreshFolder(); });
|
2020-07-04 08:51:46 +08:00
|
|
|
newFolderButton()->Click.connect([this]{ onNewFolder(); });
|
|
|
|
viewType()->ItemChange.connect([this]{ onChangeViewType(); });
|
|
|
|
location()->CloseListBox.connect([this]{ onLocationCloseListBox(); });
|
|
|
|
fileType()->Change.connect([this]{ onFileTypeChange(); });
|
|
|
|
m_fileList->FileSelected.connect([this]{ onFileListFileSelected(); });
|
|
|
|
m_fileList->FileAccepted.connect([this]{ onFileListFileAccepted(); });
|
|
|
|
m_fileList->CurrentFolderChanged.connect([this]{ onFileListCurrentFolderChanged(); });
|
2016-09-17 12:01:28 +08:00
|
|
|
}
|
|
|
|
|
2018-02-23 21:32:02 +08:00
|
|
|
void FileSelector::setDefaultExtension(const std::string& extension)
|
|
|
|
{
|
|
|
|
m_defExtension = extension;
|
|
|
|
}
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
FileSelector::~FileSelector()
|
|
|
|
{
|
2015-05-07 03:27:45 +08:00
|
|
|
}
|
2014-09-10 10:42:47 +08:00
|
|
|
|
2015-05-07 03:27:45 +08:00
|
|
|
void FileSelector::goBack()
|
|
|
|
{
|
|
|
|
onGoBack();
|
2014-09-10 10:42:47 +08:00
|
|
|
}
|
|
|
|
|
2015-05-07 03:27:45 +08:00
|
|
|
void FileSelector::goForward()
|
2014-09-10 10:42:47 +08:00
|
|
|
{
|
2015-05-07 03:27:45 +08:00
|
|
|
onGoForward();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::goUp()
|
|
|
|
{
|
|
|
|
onGoUp();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::goInsideFolder()
|
|
|
|
{
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_fileList->selectedFileItem() &&
|
|
|
|
m_fileList->selectedFileItem()->isBrowsable()) {
|
2015-05-07 03:27:45 +08:00
|
|
|
m_fileList->setCurrentFolder(
|
2017-04-08 11:06:25 +08:00
|
|
|
m_fileList->selectedFileItem());
|
2015-05-07 03:27:45 +08:00
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
2021-04-26 07:04:06 +08:00
|
|
|
void FileSelector::refreshCurrentFolder()
|
|
|
|
{
|
|
|
|
onRefreshFolder();
|
|
|
|
}
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
bool FileSelector::show(
|
2015-05-29 03:11:06 +08:00
|
|
|
const std::string& title,
|
2014-04-21 06:53:27 +08:00
|
|
|
const std::string& initialPath,
|
2018-02-21 21:39:30 +08:00
|
|
|
const base::paths& allExtensions,
|
|
|
|
base::paths& output)
|
2012-06-16 10:37:59 +08:00
|
|
|
{
|
2014-04-24 19:51:59 +08:00
|
|
|
FileSystemModule* fs = FileSystemModule::instance();
|
|
|
|
LockFS lock(fs);
|
|
|
|
|
2019-11-15 21:44:43 +08:00
|
|
|
// 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);
|
|
|
|
|
2014-04-24 19:51:59 +08:00
|
|
|
fs->refresh();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// we have to find where the user should begin to browse files (start_folder)
|
2014-04-21 06:53:27 +08:00
|
|
|
std::string start_folder_path;
|
2019-11-15 02:36:56 +08:00
|
|
|
IFileItem* start_folder = nullptr;
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// If initialPath doesn't contain a path.
|
|
|
|
if (base::get_file_path(initialPath).empty()) {
|
|
|
|
// Get the saved `path' in the configuration file.
|
2019-03-26 09:09:22 +08:00
|
|
|
std::string path = Preferences::instance().fileSelector.currentFolder();
|
2014-10-20 12:20:08 +08:00
|
|
|
if (path == "<empty>") {
|
2014-09-22 03:49:59 +08:00
|
|
|
start_folder_path = base::get_user_docs_folder();
|
|
|
|
path = base::join_path(start_folder_path, initialPath);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
2014-09-22 03:49:59 +08:00
|
|
|
start_folder = fs->getFileItemFromPath(path);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Remove the filename.
|
|
|
|
start_folder_path = base::join_path(base::get_file_path(initialPath), "");
|
|
|
|
}
|
|
|
|
start_folder_path = base::fix_path_separators(start_folder_path);
|
|
|
|
|
|
|
|
if (!start_folder)
|
2014-04-24 19:51:59 +08:00
|
|
|
start_folder = fs->getFileItemFromPath(start_folder_path);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-11-15 21:44:43 +08:00
|
|
|
FILESEL_TRACE("FILESEL: Start folder '%s' (%p)\n", start_folder_path.c_str(), start_folder);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2021-04-22 04:01:43 +08:00
|
|
|
{
|
|
|
|
const gfx::Size workareaSize = ui::Manager::getDefault()->display()->workareaSizeUIScale();
|
|
|
|
setMinSize(workareaSize*9/10);
|
|
|
|
}
|
|
|
|
|
2013-01-11 23:43:25 +08:00
|
|
|
remapWindow();
|
|
|
|
centerWindow();
|
2021-08-19 22:51:21 +08:00
|
|
|
load_window_pos(this, kConfigSection);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
// Change the file formats/extensions to be shown
|
|
|
|
std::string initialExtension = base::get_file_extension(initialPath);
|
2018-02-21 21:39:30 +08:00
|
|
|
base::paths exts;
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type == FileSelectorType::Open ||
|
|
|
|
m_type == FileSelectorType::OpenMultiple) {
|
2018-02-21 21:39:30 +08:00
|
|
|
std::string k = merge_paths(allExtensions);
|
|
|
|
auto it = preferred_open_extensions.find(k);
|
2015-05-29 05:59:12 +08:00
|
|
|
if (it == preferred_open_extensions.end())
|
2018-02-21 21:39:30 +08:00
|
|
|
exts = allExtensions;
|
2015-05-29 05:59:12 +08:00
|
|
|
else
|
2018-02-21 21:39:30 +08:00
|
|
|
exts = preferred_open_extensions[k];
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
else {
|
2015-06-03 22:58:49 +08:00
|
|
|
ASSERT(m_type == FileSelectorType::Save);
|
2015-05-29 05:59:12 +08:00
|
|
|
if (!initialExtension.empty())
|
2018-02-21 21:39:30 +08:00
|
|
|
exts = base::paths{ initialExtension };
|
|
|
|
else
|
|
|
|
exts = allExtensions;
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
2017-04-08 11:06:25 +08:00
|
|
|
m_fileList->setMultipleSelection(m_type == FileSelectorType::OpenMultiple);
|
2018-02-21 21:39:30 +08:00
|
|
|
m_fileList->setExtensions(exts);
|
2012-07-18 09:12:41 +08:00
|
|
|
if (start_folder)
|
|
|
|
m_fileList->setCurrentFolder(start_folder);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// current location
|
|
|
|
navigation_position.reset();
|
2017-04-08 11:06:25 +08:00
|
|
|
addInNavigationHistory(m_fileList->currentFolder());
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// fill the location combo-box
|
|
|
|
updateLocation();
|
|
|
|
updateNavigationButtons();
|
|
|
|
|
|
|
|
// fill file-type combo-box
|
2019-05-07 21:28:37 +08:00
|
|
|
fileType()->deleteAllItems();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
// Get the default extension from the given initial file name
|
2018-02-23 21:32:02 +08:00
|
|
|
if (m_defExtension.empty())
|
|
|
|
m_defExtension = initialExtension;
|
2015-05-29 05:59:12 +08:00
|
|
|
|
|
|
|
// File type for all formats
|
2018-02-21 21:39:30 +08:00
|
|
|
fileType()->addItem(
|
2022-01-03 16:07:47 +08:00
|
|
|
new CustomFileExtensionItem(Strings::file_selector_all_formats(),
|
|
|
|
allExtensions));
|
2018-02-21 21:39:30 +08:00
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
// One file type for each supported image format
|
2018-02-21 21:39:30 +08:00
|
|
|
for (const auto& e : allExtensions) {
|
2015-05-29 05:59:12 +08:00
|
|
|
// If the default extension is empty, use the first filter
|
|
|
|
if (m_defExtension.empty())
|
2018-02-21 21:39:30 +08:00
|
|
|
m_defExtension = e;
|
2015-05-29 05:59:12 +08:00
|
|
|
|
2018-02-21 21:39:30 +08:00
|
|
|
fileType()->addItem(
|
|
|
|
new CustomFileExtensionItem(e + " files",
|
|
|
|
base::paths{ e }));
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
// All files
|
2018-02-21 21:39:30 +08:00
|
|
|
fileType()->addItem(
|
2022-01-03 16:07:47 +08:00
|
|
|
new CustomFileExtensionItem(Strings::file_selector_all_files(),
|
2018-02-21 21:39:30 +08:00
|
|
|
base::paths())); // Empty extensions means "*.*"
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// file name entry field
|
2015-05-05 01:58:24 +08:00
|
|
|
m_fileName->setValue(base::get_file_name(initialPath).c_str());
|
|
|
|
m_fileName->getEntryWidget()->selectText(0, -1);
|
2018-02-21 21:39:30 +08:00
|
|
|
|
|
|
|
for (Widget* wItem : *fileType()) {
|
|
|
|
auto item = dynamic_cast<CustomFileExtensionItem*>(wItem);
|
|
|
|
ASSERT(item);
|
|
|
|
if (item && item->extensions() == exts) {
|
|
|
|
fileType()->setSelectedItem(item);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
// TODO this loop needs a complete refactor
|
|
|
|
// Open the window and run... the user press ok?
|
2012-06-16 10:37:59 +08:00
|
|
|
again:
|
2012-07-09 10:24:42 +08:00
|
|
|
openWindowInForeground();
|
2015-12-05 01:54:15 +08:00
|
|
|
if (closer() == ok ||
|
|
|
|
closer() == m_fileList) {
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* folder = m_fileList->currentFolder();
|
2012-06-16 10:37:59 +08:00
|
|
|
ASSERT(folder);
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
// File name in the text entry field/combobox
|
2015-05-05 01:58:24 +08:00
|
|
|
std::string fn = m_fileName->getValue();
|
2014-04-21 06:53:27 +08:00
|
|
|
std::string buf;
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* enter_folder = nullptr;
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// up a level?
|
|
|
|
if (fn == "..") {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
enter_folder = folder->parent();
|
2012-06-16 10:37:59 +08:00
|
|
|
if (!enter_folder)
|
|
|
|
enter_folder = folder;
|
|
|
|
}
|
2014-09-10 10:42:47 +08:00
|
|
|
else if (fn.empty()) {
|
2019-03-27 01:22:48 +08:00
|
|
|
IFileItem* selected = m_fileList->selectedFileItem();
|
|
|
|
if (selected && selected->isBrowsable())
|
|
|
|
enter_folder = selected;
|
|
|
|
else if (m_type != FileSelectorType::OpenMultiple ||
|
|
|
|
m_fileList->selectedFileItems().empty()) {
|
2017-04-08 11:06:25 +08:00
|
|
|
// Show the window again
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
2014-09-10 10:42:47 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-06-16 10:37:59 +08:00
|
|
|
// check if the user specified in "fn" a item of "fileview"
|
2017-04-08 11:06:25 +08:00
|
|
|
const FileItemList& children = m_fileList->fileList();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2014-04-21 06:53:27 +08:00
|
|
|
std::string fn2 = fn;
|
2015-02-12 23:46:56 +08:00
|
|
|
#ifdef _WIN32
|
2012-06-16 10:37:59 +08:00
|
|
|
fn2 = base::string_to_lower(fn2);
|
|
|
|
#endif
|
|
|
|
|
2014-09-10 10:42:47 +08:00
|
|
|
for (IFileItem* child : children) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string child_name = child->displayName();
|
2015-02-12 23:46:56 +08:00
|
|
|
#ifdef _WIN32
|
2012-06-16 10:37:59 +08:00
|
|
|
child_name = base::string_to_lower(child_name);
|
|
|
|
#endif
|
|
|
|
if (child_name == fn2) {
|
2014-09-10 10:42:47 +08:00
|
|
|
enter_folder = child;
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
buf = enter_folder->fileName();
|
2012-06-16 10:37:59 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!enter_folder) {
|
|
|
|
// does the file-name entry have separators?
|
|
|
|
if (base::is_path_separator(*fn.begin())) { // absolute path (UNIX style)
|
2015-02-12 23:46:56 +08:00
|
|
|
#ifdef _WIN32
|
2012-06-16 10:37:59 +08:00
|
|
|
// get the drive of the current folder
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string drive = folder->fileName();
|
2012-06-16 10:37:59 +08:00
|
|
|
if (drive.size() >= 2 && drive[1] == ':') {
|
|
|
|
buf += drive[0];
|
|
|
|
buf += ':';
|
|
|
|
buf += fn;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
buf = base::join_path("C:", fn);
|
|
|
|
#else
|
|
|
|
buf = fn;
|
|
|
|
#endif
|
|
|
|
}
|
2015-02-12 23:46:56 +08:00
|
|
|
#ifdef _WIN32
|
2012-06-16 10:37:59 +08:00
|
|
|
// does the file-name entry have colon?
|
2014-04-21 06:53:27 +08:00
|
|
|
else if (fn.find(':') != std::string::npos) { // absolute path on Windows
|
2012-06-16 10:37:59 +08:00
|
|
|
if (fn.size() == 2 && fn[1] == ':') {
|
|
|
|
buf = base::join_path(fn, "");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
buf = fn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
buf = folder->fileName();
|
2012-06-16 10:37:59 +08:00
|
|
|
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
|
2014-04-24 19:51:59 +08:00
|
|
|
enter_folder = fs->getFileItemFromPath(buf);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2015-05-05 01:58:24 +08:00
|
|
|
m_fileName->setValue("");
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// show the window again
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
// else file-name specified in the entry is really a file to open...
|
|
|
|
|
2017-10-07 00:52:26 +08:00
|
|
|
#ifdef _WIN32
|
2020-03-11 20:51:32 +08:00
|
|
|
// 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.
|
2020-02-10 00:42:46 +08:00
|
|
|
if (!enter_folder) {
|
2020-03-11 20:51:32 +08:00
|
|
|
const bool has_invalid_char =
|
|
|
|
(fn.find(':') != std::string::npos ||
|
2020-02-10 00:42:46 +08:00
|
|
|
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) {
|
2020-03-11 23:26:58 +08:00
|
|
|
const char* invalid_chars = ": * ? \" < > |";
|
2020-02-10 00:42:46 +08:00
|
|
|
|
|
|
|
ui::Alert::show(
|
|
|
|
fmt::format(
|
|
|
|
Strings::alerts_invalid_chars_in_filename(),
|
|
|
|
invalid_chars));
|
|
|
|
|
|
|
|
// show the window again
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
2017-10-07 00:52:26 +08:00
|
|
|
}
|
2020-02-10 00:42:46 +08:00
|
|
|
#endif
|
2017-10-07 00:52:26 +08:00
|
|
|
|
2020-08-04 04:31:14 +08:00
|
|
|
// Does it not have extension? ...we should add the extension
|
2012-06-16 10:37:59 +08:00
|
|
|
// selected in the filetype combo-box
|
2020-08-04 04:31:14 +08:00
|
|
|
if (m_type == FileSelectorType::Save &&
|
|
|
|
!buf.empty() && base::get_file_extension(buf).empty()) {
|
2012-06-16 10:37:59 +08:00
|
|
|
buf += '.';
|
2015-05-29 05:59:12 +08:00
|
|
|
buf += getSelectedExtension();
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
2015-06-03 22:58:49 +08:00
|
|
|
if (m_type == FileSelectorType::Save && base::is_file(buf)) {
|
2017-10-18 05:00:45 +08:00
|
|
|
int ret = Alert::show(
|
|
|
|
fmt::format(
|
|
|
|
Strings::alerts_overwrite_existent_file(),
|
|
|
|
base::get_file_name(buf)));
|
2015-05-29 03:11:06 +08:00
|
|
|
if (ret == 2) {
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
else if (ret == 1) {
|
|
|
|
// Check for read-only attribute
|
|
|
|
if (base::has_readonly_attr(buf)) {
|
2017-10-18 05:00:45 +08:00
|
|
|
ui::Alert::show(Strings::alerts_cannot_save_in_read_only_file());
|
2015-05-29 03:11:06 +08:00
|
|
|
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Cancel
|
|
|
|
else if (ret != 1) {
|
2017-04-08 11:06:25 +08:00
|
|
|
return false;
|
2015-05-29 03:11:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
// 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());
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// save the path in the configuration file
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string lastpath = folder->keyName();
|
2019-03-26 09:09:22 +08:00
|
|
|
Preferences::instance().fileSelector.currentFolder(lastpath);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
2019-03-26 09:09:22 +08:00
|
|
|
Preferences::instance().fileSelector.zoom(m_fileList->zoom());
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
return (!output.empty());
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
2021-08-19 22:51:21 +08:00
|
|
|
bool FileSelector::onProcessMessage(ui::Message* msg)
|
|
|
|
{
|
|
|
|
switch (msg->type()) {
|
|
|
|
case kCloseMessage:
|
|
|
|
save_window_pos(this, kConfigSection);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return app::gen::FileSelector::onProcessMessage(msg);
|
|
|
|
}
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
// Updates the content of the combo-box that shows the current
|
|
|
|
// location in the file-system.
|
|
|
|
void FileSelector::updateLocation()
|
|
|
|
{
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* currentFolder = m_fileList->currentFolder();
|
2012-06-16 10:37:59 +08:00
|
|
|
IFileItem* fileItem = currentFolder;
|
2012-07-18 08:42:02 +08:00
|
|
|
std::list<IFileItem*> locations;
|
2012-06-16 10:37:59 +08:00
|
|
|
int selected_index = -1;
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
while (fileItem) {
|
2012-07-18 08:42:02 +08:00
|
|
|
locations.push_front(fileItem);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
fileItem = fileItem->parent();
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear all the items from the combo-box
|
2019-05-07 21:28:37 +08:00
|
|
|
location()->deleteAllItems();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Add item by item (from root to the specific current folder)
|
|
|
|
int level = 0;
|
2017-04-08 11:06:25 +08:00
|
|
|
for (auto it=locations.begin(), end=locations.end(); it!=end; ++it) {
|
2012-07-18 08:42:02 +08:00
|
|
|
fileItem = *it;
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Indentation
|
2014-04-21 06:53:27 +08:00
|
|
|
std::string buf;
|
2012-06-16 10:37:59 +08:00
|
|
|
for (int c=0; c<level; ++c)
|
|
|
|
buf += " ";
|
|
|
|
|
|
|
|
// Location name
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
buf += fileItem->displayName();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Add the new location to the combo-box
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->addItem(new CustomFileNameItem(buf.c_str(), fileItem));
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
if (fileItem == currentFolder)
|
|
|
|
selected_index = level;
|
|
|
|
|
|
|
|
level++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add paths from recent files list
|
2018-12-22 13:08:04 +08:00
|
|
|
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()));
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Select the location
|
|
|
|
{
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->setSelectedItemIndex(selected_index);
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
location()->getEntryWidget()->setText(currentFolder->displayName().c_str());
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->getEntryWidget()->deselectText();
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2019-11-15 02:36:56 +08:00
|
|
|
goBackButton()->setEnabled(
|
|
|
|
navigation_history.size() > 1 &&
|
|
|
|
(navigation_position.is_null() ||
|
|
|
|
navigation_position.get() != navigation_history.begin()));
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Update the state of the go forward button: if the
|
|
|
|
// navigation-history has two elements and the navigation-position
|
|
|
|
// isn't the last one.
|
2019-11-15 02:36:56 +08:00
|
|
|
goForwardButton()->setEnabled(
|
|
|
|
navigation_history.size() > 1 &&
|
2019-11-15 04:55:13 +08:00
|
|
|
navigation_position.is_valid() &&
|
|
|
|
navigation_position.get() != navigation_history.end()-1);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Update the state of the go up button: if the current-folder isn't
|
|
|
|
// the root-item.
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* currentFolder = m_fileList->currentFolder();
|
2015-06-03 23:16:25 +08:00
|
|
|
goUpButton()->setEnabled(currentFolder != FileSystemModule::instance()->getRootFileItem());
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::addInNavigationHistory(IFileItem* folder)
|
|
|
|
{
|
2019-11-15 02:36:56 +08:00
|
|
|
ASSERT(folder);
|
2012-06-16 10:37:59 +08:00
|
|
|
ASSERT(folder->isFolder());
|
|
|
|
|
|
|
|
// Remove the history from the current position
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_position.is_valid()) {
|
|
|
|
navigation_history.erase(navigation_position.get()+1,
|
|
|
|
navigation_history.end());
|
2012-06-16 10:37:59 +08:00
|
|
|
navigation_position.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the history is empty or if the last item isn't the folder that
|
|
|
|
// we are visiting...
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_history.empty() ||
|
|
|
|
navigation_history.back() != folder) {
|
2012-06-16 10:37:59 +08:00
|
|
|
// We can add the location in the history
|
2019-11-15 02:36:56 +08:00
|
|
|
navigation_history.push_back(folder);
|
|
|
|
navigation_position.set(navigation_history.end()-1);
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoBack()
|
|
|
|
{
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_history.size() > 1) {
|
2019-11-15 21:44:43 +08:00
|
|
|
// The default navigation position is at the end of the history
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_position.is_null())
|
|
|
|
navigation_position.set(navigation_history.end()-1);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_position.get() != navigation_history.begin()) {
|
2019-11-15 21:44:43 +08:00
|
|
|
// 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();
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoForward()
|
|
|
|
{
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_history.size() > 1) {
|
2019-11-15 21:44:43 +08:00
|
|
|
// 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);
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
if (navigation_position.get() != navigation_history.end()-1) {
|
2019-11-15 21:44:43 +08:00
|
|
|
// 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();
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoUp()
|
|
|
|
{
|
|
|
|
m_fileList->goUp();
|
|
|
|
}
|
|
|
|
|
2021-04-26 07:04:06 +08:00
|
|
|
void FileSelector::onRefreshFolder()
|
|
|
|
{
|
2022-05-04 02:46:01 +08:00
|
|
|
auto fs = FileSystemModule::instance();
|
|
|
|
fs->refresh();
|
|
|
|
|
2022-05-04 02:17:18 +08:00
|
|
|
m_fileList->setCurrentFolder(m_fileList->currentFolder());
|
2021-04-26 07:04:06 +08:00
|
|
|
}
|
|
|
|
|
2014-06-29 05:01:32 +08:00
|
|
|
void FileSelector::onNewFolder()
|
|
|
|
{
|
2015-06-03 23:20:44 +08:00
|
|
|
app::gen::NewFolderWindow window;
|
2014-06-29 05:01:32 +08:00
|
|
|
|
2015-06-03 23:20:44 +08:00
|
|
|
window.openWindowInForeground();
|
2015-12-05 01:54:15 +08:00
|
|
|
if (window.closer() == window.ok()) {
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* currentFolder = m_fileList->currentFolder();
|
2014-06-29 05:01:32 +08:00
|
|
|
if (currentFolder) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string dirname = window.name()->text();
|
2014-06-29 05:01:32 +08:00
|
|
|
|
|
|
|
// Create the new directory
|
|
|
|
try {
|
|
|
|
currentFolder->createDirectory(dirname);
|
|
|
|
|
|
|
|
// Enter in the new folder
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
for (auto child : currentFolder->children()) {
|
|
|
|
if (child->displayName() == dirname) {
|
|
|
|
m_fileList->setCurrentFolder(child);
|
2014-06-29 05:01:32 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
Console::showException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-26 09:09:22 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
// Hook for the 'location' combo-box
|
2014-03-30 05:42:17 +08:00
|
|
|
void FileSelector::onLocationCloseListBox()
|
2012-06-16 10:37:59 +08:00
|
|
|
{
|
|
|
|
// When the user change the location we have to set the
|
|
|
|
// current-folder in the 'fileview' widget
|
2015-06-03 23:16:25 +08:00
|
|
|
CustomFileNameItem* comboFileItem = dynamic_cast<CustomFileNameItem*>(location()->getSelectedItem());
|
2019-11-15 02:36:56 +08:00
|
|
|
IFileItem* fileItem = (comboFileItem ? comboFileItem->getFileItem(): nullptr);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Maybe the user selected a recent file path
|
2019-11-15 02:36:56 +08:00
|
|
|
if (fileItem == nullptr) {
|
2013-04-04 07:31:02 +08:00
|
|
|
CustomFolderNameItem* comboFolderItem =
|
2015-06-03 23:16:25 +08:00
|
|
|
dynamic_cast<CustomFolderNameItem*>(location()->getSelectedItem());
|
2013-04-04 07:31:02 +08:00
|
|
|
|
2019-11-15 02:36:56 +08:00
|
|
|
if (comboFolderItem) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string path = comboFolderItem->text();
|
2012-06-16 10:37:59 +08:00
|
|
|
fileItem = FileSystemModule::instance()->getFileItemFromPath(path);
|
2013-04-04 07:31:02 +08:00
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
2016-11-25 05:54:59 +08:00
|
|
|
if (fileItem) {
|
2012-06-16 10:37:59 +08:00
|
|
|
m_fileList->setCurrentFolder(fileItem);
|
|
|
|
|
|
|
|
// Refocus the 'fileview' (the focus in that widget is more
|
|
|
|
// useful for the user)
|
2016-11-25 05:54:59 +08:00
|
|
|
m_fileList->requestFocus();
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// When the user selects a new file-type (extension), we have to
|
|
|
|
// change the file-extension in the 'filename' entry widget
|
|
|
|
void FileSelector::onFileTypeChange()
|
|
|
|
{
|
2018-02-21 21:39:30 +08:00
|
|
|
base::paths exts;
|
|
|
|
auto* selExtItem = dynamic_cast<CustomFileExtensionItem*>(fileType()->getSelectedItem());
|
|
|
|
if (selExtItem)
|
|
|
|
exts = selExtItem->extensions();
|
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
if (exts != m_fileList->extensions()) {
|
|
|
|
m_navigationLocked = true;
|
2018-02-21 21:39:30 +08:00
|
|
|
m_fileList->setExtensions(exts);
|
2015-05-29 05:59:12 +08:00
|
|
|
m_navigationLocked = false;
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type == FileSelectorType::Open ||
|
|
|
|
m_type == FileSelectorType::OpenMultiple) {
|
2018-02-21 21:39:30 +08:00
|
|
|
const base::paths& allExtensions =
|
|
|
|
dynamic_cast<CustomFileExtensionItem*>(fileType()->getItem(0))->extensions();
|
|
|
|
std::string k = merge_paths(allExtensions);
|
|
|
|
preferred_open_extensions[k] = exts;
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2015-06-03 22:58:49 +08:00
|
|
|
if (m_type == FileSelectorType::Save) {
|
2015-05-29 05:59:12 +08:00
|
|
|
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());
|
|
|
|
}
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onFileListFileSelected()
|
|
|
|
{
|
2017-04-08 11:06:25 +08:00
|
|
|
IFileItem* fileitem = m_fileList->selectedFileItem();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2019-03-26 09:09:22 +08:00
|
|
|
if (fileitem && !fileitem->isFolder()) {
|
Refactor several "getNoun()" getters to "noun()"
This is a work-in-progress to create a consistent API and finally
separate the whole Aseprite base/gfx/ui libs into a reusable C++ library.
Classes:
app::IFileItem, app::AppMenuItem, app::skin::SkinPart,
gfx::Rect, gfx::Border, she::FileDialog,
ui::IButtonIcon, ui::Graphics, ui::Overlay, ui::Widget,
ui::ScrollableViewDelegate, and UI events
2015-12-05 01:39:04 +08:00
|
|
|
std::string filename = base::get_file_name(fileitem->fileName());
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type != FileSelectorType::OpenMultiple ||
|
|
|
|
m_fileList->selectedFileItems().size() == 1)
|
|
|
|
m_fileName->setValue(filename.c_str());
|
|
|
|
else
|
|
|
|
m_fileName->setValue(std::string());
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onFileListFileAccepted()
|
|
|
|
{
|
|
|
|
closeWindow(m_fileList);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onFileListCurrentFolderChanged()
|
|
|
|
{
|
2014-03-30 05:00:19 +08:00
|
|
|
if (!m_navigationLocked)
|
2017-04-08 11:06:25 +08:00
|
|
|
addInNavigationHistory(m_fileList->currentFolder());
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
updateLocation();
|
|
|
|
updateNavigationButtons();
|
2015-05-07 03:58:32 +08:00
|
|
|
|
|
|
|
// Close the autocomplete popup just in case it's open.
|
|
|
|
m_fileName->closeListBox();
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
std::string FileSelector::getSelectedExtension() const
|
|
|
|
{
|
2018-02-21 21:39:30 +08:00
|
|
|
auto selExtItem = dynamic_cast<CustomFileExtensionItem*>(fileType()->getSelectedItem());
|
|
|
|
if (selExtItem && selExtItem->extensions().size() == 1)
|
|
|
|
return selExtItem->extensions().front();
|
|
|
|
else
|
|
|
|
return m_defExtension;
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|