From 7fc0e7eee90e6591ecd689aad376166f2067091a Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Mon, 22 Dec 2014 15:18:22 +0300 Subject: [PATCH] FileDialog fixes --- dlanguilib.visualdproj | 2 +- examples/example1/src/main.d | 4 +- src/dlangui/core/events.d | 10 ++- src/dlangui/dialogs/dialog.d | 31 +++++++-- src/dlangui/dialogs/filedlg.d | 93 ++++++++++++++++++++++--- src/dlangui/platforms/common/platform.d | 9 ++- src/dlangui/widgets/controls.d | 3 + src/dlangui/widgets/grid.d | 11 ++- src/dlangui/widgets/lists.d | 2 +- src/dlangui/widgets/widget.d | 17 ++++- 10 files changed, 154 insertions(+), 28 deletions(-) diff --git a/dlanguilib.visualdproj b/dlanguilib.visualdproj index 7b3c0e7e..7a330f81 100644 --- a/dlanguilib.visualdproj +++ b/dlanguilib.visualdproj @@ -64,7 +64,7 @@ 1 $(IntDir)\$(TargetName).json 0 - + DebugFocus 0 USE_SDL USE_OPENGL 0 diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index c12e6b0a..1749c6ea 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -293,8 +293,8 @@ extern (C) int UIAppMain(string[] args) { UIString caption; caption = "Open Text File"d; FileDialog dlg = new FileDialog(caption, window, null); - dlg.onDialogResult = delegate(Dialog dlg, Action result) { - Log.d("FileDialog.onDialogResult"); + dlg.onDialogResult = delegate(Dialog dlg, const Action result) { + Log.d("FileDialog.onDialogResult: ", result, " param=", result.stringParam); }; dlg.show(); return true; diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index 240509db..29f084ef 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -66,7 +66,7 @@ class Action { /// optional object parameter protected Object _objectParam; /// returns optional string parameter - @property string stringParam() { + @property string stringParam() const { return _stringParam; } /// sets optional string parameter @@ -107,6 +107,10 @@ class Action { } /// deep copy @property Action clone() immutable { return new Action(this); } + /// deep copy + @property Action clone() const { return new Action(cast(immutable)this); } + /// deep copy + @property Action clone() { return new Action(cast(immutable)this); } /// create action only with ID this(int id) { _id = id; @@ -185,6 +189,10 @@ class Action { _iconId = id; return this; } + + override string toString() const { + return "Action(" ~ to!string(_id) ~ ")"; + } } /// Map of Accelerator to Action diff --git a/src/dlangui/dialogs/dialog.d b/src/dlangui/dialogs/dialog.d index 68a6fb3d..5b825496 100644 --- a/src/dlangui/dialogs/dialog.d +++ b/src/dlangui/dialogs/dialog.d @@ -34,7 +34,7 @@ enum DialogFlag : uint { } interface DialogResultHandler { - public void onDialogResult(Dialog dlg, Action result); + public void onDialogResult(Dialog dlg, const Action result); } /// base for all dialogs @@ -88,8 +88,11 @@ class Dialog : VerticalLayout { return this; } + protected const(Action) [] _buttonActions; + /// create panel with buttons based on list of actions - Widget createButtonsPanel(const Action[] actions, int defaultActionIndex, int splitBeforeIndex) { + Widget createButtonsPanel(const(Action) [] actions, int defaultActionIndex, int splitBeforeIndex) { + _buttonActions = actions; LinearLayout res = new HorizontalLayout("buttons"); res.layoutWidth(FILL_PARENT); res.layoutWeight = 0; @@ -97,18 +100,26 @@ class Dialog : VerticalLayout { if (splitBeforeIndex == i) res.addChild(new HSpacer()); const Action a = actions[i]; - string id = "btn" ~ to!string(a.id); + string id = "btn" ~ to!string(a.id); ImageTextButton btn = new ImageTextButton(id, a.iconId, a.label); if (defaultActionIndex == i) btn.setState(State.Default); - btn.onClickListener = delegate(Widget source) { - return handleAction(a); - }; + btn.action = a.clone(); res.addChild(btn); } return res; } + /// Custom handling of actions + override bool handleAction(const Action action) { + foreach(const Action a; _buttonActions) + if (a.id == action.id) { + close(action); + return true; + } + return false; + } + /// override to implement creation of dialog controls void init() { } @@ -121,7 +132,7 @@ class Dialog : VerticalLayout { If action is null, no result dispatching will occur. */ - void close(Action action) { + void close(const Action action) { if (action) { if (onDialogResult.assigned) onDialogResult(this, action); @@ -144,5 +155,11 @@ class Dialog : VerticalLayout { _window.windowIcon = drawableCache.getImage(_icon); _window.mainWidget = this; _window.show(); + onShow(); + } + + /// called after window with dialog is shown + void onShow() { + // override to do something useful } } diff --git a/src/dlangui/dialogs/filedlg.d b/src/dlangui/dialogs/filedlg.d index 0e04cc64..66ceab51 100644 --- a/src/dlangui/dialogs/filedlg.d +++ b/src/dlangui/dialogs/filedlg.d @@ -36,6 +36,7 @@ import dlangui.widgets.editors; import dlangui.platforms.common.platform; import dlangui.dialogs.dialog; +private import std.algorithm; private import std.file; private import std.path; private import std.utf; @@ -70,8 +71,17 @@ class FileDialog : Dialog, CustomGridCellAdapter { protected DirEntry[] _entries; protected bool _isRoot; + protected bool _isOpenDialog; + this(UIString caption, Window parent, Action action = null, uint fileDialogFlags = DialogFlag.Modal | DialogFlag.Resizable | FileDialogFlag.FileMustExist) { super(caption, parent, fileDialogFlags); + _isOpenDialog = !(_flags & FileDialogFlag.ConfirmOverwrite); + if (action is null) { + if (_isOpenDialog) + action = ACTION_OPEN.clone(); + else + action = ACTION_SAVE.clone(); + } _action = action; } @@ -82,7 +92,11 @@ class FileDialog : Dialog, CustomGridCellAdapter { _fileList.fillColumnWidth(1); } - protected bool openDirectory(string dir) { + protected bool upLevel() { + return openDirectory(buildNormalizedPath(_path, ".."), _path); + } + + protected bool openDirectory(string dir, string selectedItemPath) { dir = buildNormalizedPath(dir); Log.d("FileDialog.openDirectory(", dir, ")"); _fileList.rows = 0; @@ -93,7 +107,10 @@ class FileDialog : Dialog, CustomGridCellAdapter { _isRoot = isRoot(dir); _edPath.text = toUTF32(_path); _fileList.rows = cast(int)_entries.length; + int selectionIndex = -1; for (int i = 0; i < _entries.length; i++) { + if (_entries[i].name.equal(selectedItemPath)) + selectionIndex = i; string fname = baseName(_entries[i].name); string sz; string date; @@ -111,9 +128,23 @@ class FileDialog : Dialog, CustomGridCellAdapter { } _fileList.autoFitColumnWidths(); _fileList.fillColumnWidth(1); + if (selectionIndex >= 0) + _fileList.selectCell(1, selectionIndex + 1, true); + else if (_entries.length > 0) + _fileList.selectCell(1, 1, true); return true; } + override bool onKeyEvent(KeyEvent event) { + if (event.action == KeyAction.KeyDown) { + if (event.keyCode == KeyCode.BACK && event.flags == 0) { + upLevel(); + return true; + } + } + return false; + } + /// return true for custom drawn cell override bool isCustomCell(int col, int row) { if (col == 0 && row >= 0) @@ -150,8 +181,9 @@ class FileDialog : Dialog, CustomGridCellAdapter { } } - protected Widget createRootsList() { + protected ListWidget createRootsList() { ListWidget res = new ListWidget("ROOTS_LIST"); + res.styleId = "EDIT_BOX"; WidgetListAdapter adapter = new WidgetListAdapter(); foreach(ref RootEntry root; _roots) { ImageTextButton btn = new ImageTextButton(null, root.icon, root.label); @@ -161,28 +193,61 @@ class FileDialog : Dialog, CustomGridCellAdapter { adapter.widgets.add(btn); } res.ownAdapter = adapter; - res.layoutWidth = WRAP_CONTENT; - res.layoutHeight = FILL_PARENT; + res.layoutWidth(WRAP_CONTENT).layoutHeight(FILL_PARENT).layoutWeight(0); res.onItemClickListener = delegate(Widget source, int itemIndex) { - openDirectory(_roots[itemIndex].path); + openDirectory(_roots[itemIndex].path, null); return true; }; + res.focusable = true; + debug Log.d("root lisk styleId=", res.styleId); return res; } + /// file list item activated (double clicked or Enter key pressed) protected void onItemActivated(int index) { DirEntry e = _entries[index]; if (e.isDir) { - openDirectory(e.name); + openDirectory(e.name, _path); } else if (e.isFile) { string fname = e.name; - Action result = ACTION_OPEN.clone(); + Action result = _action; result.stringParam = fname; close(result); } } + /// file list item selected + protected void onItemSelected(int index) { + DirEntry e = _entries[index]; + if (e.isDir) { + _edFilename.text = ""d; + _filename = ""; + } else if (e.isFile) { + string fname = e.name; + _edFilename.text = toUTF32(baseName(fname)); + _filename = fname; + } + } + + /// Custom handling of actions + override bool handleAction(const Action action) { + if (action.id == StandardAction.Cancel) { + super.handleAction(action); + return true; + } + if (action.id == StandardAction.Open || action.id == StandardAction.Save) { + if (_filename.length > 0) { + Action result = _action; + result.stringParam = _filename; + close(result); + return true; + } + } + return super.handleAction(action); + } + + /// override to implement creation of dialog controls override void init() { _roots = getRootPaths; @@ -211,7 +276,7 @@ class FileDialog : Dialog, CustomGridCellAdapter { _edPath.layoutWidth(FILL_PARENT); _edPath.layoutWeight = 0; - _edFilename = new EditLine("path"); + _edFilename = new EditLine("filename"); _edFilename.layoutWidth(FILL_PARENT); _edFilename.layoutWeight = 0; @@ -230,16 +295,22 @@ class FileDialog : Dialog, CustomGridCellAdapter { addChild(content); - addChild(createButtonsPanel([ACTION_OPEN, ACTION_CANCEL], 0, 0)); + addChild(createButtonsPanel([cast(immutable)_action, ACTION_CANCEL], 0, 0)); _fileList.customCellAdapter = this; _fileList.onCellActivated = delegate(GridWidgetBase source, int col, int row) { onItemActivated(row); }; + _fileList.onCellSelected = delegate(GridWidgetBase source, int col, int row) { + onItemSelected(row); + }; - openDirectory(currentDir); + openDirectory(currentDir, null); _fileList.layoutHeight = FILL_PARENT; - _fileList.setFocus(); } + + override void onShow() { + _fileList.setFocus(); + } } diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index bd50d1be..0641ac64 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -311,8 +311,11 @@ class Window { if (focus.onKeyEvent(event)) return true; // processed by focused widget } - if (_mainWidget) - return dispatchKeyEvent(_mainWidget, event) || res; + if (_mainWidget) { + if (dispatchKeyEvent(_mainWidget, event)) + return res; + return _mainWidget.onKeyEvent(event) || res; + } return res; } @@ -431,7 +434,7 @@ class Window { } /// dispatch action to main widget - bool dispatchAction(Action action) { + bool dispatchAction(const Action action) { Widget focus = focusedWidget; // first, offer action to focused widget if (focus && focus.handleAction(action)) diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index bec72a3e..d3939eea 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -191,6 +191,7 @@ class ImageWidget : Widget { } } + /// button with image only class ImageButton : ImageWidget { this(string ID = null, string drawableId = null) { @@ -201,6 +202,8 @@ class ImageButton : ImageWidget { focusable = true; trackHover = true; } + + } /// button with image and text diff --git a/src/dlangui/widgets/grid.d b/src/dlangui/widgets/grid.d index 505d97de..c4ef073b 100644 --- a/src/dlangui/widgets/grid.d +++ b/src/dlangui/widgets/grid.d @@ -200,6 +200,8 @@ enum GridActions : int { DocumentEnd, /// move cursor to the end of document with selection SelectDocumentEnd, + /// Enter key pressed on cell + ActivateCell, } /// Adapter for custom drawing of some cells in grid widgets @@ -610,7 +612,7 @@ class GridWidgetBase : ScrollWidgetBase { if (makeVisible) makeCellVisible(_col, _row); if (onCellSelected.assigned) - onCellSelected(this, _col, _row); + onCellSelected(this, _col - _headerCols, _row - _headerRows); return true; } @@ -799,6 +801,12 @@ class GridWidgetBase : ScrollWidgetBase { } switch (actionId) { + case GridActions.ActivateCell: + if (onCellActivated.assigned) { + onCellActivated(this, col, row); + return true; + } + return false; case GridActions.ScrollLeft: scrollBy(-1, 0); return true; @@ -1103,6 +1111,7 @@ class GridWidgetBase : ScrollWidgetBase { new Action(GridActions.PageEnd, KeyCode.PAGEDOWN, KeyFlag.Control), new Action(GridActions.DocumentBegin, KeyCode.HOME, KeyFlag.Control), new Action(GridActions.DocumentEnd, KeyCode.END, KeyFlag.Control), + new Action(GridActions.ActivateCell, KeyCode.RETURN, 0), ]); focusable = true; } diff --git a/src/dlangui/widgets/lists.d b/src/dlangui/widgets/lists.d index ef8f7627..63a4d39f 100644 --- a/src/dlangui/widgets/lists.d +++ b/src/dlangui/widgets/lists.d @@ -780,7 +780,7 @@ class ListWidget : WidgetGroup, OnScrollHandler { // TODO } } - return false; + return super.onKeyEvent(event); //if (_selectedItemIndex != -1 && event.action == KeyAction.KeyUp && (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN)) { // itemClicked(_selectedItemIndex); // return true; diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index f0b8df7a..548d3d9a 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -596,6 +596,13 @@ class Widget { return false; } + protected Action _action; + /// action to emit on click + @property Action action() { return _action; } + /// action to emit on click + @property void action(Action action) { _action = action; } + + protected bool _focusGroup; /***************************************** * When focus group is set for some parent widget, focus from one of containing widgets can be moved using keyboard only to one of other widgets containing in it and cannot bypass bounds of focusGroup. @@ -668,6 +675,9 @@ class Widget { } return obj1.rect.left < obj2.rect.left; } + override string toString() { + return widget.id; + } } private void findFocusableChildren(ref TabOrderInfo[] results, Rect clipRect) { @@ -727,6 +737,7 @@ class Widget { break; } } + debug(DebugFocus) Log.d("findNextFocusWidget myIndex=", myIndex, " of focusables: ", focusables); if (myIndex == -1) return null; // not found myself if (focusables.length == 1) @@ -876,7 +887,11 @@ class Widget { // called to process click and notify listeners protected bool handleClick() { - bool res = onClickListener(this); + bool res = false; + if (onClickListener.assigned) + res = onClickListener(this); + else if (_action) + res = handleAction(_action); return res; }