FileDialog - continue development; drive list

This commit is contained in:
Vadim Lopatin 2014-12-17 13:59:53 +03:00
parent f7409f6c07
commit 35104b170a
14 changed files with 342 additions and 27 deletions

View File

@ -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();
}

BIN
res/mdpi/computer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/mdpi/drive-harddisk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/mdpi/drive-optical.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
res/mdpi/folder-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/mdpi/folder-network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/mdpi/user-home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

253
src/dlangui/core/files.d Normal file
View File

@ -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;
}

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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