FileDialog - continue development; drive list
|
@ -717,5 +717,10 @@ extern (C) int UIAppMain(string[] args) {
|
|||
window.show();
|
||||
//window.windowCaption = "New Window Caption";
|
||||
// run message loop
|
||||
|
||||
Log.i("HOME path: ", homePath);
|
||||
Log.i("APPDATA path: ", appDataPath(".dlangui"));
|
||||
Log.i("Root paths: ", getRootPaths);
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
/// file open / save dialog
|
||||
/// File open / save dialog
|
||||
class FileDialog : Dialog {
|
||||
EditLine path;
|
||||
StringGridWidget list;
|
||||
StringGridWidget places;
|
||||
VerticalLayout leftPanel;
|
||||
VerticalLayout rightPanel;
|
||||
protected EditLine path;
|
||||
protected EditLine filename;
|
||||
protected StringGridWidget list;
|
||||
//protected StringGridWidget places;
|
||||
protected VerticalLayout leftPanel;
|
||||
protected VerticalLayout rightPanel;
|
||||
|
||||
protected RootEntry[] _roots;
|
||||
|
||||
this(UIString caption, Window parent, uint fileDialogFlags = DialogFlag.Modal | FileDialogFlag.FileMustExist) {
|
||||
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 void init() {
|
||||
_roots = getRootPaths;
|
||||
layoutWidth(FILL_PARENT);
|
||||
layoutWidth(FILL_PARENT);
|
||||
LinearLayout content = new HorizontalLayout("dlgcontent");
|
||||
content.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT).minWidth(400).minHeight(300);
|
||||
leftPanel = new VerticalLayout("places");
|
||||
leftPanel.addChild(createRootsList());
|
||||
rightPanel = new VerticalLayout("main");
|
||||
leftPanel.layoutHeight(FILL_PARENT).minWidth(40);
|
||||
rightPanel.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT);
|
||||
|
@ -73,6 +104,8 @@ class FileDialog : Dialog {
|
|||
content.addChild(rightPanel);
|
||||
path = new EditLine("path");
|
||||
path.layoutWidth(FILL_PARENT);
|
||||
filename = new EditLine("path");
|
||||
filename.layoutWidth(FILL_PARENT);
|
||||
|
||||
rightPanel.addChild(path);
|
||||
list = new StringGridWidget("files");
|
||||
|
@ -84,12 +117,13 @@ class FileDialog : Dialog {
|
|||
list.showRowHeaders = false;
|
||||
list.rowSelect = true;
|
||||
rightPanel.addChild(list);
|
||||
rightPanel.addChild(filename);
|
||||
|
||||
places = new StringGridWidget("placesList");
|
||||
places.resize(1, 10);
|
||||
places.showRowHeaders(false).showColHeaders(true);
|
||||
places.setColTitle(0, "Places"d);
|
||||
leftPanel.addChild(places);
|
||||
//places = new StringGridWidget("placesList");
|
||||
//places.resize(1, 10);
|
||||
//places.showRowHeaders(false).showColHeaders(true);
|
||||
//places.setColTitle(0, "Places"d);
|
||||
//leftPanel.addChild(places);
|
||||
|
||||
addChild(content);
|
||||
addChild(createButtonsPanel([ACTION_OPEN, ACTION_CANCEL], 0, 0));
|
||||
|
|
|
@ -207,14 +207,40 @@ class ImageButton : ImageWidget {
|
|||
class ImageTextButton : HorizontalLayout {
|
||||
protected ImageWidget _icon;
|
||||
protected TextWidget _label;
|
||||
|
||||
/// Get 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; }
|
||||
/// Set label string resource Id
|
||||
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";
|
||||
_icon = new ImageWidget("icon", drawableId);
|
||||
_label = new TextWidget("label", textResourceId);
|
||||
_label = new TextWidget("label", caption);
|
||||
_label.styleId = "BUTTON_LABEL";
|
||||
_icon.state = State.Parent;
|
||||
_label.state = State.Parent;
|
||||
|
@ -224,20 +250,17 @@ class ImageTextButton : HorizontalLayout {
|
|||
focusable = 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) {
|
||||
super(ID);
|
||||
styleId = "BUTTON";
|
||||
_icon = new ImageWidget("icon", drawableId);
|
||||
_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;
|
||||
UIString caption = rawText;
|
||||
init(drawableId, caption);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ interface ListAdapter {
|
|||
|
||||
/// List adapter for simple list of widget instances
|
||||
class WidgetListAdapter : ListAdapter {
|
||||
WidgetList _widgets;
|
||||
private WidgetList _widgets;
|
||||
/// list of widgets to display
|
||||
@property ref WidgetList widgets() { return _widgets; }
|
||||
/// returns number of widgets in list
|
||||
|
|