2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2017-02-14 05:34:23 +08:00
|
|
|
// Copyright (C) 2001-2017 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/ini_file.h"
|
|
|
|
#include "app/modules/gfx.h"
|
|
|
|
#include "app/modules/gui.h"
|
|
|
|
#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"
|
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"
|
|
|
|
#include "base/bind.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "base/bind.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"
|
2012-06-16 10:37:59 +08:00
|
|
|
#include "base/split_string.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "base/string.h"
|
2014-06-29 05:01:32 +08:00
|
|
|
#include "base/unique_ptr.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#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"
|
2016-09-17 12:01:28 +08:00
|
|
|
#include "file_selector_extras.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>
|
|
|
|
#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
|
|
|
|
|
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
|
|
|
|
|
|
|
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) { }
|
|
|
|
|
|
|
|
void reset() { m_isNull = true; }
|
|
|
|
|
|
|
|
bool isNull() const { return m_isNull; }
|
|
|
|
bool isValid() const { return !m_isNull; }
|
|
|
|
|
|
|
|
iterator getIterator() {
|
|
|
|
ASSERT(!m_isNull);
|
|
|
|
return m_iterator;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setIterator(const iterator& it) {
|
|
|
|
m_isNull = false;
|
|
|
|
m_iterator = it;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool m_isNull;
|
|
|
|
typename Container::iterator m_iterator;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Variables used only to maintain the history of navigation.
|
|
|
|
static FileItemList* navigation_history = NULL; // Set of FileItems navigated
|
|
|
|
static NullableIterator<FileItemList> navigation_position; // Current position in the navigation history
|
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
// This map acts like a temporal customization by the user when he/she
|
|
|
|
// wants to open files. The key (first) is the real "showExtensions"
|
|
|
|
// parameter given to the FileSelector::show() function. The value
|
|
|
|
// (second) is the selected extension by the user. It's used only in
|
|
|
|
// FileSelector::Open type of dialogs.
|
|
|
|
static std::map<std::string, std::string> preferred_open_extensions;
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
// Slot for App::Exit signal
|
|
|
|
static void on_exit_delete_navigation_history()
|
|
|
|
{
|
|
|
|
delete navigation_history;
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
2015-05-05 01:58:24 +08:00
|
|
|
removeAllItems();
|
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)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
class FileSelector::ExtrasWindow : public PopupWindow {
|
|
|
|
public:
|
|
|
|
ExtrasWindow(FileSelectorDelegate* delegate)
|
|
|
|
: PopupWindow("",
|
|
|
|
ClickBehavior::CloseOnClickInOtherWindow,
|
|
|
|
EnterBehavior::CloseOnEnter)
|
|
|
|
, m_delegate(delegate)
|
|
|
|
, m_extras(new gen::FileSelectorExtras) {
|
|
|
|
|
|
|
|
setAutoRemap(false);
|
|
|
|
setBorder(gfx::Border(4*guiscale()));
|
|
|
|
|
|
|
|
addChild(m_extras);
|
|
|
|
m_extras->resize()->setValue(
|
|
|
|
base::convert_to<std::string>(m_delegate->getResizeScale()));
|
|
|
|
m_delegate->fillLayersComboBox(m_extras->layers());
|
|
|
|
m_delegate->fillFramesComboBox(m_extras->frames());
|
2016-12-29 22:22:16 +08:00
|
|
|
m_extras->pixelRatio()->setSelected(m_delegate->applyPixelRatio());
|
2016-09-17 12:01:28 +08:00
|
|
|
|
|
|
|
m_extras->resize()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
|
|
|
m_extras->layers()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
|
|
|
m_extras->frames()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
2016-12-29 22:22:16 +08:00
|
|
|
m_extras->pixelRatio()->Click.connect(base::Bind<void>(&ExtrasWindow::onUpdateExtras, this));
|
2016-09-17 12:01:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string extrasLabel() const {
|
|
|
|
std::string label = "Resize: " + m_extras->resize()->getSelectedItem()->text();
|
|
|
|
|
2017-07-25 02:13:32 +08:00
|
|
|
auto layerItem = dynamic_cast<ListItem*>(m_extras->layers()->getSelectedItem());
|
|
|
|
if (layerItem &&
|
|
|
|
!layerItem->getValue().empty())
|
2016-09-17 12:01:28 +08:00
|
|
|
label += ", " + layerItem->text();
|
|
|
|
|
2017-07-25 02:13:32 +08:00
|
|
|
auto frameItem = dynamic_cast<ListItem*>(m_extras->frames()->getSelectedItem());
|
2016-09-17 12:01:28 +08:00
|
|
|
if (frameItem && !frameItem->getValue().empty())
|
|
|
|
label += ", " + frameItem->text();
|
|
|
|
|
2016-12-29 22:22:16 +08:00
|
|
|
if (m_extras->pixelRatio()->isSelected()) {
|
|
|
|
PixelRatio pr = m_delegate->pixelRatio();
|
|
|
|
label += " (";
|
|
|
|
label += base::convert_to<std::string>(pr.w);
|
|
|
|
label += ":";
|
|
|
|
label += base::convert_to<std::string>(pr.h);
|
|
|
|
label += ")";
|
|
|
|
}
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
double resizeValue() const {
|
|
|
|
return base::convert_to<double>(m_extras->resize()->getValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string layersValue() const {
|
|
|
|
return m_extras->layers()->getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string framesValue() const {
|
|
|
|
return m_extras->frames()->getValue();
|
|
|
|
}
|
|
|
|
|
2016-12-29 22:22:16 +08:00
|
|
|
bool applyPixelRatio() const {
|
|
|
|
return m_extras->pixelRatio()->isSelected();
|
|
|
|
}
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
obs::signal<void()> UpdateExtras;
|
|
|
|
|
|
|
|
private:
|
|
|
|
void onUpdateExtras() {
|
|
|
|
UpdateExtras();
|
|
|
|
}
|
|
|
|
|
|
|
|
FileSelectorDelegate* m_delegate;
|
|
|
|
gen::FileSelectorExtras* m_extras;
|
|
|
|
};
|
|
|
|
|
2015-06-04 03:34:27 +08:00
|
|
|
FileSelector::FileSelector(FileSelectorType type, FileSelectorDelegate* delegate)
|
2015-06-03 23:16:25 +08:00
|
|
|
: m_type(type)
|
2015-06-04 03:34:27 +08:00
|
|
|
, m_delegate(delegate)
|
2016-09-17 12:01:28 +08:00
|
|
|
, m_extras(nullptr)
|
2014-03-30 05:42:17 +08:00
|
|
|
, m_navigationLocked(false)
|
2012-06-16 10:37:59 +08:00
|
|
|
{
|
2015-06-04 03:34:27 +08:00
|
|
|
bool withResizeOptions = (delegate && delegate->hasResizeCombobox());
|
|
|
|
|
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);
|
|
|
|
newFolderButton()->setFocusStop(false);
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
m_fileList = new FileList();
|
|
|
|
m_fileList->setId("fileview");
|
|
|
|
m_fileName->setAssociatedFileList(m_fileList);
|
|
|
|
|
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);
|
|
|
|
|
2015-12-05 02:17:42 +08:00
|
|
|
goBackButton()->Click.connect(base::Bind<void>(&FileSelector::onGoBack, this));
|
|
|
|
goForwardButton()->Click.connect(base::Bind<void>(&FileSelector::onGoForward, this));
|
|
|
|
goUpButton()->Click.connect(base::Bind<void>(&FileSelector::onGoUp, this));
|
|
|
|
newFolderButton()->Click.connect(base::Bind<void>(&FileSelector::onNewFolder, this));
|
|
|
|
location()->CloseListBox.connect(base::Bind<void>(&FileSelector::onLocationCloseListBox, this));
|
|
|
|
fileType()->Change.connect(base::Bind<void>(&FileSelector::onFileTypeChange, this));
|
|
|
|
m_fileList->FileSelected.connect(base::Bind<void>(&FileSelector::onFileListFileSelected, this));
|
|
|
|
m_fileList->FileAccepted.connect(base::Bind<void>(&FileSelector::onFileListFileAccepted, this));
|
|
|
|
m_fileList->CurrentFolderChanged.connect(base::Bind<void>(&FileSelector::onFileListCurrentFolderChanged, this));
|
2015-06-04 03:34:27 +08:00
|
|
|
|
|
|
|
if (withResizeOptions) {
|
2016-09-17 12:01:28 +08:00
|
|
|
extraOptions()->Click.connect(base::Bind<void>(&FileSelector::onExtraOptions, this));
|
|
|
|
|
|
|
|
m_extras = new ExtrasWindow(m_delegate);
|
|
|
|
m_extras->UpdateExtras.connect(base::Bind<void>(&FileSelector::updateExtraLabel, this));
|
2015-06-04 03:34:27 +08:00
|
|
|
}
|
2016-09-17 12:01:28 +08:00
|
|
|
|
|
|
|
updateExtraLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
FileSelector::~FileSelector()
|
|
|
|
{
|
|
|
|
delete m_extras;
|
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
|
|
|
}
|
|
|
|
|
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,
|
2017-04-08 11:06:25 +08:00
|
|
|
const std::string& showExtensions,
|
|
|
|
FileSelectorFiles& output)
|
2012-06-16 10:37:59 +08:00
|
|
|
{
|
2014-04-24 19:51:59 +08:00
|
|
|
FileSystemModule* fs = FileSystemModule::instance();
|
|
|
|
LockFS lock(fs);
|
|
|
|
|
|
|
|
fs->refresh();
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
if (!navigation_history) {
|
|
|
|
navigation_history = new FileItemList();
|
|
|
|
App::instance()->Exit.connect(&on_exit_delete_navigation_history);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2012-06-16 10:37:59 +08:00
|
|
|
IFileItem* start_folder = NULL;
|
|
|
|
|
|
|
|
// If initialPath doesn't contain a path.
|
|
|
|
if (base::get_file_path(initialPath).empty()) {
|
|
|
|
// Get the saved `path' in the configuration file.
|
2014-10-20 12:20:08 +08:00
|
|
|
std::string path = get_config_string("FileSelect", "CurrentDirectory", "<empty>");
|
|
|
|
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
|
|
|
|
2016-10-27 23:25:33 +08:00
|
|
|
TRACE("FILESEL: Start folder '%s' (%p)\n", start_folder_path.c_str(), start_folder);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2014-09-01 01:17:49 +08:00
|
|
|
setMinSize(gfx::Size(ui::display_w()*9/10, ui::display_h()*9/10));
|
2013-01-11 23:43:25 +08:00
|
|
|
remapWindow();
|
|
|
|
centerWindow();
|
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);
|
|
|
|
std::string exts = showExtensions;
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type == FileSelectorType::Open ||
|
|
|
|
m_type == FileSelectorType::OpenMultiple) {
|
2015-05-29 05:59:12 +08:00
|
|
|
auto it = preferred_open_extensions.find(exts);
|
|
|
|
if (it == preferred_open_extensions.end())
|
|
|
|
exts = showExtensions;
|
|
|
|
else
|
|
|
|
exts = preferred_open_extensions[exts];
|
|
|
|
}
|
|
|
|
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())
|
|
|
|
exts = initialExtension;
|
|
|
|
}
|
2017-04-08 11:06:25 +08:00
|
|
|
m_fileList->setMultipleSelection(m_type == FileSelectorType::OpenMultiple);
|
2015-05-29 05:59:12 +08:00
|
|
|
m_fileList->setExtensions(exts.c_str());
|
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
|
2015-06-03 23:16:25 +08:00
|
|
|
fileType()->removeAllItems();
|
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
|
|
|
|
m_defExtension = initialExtension;
|
|
|
|
|
|
|
|
// File type for all formats
|
|
|
|
{
|
|
|
|
ListItem* item = new ListItem("All formats");
|
|
|
|
item->setValue(showExtensions);
|
2015-06-03 23:16:25 +08:00
|
|
|
fileType()->addItem(item);
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
// One file type for each supported image format
|
2014-04-21 06:53:27 +08:00
|
|
|
std::vector<std::string> tokens;
|
2012-06-16 10:37:59 +08:00
|
|
|
base::split_string(showExtensions, tokens, ",");
|
2015-05-29 05:59:12 +08:00
|
|
|
for (const auto& tok : tokens) {
|
|
|
|
// If the default extension is empty, use the first filter
|
|
|
|
if (m_defExtension.empty())
|
|
|
|
m_defExtension = tok;
|
|
|
|
|
|
|
|
ListItem* item = new ListItem(tok + " files");
|
|
|
|
item->setValue(tok);
|
2015-06-03 23:16:25 +08:00
|
|
|
fileType()->addItem(item);
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
|
|
|
// All files
|
|
|
|
{
|
|
|
|
ListItem* item = new ListItem("All files");
|
|
|
|
item->setValue(""); // Empty extensions means "*.*"
|
2015-06-03 23:16:25 +08:00
|
|
|
fileType()->addItem(item);
|
2015-05-29 05:59:12 +08:00
|
|
|
}
|
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);
|
2015-06-03 23:16:25 +08:00
|
|
|
fileType()->setValue(exts);
|
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()) {
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type != FileSelectorType::OpenMultiple) {
|
|
|
|
// 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
|
|
|
// check if the filename doesn't contain slashes or other ilegal characters...
|
|
|
|
bool has_invalid_char = (fn.find('/') != std::string::npos);
|
|
|
|
#ifdef _WIN32
|
|
|
|
has_invalid_char =
|
|
|
|
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 ||
|
|
|
|
fn.find('|') != std::string::npos);
|
|
|
|
#endif
|
|
|
|
if (has_invalid_char) {
|
2017-10-18 05:00:45 +08:00
|
|
|
const char* invalid_chars =
|
|
|
|
"/"
|
2017-10-07 00:52:26 +08:00
|
|
|
#ifdef _WIN32
|
2017-10-18 05:00:45 +08:00
|
|
|
" \\ : * ? \" < > |"
|
2017-10-07 00:52:26 +08:00
|
|
|
#endif
|
2017-10-18 05:00:45 +08:00
|
|
|
;
|
|
|
|
|
|
|
|
ui::Alert::show(
|
|
|
|
fmt::format(
|
|
|
|
Strings::alerts_invalid_chars_in_filename(),
|
|
|
|
invalid_chars));
|
2017-10-07 00:52:26 +08:00
|
|
|
|
|
|
|
// show the window again
|
|
|
|
setVisible(true);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
2012-06-16 10:37:59 +08:00
|
|
|
// does it not have extension? ...we should add the extension
|
|
|
|
// selected in the filetype combo-box
|
2017-04-08 11:06:25 +08:00
|
|
|
if (!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();
|
2012-06-16 10:37:59 +08:00
|
|
|
set_config_string("FileSelect", "CurrentDirectory",
|
|
|
|
lastpath.c_str());
|
2015-06-04 03:34:27 +08:00
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
if (m_delegate &&
|
|
|
|
m_delegate->hasResizeCombobox() &&
|
|
|
|
m_extras) {
|
|
|
|
m_delegate->setResizeScale(m_extras->resizeValue());
|
|
|
|
m_delegate->setLayers(m_extras->layersValue());
|
|
|
|
m_delegate->setFrames(m_extras->framesValue());
|
2016-12-29 22:22:16 +08:00
|
|
|
m_delegate->setApplyPixelRatio(m_extras->applyPixelRatio());
|
2015-06-04 03:34:27 +08:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->removeAllItems();
|
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
|
|
|
|
{
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->addItem("");
|
|
|
|
location()->addItem("-------- Recent Paths --------");
|
2012-06-16 10:37:59 +08:00
|
|
|
|
2016-04-23 00:19:06 +08:00
|
|
|
auto it = App::instance()->recentFiles()->paths_begin();
|
|
|
|
auto end = App::instance()->recentFiles()->paths_end();
|
2012-06-16 10:37:59 +08:00
|
|
|
for (; it != end; ++it)
|
2015-06-03 23:16:25 +08:00
|
|
|
location()->addItem(new CustomFolderNameItem(it->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.
|
2015-06-03 23:16:25 +08:00
|
|
|
goBackButton()->setEnabled(navigation_history->size() > 1 &&
|
|
|
|
(navigation_position.isNull() ||
|
|
|
|
navigation_position.getIterator() != 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.
|
2015-06-03 23:16:25 +08:00
|
|
|
goForwardButton()->setEnabled(navigation_history->size() > 1 &&
|
|
|
|
(navigation_position.isNull() ||
|
|
|
|
navigation_position.getIterator() != 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)
|
|
|
|
{
|
|
|
|
ASSERT(folder != NULL);
|
|
|
|
ASSERT(folder->isFolder());
|
|
|
|
|
|
|
|
// Remove the history from the current position
|
|
|
|
if (navigation_position.isValid()) {
|
|
|
|
navigation_history->erase(navigation_position.getIterator()+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.setIterator(navigation_history->end()-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoBack()
|
|
|
|
{
|
|
|
|
if (navigation_history->size() > 1) {
|
|
|
|
if (navigation_position.isNull())
|
|
|
|
navigation_position.setIterator(navigation_history->end()-1);
|
|
|
|
|
|
|
|
if (navigation_position.getIterator() != navigation_history->begin()) {
|
|
|
|
navigation_position.setIterator(navigation_position.getIterator()-1);
|
|
|
|
|
2014-03-30 05:00:19 +08:00
|
|
|
m_navigationLocked = true;
|
2012-06-16 10:37:59 +08:00
|
|
|
m_fileList->setCurrentFolder(*navigation_position.getIterator());
|
2014-03-30 05:00:19 +08:00
|
|
|
m_navigationLocked = false;
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoForward()
|
|
|
|
{
|
2012-07-18 08:42:02 +08:00
|
|
|
if (navigation_history->size() > 1) {
|
2012-06-16 10:37:59 +08:00
|
|
|
if (navigation_position.isNull())
|
|
|
|
navigation_position.setIterator(navigation_history->begin());
|
|
|
|
|
|
|
|
if (navigation_position.getIterator() != navigation_history->end()-1) {
|
|
|
|
navigation_position.setIterator(navigation_position.getIterator()+1);
|
|
|
|
|
2014-03-30 05:00:19 +08:00
|
|
|
m_navigationLocked = true;
|
2012-06-16 10:37:59 +08:00
|
|
|
m_fileList->setCurrentFolder(*navigation_position.getIterator());
|
2014-03-30 05:00:19 +08:00
|
|
|
m_navigationLocked = false;
|
2012-06-16 10:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FileSelector::onGoUp()
|
|
|
|
{
|
|
|
|
m_fileList->goUp();
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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());
|
2013-04-04 07:31:02 +08:00
|
|
|
IFileItem* fileItem = (comboFileItem != NULL ? comboFileItem->getFileItem(): NULL);
|
2012-06-16 10:37:59 +08:00
|
|
|
|
|
|
|
// Maybe the user selected a recent file path
|
|
|
|
if (fileItem == NULL) {
|
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
|
|
|
|
|
|
|
if (comboFolderItem != NULL) {
|
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()
|
|
|
|
{
|
2015-06-03 23:16:25 +08:00
|
|
|
std::string exts = fileType()->getValue();
|
2015-05-29 05:59:12 +08:00
|
|
|
if (exts != m_fileList->extensions()) {
|
|
|
|
m_navigationLocked = true;
|
|
|
|
m_fileList->setExtensions(exts.c_str());
|
|
|
|
m_navigationLocked = false;
|
|
|
|
|
2017-04-08 11:06:25 +08:00
|
|
|
if (m_type == FileSelectorType::Open ||
|
|
|
|
m_type == FileSelectorType::OpenMultiple) {
|
2017-07-25 02:13:32 +08:00
|
|
|
std::string origShowExtensions =
|
|
|
|
dynamic_cast<ListItem*>(fileType()->getItem(0))->getValue();
|
2015-06-03 23:16:25 +08:00
|
|
|
preferred_open_extensions[origShowExtensions] = fileType()->getValue();
|
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
|
|
|
|
|
|
|
if (!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
|
|
|
}
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
void FileSelector::onExtraOptions()
|
|
|
|
{
|
|
|
|
ASSERT(m_extras);
|
|
|
|
|
|
|
|
m_extras->remapWindow();
|
|
|
|
gfx::Rect bounds = m_extras->bounds();
|
|
|
|
ui::fit_bounds(BOTTOM, extraOptions()->bounds(), bounds);
|
|
|
|
|
|
|
|
m_extras->moveWindow(bounds);
|
|
|
|
m_extras->openWindow();
|
|
|
|
}
|
|
|
|
|
2015-05-29 05:59:12 +08:00
|
|
|
std::string FileSelector::getSelectedExtension() const
|
|
|
|
{
|
2015-06-03 23:16:25 +08:00
|
|
|
std::string ext = fileType()->getValue();
|
2015-05-29 05:59:12 +08:00
|
|
|
if (ext.empty() || ext.find(',') != std::string::npos)
|
|
|
|
ext = m_defExtension;
|
|
|
|
return ext;
|
|
|
|
}
|
|
|
|
|
2016-09-17 12:01:28 +08:00
|
|
|
void FileSelector::updateExtraLabel()
|
|
|
|
{
|
|
|
|
if (!m_extras) {
|
|
|
|
extraOptions()->setVisible(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
extraOptions()->setVisible(true);
|
|
|
|
|
|
|
|
std::string newLabel = m_extras->extrasLabel();
|
|
|
|
if (extraOptions()->text() != newLabel) {
|
|
|
|
extraOptions()->setText(newLabel);
|
|
|
|
extraOptions()->window()->layout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|