diff --git a/src/dlangui/dialogs/filedlg.d b/src/dlangui/dialogs/filedlg.d index 555ae831..248b89a6 100644 --- a/src/dlangui/dialogs/filedlg.d +++ b/src/dlangui/dialogs/filedlg.d @@ -212,6 +212,39 @@ class FileDialog : Dialog, CustomGridCellAdapter { return openDirectory(_path, null); } + protected void locateFileInList(dstring pattern) { + if (!pattern.length) + return; + int selection = _fileList.row; + if (selection < 0) + selection = 0; + int index = -1; // first matched item + string mask = pattern.toUTF8; + // search forward from current row to end of list + for(int i = selection; i < _entries.length; i++) { + string fname = baseName(_entries[i].name); + if (fname.startsWith(mask)) { + index = i; + break; + } + } + if (index < 0) { + // search from beginning of list to current position + for(int i = 0; i < selection && i < _entries.length; i++) { + string fname = baseName(_entries[i].name); + if (fname.startsWith(mask)) { + index = i; + break; + } + } + } + if (index >= 0) { + // move selection + _fileList.selectCell(1, index + 1); + window.update(); + } + } + protected bool openDirectory(string dir, string selectedItemPath) { dir = buildNormalizedPath(dir); Log.d("FileDialog.openDirectory(", dir, ")"); @@ -502,6 +535,12 @@ class FileDialog : Dialog, CustomGridCellAdapter { _fileList.cellPopupMenu = &getCellPopupMenu; _fileList.menuItemAction = &handleAction; + _fileList.keyEvent = delegate(Widget source, KeyEvent event) { + if (_shortcutHelper.onKeyEvent(event)) + locateFileInList(_shortcutHelper.text); + return false; + }; + rightPanel.addChild(_edPath); rightPanel.addChild(_fileList); rightPanel.addChild(fnlayout); @@ -530,6 +569,8 @@ class FileDialog : Dialog, CustomGridCellAdapter { } + protected TextTypingShortcutHelper _shortcutHelper; + /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). override void layout(Rect rc) { super.layout(rc); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 28f774eb..7ed51aa8 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1792,6 +1792,57 @@ class WidgetGroupDefaultDrawing : WidgetGroup { } } +/// helper for locating items in list, tree, table or other controls by typing their name +struct TextTypingShortcutHelper { + int timeoutMillis = 800; // expiration time for entered text; after timeout collected text will be cleared + private long _lastUpdateTimeStamp; + private dchar[] _text; + /// cancel text collection (next typed text will be collected from scratch) + void cancel() { + _text.length = 0; + _lastUpdateTimeStamp = 0; + } + /// returns collected text string - use it for lookup + @property dstring text() { return _text.dup; } + /// pass key event here; returns true if search text is updated and you can move selection using it + bool onKeyEvent(KeyEvent event) { + long ts = currentTimeMillis; + if (_lastUpdateTimeStamp && ts - _lastUpdateTimeStamp > timeoutMillis) + cancel(); + if (event.action == KeyAction.Text) { + _text ~= event.text; + _lastUpdateTimeStamp = ts; + return _text.length > 0; + } + if (event.action == KeyAction.KeyDown || event.action == KeyAction.KeyUp) { + switch (event.keyCode) with (KeyCode) { + case LEFT: + case RIGHT: + case UP: + case DOWN: + case HOME: + case END: + case TAB: + case PAGEUP: + case PAGEDOWN: + case BACK: + cancel(); + break; + default: + break; + } + } + return false; + } + + /// cancel text typing on some mouse events, if necessary + void onMouseEvent(MouseEvent event) { + if (event.action == MouseAction.ButtonUp || event.action == MouseAction.ButtonDown) + cancel(); + } +} + + enum ONE_SECOND = 10_000_000L; /// Helper to handle animation progress