FileDialog - continue development; drive list
|
@ -717,5 +717,10 @@ extern (C) int UIAppMain(string[] args) {
|
||||||
window.show();
|
window.show();
|
||||||
//window.windowCaption = "New Window Caption";
|
//window.windowCaption = "New Window Caption";
|
||||||
// run message loop
|
// run message loop
|
||||||
|
|
||||||
|
Log.i("HOME path: ", homePath);
|
||||||
|
Log.i("APPDATA path: ", appDataPath(".dlangui"));
|
||||||
|
Log.i("Root paths: ", getRootPaths);
|
||||||
|
|
||||||
return Platform.instance.enterMessageLoop();
|
return Platform.instance.enterMessageLoop();
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,253 @@
|
||||||
|
// Written in the D programming language.
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
This module contains cross-platform file access utilities
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Synopsis:
|
||||||
|
|
||||||
|
----
|
||||||
|
import dlangui.core.files;
|
||||||
|
----
|
||||||
|
|
||||||
|
Copyright: Vadim Lopatin, 2014
|
||||||
|
License: Boost License 1.0
|
||||||
|
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||||
|
*/
|
||||||
|
module dlangui.core.files;
|
||||||
|
|
||||||
|
import std.algorithm;
|
||||||
|
|
||||||
|
private import dlangui.core.logger;
|
||||||
|
private import std.process;
|
||||||
|
private import std.path;
|
||||||
|
private import std.file;
|
||||||
|
private import std.utf;
|
||||||
|
|
||||||
|
version (Windows) {
|
||||||
|
/// path delimiter (\ for windows, / for others)
|
||||||
|
immutable char PATH_DELIMITER = '\\';
|
||||||
|
} else {
|
||||||
|
/// path delimiter (\ for windows, / for others)
|
||||||
|
immutable char PATH_DELIMITER = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filesystem root entry / bookmark types
|
||||||
|
enum RootEntryType : uint {
|
||||||
|
/// filesystem root
|
||||||
|
ROOT,
|
||||||
|
/// current user home
|
||||||
|
HOME,
|
||||||
|
/// removable drive
|
||||||
|
REMOVABLE,
|
||||||
|
/// fixed drive
|
||||||
|
FIXED,
|
||||||
|
/// network
|
||||||
|
NETWORK,
|
||||||
|
/// cd rom
|
||||||
|
CDROM,
|
||||||
|
/// sd card
|
||||||
|
SDCARD,
|
||||||
|
/// custom bookmark
|
||||||
|
BOOKMARK,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filesystem root entry item
|
||||||
|
struct RootEntry {
|
||||||
|
private RootEntryType _type;
|
||||||
|
private string _path;
|
||||||
|
private dstring _display;
|
||||||
|
this(RootEntryType type, string path, dstring display = null) {
|
||||||
|
_type = type;
|
||||||
|
_path = path;
|
||||||
|
_display = display;
|
||||||
|
if (display is null) {
|
||||||
|
_display = toUTF32(baseName(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns type
|
||||||
|
@property RootEntryType type() { return _type; }
|
||||||
|
/// Returns path
|
||||||
|
@property string path() { return _path; }
|
||||||
|
/// Returns display label
|
||||||
|
@property dstring label() { return _display; }
|
||||||
|
/// Returns icon resource id
|
||||||
|
@property string icon() {
|
||||||
|
switch (type) {
|
||||||
|
case RootEntryType.NETWORK:
|
||||||
|
return "folder-network";
|
||||||
|
case RootEntryType.BOOKMARK:
|
||||||
|
return "folder-bookmark";
|
||||||
|
case RootEntryType.CDROM:
|
||||||
|
return "drive-optical";
|
||||||
|
case RootEntryType.FIXED:
|
||||||
|
return "drive-harddisk";
|
||||||
|
case RootEntryType.HOME:
|
||||||
|
return "user-home";
|
||||||
|
case RootEntryType.ROOT:
|
||||||
|
return "computer";
|
||||||
|
case RootEntryType.SDCARD:
|
||||||
|
return "media-flash-sd-mmc";
|
||||||
|
case RootEntryType.REMOVABLE:
|
||||||
|
return "device-removable-media";
|
||||||
|
default:
|
||||||
|
return "folder-blue";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns
|
||||||
|
@property RootEntry homeEntry() {
|
||||||
|
return RootEntry(RootEntryType.HOME, homePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns array of system root entries
|
||||||
|
@property RootEntry[] getRootPaths() {
|
||||||
|
RootEntry[] res;
|
||||||
|
res ~= RootEntry(RootEntryType.HOME, homePath);
|
||||||
|
version (posix) {
|
||||||
|
res ~= RootEntry(RootEntryType.ROOT, "/", "File System"d);
|
||||||
|
}
|
||||||
|
version (Windows) {
|
||||||
|
import win32.windows;
|
||||||
|
uint mask = GetLogicalDrives();
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
|
if (mask & (1 << i)) {
|
||||||
|
char letter = cast(char)('A' + i);
|
||||||
|
string path = "" ~ letter ~ ":\\";
|
||||||
|
dstring display = ""d ~ letter ~ ":"d;
|
||||||
|
// detect drive type
|
||||||
|
RootEntryType type;
|
||||||
|
uint wtype = GetDriveTypeA(("" ~ path).ptr);
|
||||||
|
//Log.d("Drive ", path, " type ", wtype);
|
||||||
|
switch (wtype) {
|
||||||
|
case DRIVE_REMOVABLE:
|
||||||
|
type = RootEntryType.REMOVABLE;
|
||||||
|
break;
|
||||||
|
case DRIVE_REMOTE:
|
||||||
|
type = RootEntryType.NETWORK;
|
||||||
|
break;
|
||||||
|
case DRIVE_CDROM:
|
||||||
|
type = RootEntryType.CDROM;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = RootEntryType.FIXED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res ~= RootEntry(type, path, display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if char ch is / or \ slash */
|
||||||
|
bool isPathDelimiter(char ch) {
|
||||||
|
return ch == '/' || ch == '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns current executable path only, including last path delimiter - removes executable name from result of std.file.thisExePath() */
|
||||||
|
@property string exePath() {
|
||||||
|
string path = thisExePath();
|
||||||
|
int lastSlash = 0;
|
||||||
|
for (int i = 0; i < path.length; i++)
|
||||||
|
if (path[i] == PATH_DELIMITER)
|
||||||
|
lastSlash = i;
|
||||||
|
return path[0 .. lastSlash + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns user's home directory
|
||||||
|
@property string homePath() {
|
||||||
|
string path;
|
||||||
|
version (Windows) {
|
||||||
|
path = environment.get("USERPROFILE");
|
||||||
|
if (path is null)
|
||||||
|
path = environment.get("HOME");
|
||||||
|
} else {
|
||||||
|
path = environment.get("HOME");
|
||||||
|
}
|
||||||
|
if (path is null)
|
||||||
|
path = "."; // fallback to current directory
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
Returns application data directory
|
||||||
|
|
||||||
|
On unix, it will return path to subdirectory in home directory - e.g. /home/user/.subdir if ".subdir" is passed as a paramter.
|
||||||
|
|
||||||
|
On windows, it will return path to subdir in APPDATA directory - e.g. C:\Users\User\AppData\Roaming\.subdir.
|
||||||
|
|
||||||
|
*/
|
||||||
|
string appDataPath(string subdir = null) {
|
||||||
|
string path;
|
||||||
|
version (Windows) {
|
||||||
|
path = environment.get("APPDATA");
|
||||||
|
}
|
||||||
|
if (path is null)
|
||||||
|
path = homePath;
|
||||||
|
if (subdir !is null) {
|
||||||
|
path ~= PATH_DELIMITER;
|
||||||
|
path ~= subdir;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts path delimiters to standard for platform inplace in buffer(e.g. / to \ on windows, \ to / on posix), returns buf
|
||||||
|
char[] convertPathDelimiters(char[] buf) {
|
||||||
|
foreach(ref ch; buf) {
|
||||||
|
version (Windows) {
|
||||||
|
if (ch == '/')
|
||||||
|
ch = '\\';
|
||||||
|
} else {
|
||||||
|
if (ch == '\\')
|
||||||
|
ch = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts path delimiters to standard for platform (e.g. / to \ on windows, \ to / on posix) */
|
||||||
|
string convertPathDelimiters(string src) {
|
||||||
|
char[] buf = src.dup;
|
||||||
|
return cast(string)convertPathDelimiters(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Appends file path parts with proper delimiters e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
|
||||||
|
string appendPath(string[] pathItems ...) {
|
||||||
|
char[] buf;
|
||||||
|
foreach (s; pathItems) {
|
||||||
|
if (buf.length && !isPathDelimiter(buf[$-1]))
|
||||||
|
buf ~= PATH_DELIMITER;
|
||||||
|
buf ~= s;
|
||||||
|
}
|
||||||
|
return convertPathDelimiters(buf).dup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Appends file path parts with proper delimiters (as well converts delimiters inside path to system) to buffer e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
|
||||||
|
char[] appendPath(char[] buf, string[] pathItems ...) {
|
||||||
|
foreach (s; pathItems) {
|
||||||
|
if (buf.length && !isPathDelimiter(buf[$-1]))
|
||||||
|
buf ~= PATH_DELIMITER;
|
||||||
|
buf ~= s;
|
||||||
|
}
|
||||||
|
return convertPathDelimiters(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Split path into elements, e.g. /home/user/dir1 -> ["home", "user", "dir1"], "c:\dir1\dir2" -> ["c:", "dir1", "dir2"] */
|
||||||
|
string[] splitPath(string path) {
|
||||||
|
string[] res;
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i <= path.length; i++) {
|
||||||
|
char ch = i < path.length ? path[i] : 0;
|
||||||
|
if (ch == '\\' || ch == '/' || ch == 0) {
|
||||||
|
if (start < i)
|
||||||
|
res ~= path[start .. i].dup;
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
|
@ -48,23 +48,54 @@ enum FileDialogFlag : uint {
|
||||||
Save = ConfirmOverwrite,
|
Save = ConfirmOverwrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// file open / save dialog
|
/// File open / save dialog
|
||||||
class FileDialog : Dialog {
|
class FileDialog : Dialog {
|
||||||
EditLine path;
|
protected EditLine path;
|
||||||
StringGridWidget list;
|
protected EditLine filename;
|
||||||
StringGridWidget places;
|
protected StringGridWidget list;
|
||||||
VerticalLayout leftPanel;
|
//protected StringGridWidget places;
|
||||||
VerticalLayout rightPanel;
|
protected VerticalLayout leftPanel;
|
||||||
|
protected VerticalLayout rightPanel;
|
||||||
|
|
||||||
|
protected RootEntry[] _roots;
|
||||||
|
|
||||||
this(UIString caption, Window parent, uint fileDialogFlags = DialogFlag.Modal | FileDialogFlag.FileMustExist) {
|
this(UIString caption, Window parent, uint fileDialogFlags = DialogFlag.Modal | FileDialogFlag.FileMustExist) {
|
||||||
super(caption, parent, fileDialogFlags);
|
super(caption, parent, fileDialogFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void rootEntrySelected(RootEntry entry) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Widget createRootsList() {
|
||||||
|
ListWidget list = new ListWidget("ROOTS_LIST");
|
||||||
|
WidgetListAdapter adapter = new WidgetListAdapter();
|
||||||
|
foreach(ref RootEntry root; _roots) {
|
||||||
|
ImageTextButton btn = new ImageTextButton(null, root.icon, root.label);
|
||||||
|
btn.orientation = Orientation.Vertical;
|
||||||
|
btn.styleId = "TRANSPARENT_BUTTON_BACKGROUND";
|
||||||
|
btn.focusable = false;
|
||||||
|
btn.onClickListener = delegate(Widget source) {
|
||||||
|
rootEntrySelected(root);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
adapter.widgets.add(btn);
|
||||||
|
}
|
||||||
|
list.ownAdapter = adapter;
|
||||||
|
list.layoutWidth = WRAP_CONTENT;
|
||||||
|
list.layoutHeight = FILL_PARENT;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/// override to implement creation of dialog controls
|
/// override to implement creation of dialog controls
|
||||||
override void init() {
|
override void init() {
|
||||||
|
_roots = getRootPaths;
|
||||||
layoutWidth(FILL_PARENT);
|
layoutWidth(FILL_PARENT);
|
||||||
layoutWidth(FILL_PARENT);
|
layoutWidth(FILL_PARENT);
|
||||||
LinearLayout content = new HorizontalLayout("dlgcontent");
|
LinearLayout content = new HorizontalLayout("dlgcontent");
|
||||||
content.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).minWidth(400).minHeight(300);
|
content.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).minWidth(400).minHeight(300);
|
||||||
leftPanel = new VerticalLayout("places");
|
leftPanel = new VerticalLayout("places");
|
||||||
|
leftPanel.addChild(createRootsList());
|
||||||
rightPanel = new VerticalLayout("main");
|
rightPanel = new VerticalLayout("main");
|
||||||
leftPanel.layoutHeight(FILL_PARENT).minWidth(40);
|
leftPanel.layoutHeight(FILL_PARENT).minWidth(40);
|
||||||
rightPanel.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT);
|
rightPanel.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT);
|
||||||
|
@ -73,6 +104,8 @@ class FileDialog : Dialog {
|
||||||
content.addChild(rightPanel);
|
content.addChild(rightPanel);
|
||||||
path = new EditLine("path");
|
path = new EditLine("path");
|
||||||
path.layoutWidth(FILL_PARENT);
|
path.layoutWidth(FILL_PARENT);
|
||||||
|
filename = new EditLine("path");
|
||||||
|
filename.layoutWidth(FILL_PARENT);
|
||||||
|
|
||||||
rightPanel.addChild(path);
|
rightPanel.addChild(path);
|
||||||
list = new StringGridWidget("files");
|
list = new StringGridWidget("files");
|
||||||
|
@ -84,12 +117,13 @@ class FileDialog : Dialog {
|
||||||
list.showRowHeaders = false;
|
list.showRowHeaders = false;
|
||||||
list.rowSelect = true;
|
list.rowSelect = true;
|
||||||
rightPanel.addChild(list);
|
rightPanel.addChild(list);
|
||||||
|
rightPanel.addChild(filename);
|
||||||
|
|
||||||
places = new StringGridWidget("placesList");
|
//places = new StringGridWidget("placesList");
|
||||||
places.resize(1, 10);
|
//places.resize(1, 10);
|
||||||
places.showRowHeaders(false).showColHeaders(true);
|
//places.showRowHeaders(false).showColHeaders(true);
|
||||||
places.setColTitle(0, "Places"d);
|
//places.setColTitle(0, "Places"d);
|
||||||
leftPanel.addChild(places);
|
//leftPanel.addChild(places);
|
||||||
|
|
||||||
addChild(content);
|
addChild(content);
|
||||||
addChild(createButtonsPanel([ACTION_OPEN, ACTION_CANCEL], 0, 0));
|
addChild(createButtonsPanel([ACTION_OPEN, ACTION_CANCEL], 0, 0));
|
||||||
|
|
|
@ -207,14 +207,40 @@ class ImageButton : ImageWidget {
|
||||||
class ImageTextButton : HorizontalLayout {
|
class ImageTextButton : HorizontalLayout {
|
||||||
protected ImageWidget _icon;
|
protected ImageWidget _icon;
|
||||||
protected TextWidget _label;
|
protected TextWidget _label;
|
||||||
|
|
||||||
|
/// Get label text
|
||||||
override @property dstring text() { return _label.text; }
|
override @property dstring text() { return _label.text; }
|
||||||
|
/// Set label plain unicode string
|
||||||
override @property Widget text(dstring s) { _label.text = s; requestLayout(); return this; }
|
override @property Widget text(dstring s) { _label.text = s; requestLayout(); return this; }
|
||||||
|
/// Set label string resource Id
|
||||||
override @property Widget text(UIString s) { _label.text = s; requestLayout(); return this; }
|
override @property Widget text(UIString s) { _label.text = s; requestLayout(); return this; }
|
||||||
this(string ID = null, string drawableId = null, string textResourceId = null) {
|
|
||||||
super(ID);
|
/// Returns orientation: Vertical - image top, Horizontal - image left"
|
||||||
|
override @property Orientation orientation() {
|
||||||
|
return super.orientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets orientation: Vertical - image top, Horizontal - image left"
|
||||||
|
override @property LinearLayout orientation(Orientation value) {
|
||||||
|
if (!_icon || !_label)
|
||||||
|
return super.orientation(value);
|
||||||
|
if (value != orientation) {
|
||||||
|
super.orientation(value);
|
||||||
|
if (value == Orientation.Horizontal) {
|
||||||
|
_icon.alignment = Align.Left | Align.VCenter;
|
||||||
|
_label.alignment = Align.Right | Align.VCenter;
|
||||||
|
} else {
|
||||||
|
_icon.alignment = Align.Top | Align.HCenter;
|
||||||
|
_label.alignment = Align.Bottom | Align.HCenter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(string drawableId, UIString caption) {
|
||||||
styleId = "BUTTON";
|
styleId = "BUTTON";
|
||||||
_icon = new ImageWidget("icon", drawableId);
|
_icon = new ImageWidget("icon", drawableId);
|
||||||
_label = new TextWidget("label", textResourceId);
|
_label = new TextWidget("label", caption);
|
||||||
_label.styleId = "BUTTON_LABEL";
|
_label.styleId = "BUTTON_LABEL";
|
||||||
_icon.state = State.Parent;
|
_icon.state = State.Parent;
|
||||||
_label.state = State.Parent;
|
_label.state = State.Parent;
|
||||||
|
@ -224,20 +250,17 @@ class ImageTextButton : HorizontalLayout {
|
||||||
focusable = true;
|
focusable = true;
|
||||||
trackHover = true;
|
trackHover = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this(string ID = null, string drawableId = null, string textResourceId = null) {
|
||||||
|
super(ID);
|
||||||
|
UIString caption = textResourceId;
|
||||||
|
init(drawableId, caption);
|
||||||
|
}
|
||||||
|
|
||||||
this(string ID, string drawableId, dstring rawText) {
|
this(string ID, string drawableId, dstring rawText) {
|
||||||
super(ID);
|
super(ID);
|
||||||
styleId = "BUTTON";
|
UIString caption = rawText;
|
||||||
_icon = new ImageWidget("icon", drawableId);
|
init(drawableId, caption);
|
||||||
_label = new TextWidget("label", rawText);
|
|
||||||
_label.styleId = "BUTTON_LABEL";
|
|
||||||
_icon.styleId = "BUTTON_ICON";
|
|
||||||
_icon.state = State.Parent;
|
|
||||||
_label.state = State.Parent;
|
|
||||||
addChild(_icon);
|
|
||||||
addChild(_label);
|
|
||||||
clickable = true;
|
|
||||||
focusable = true;
|
|
||||||
trackHover = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ interface ListAdapter {
|
||||||
|
|
||||||
/// List adapter for simple list of widget instances
|
/// List adapter for simple list of widget instances
|
||||||
class WidgetListAdapter : ListAdapter {
|
class WidgetListAdapter : ListAdapter {
|
||||||
WidgetList _widgets;
|
private WidgetList _widgets;
|
||||||
/// list of widgets to display
|
/// list of widgets to display
|
||||||
@property ref WidgetList widgets() { return _widgets; }
|
@property ref WidgetList widgets() { return _widgets; }
|
||||||
/// returns number of widgets in list
|
/// returns number of widgets in list
|
||||||
|
|