mirror of https://github.com/buggins/dlangide.git
409 lines
14 KiB
D
409 lines
14 KiB
D
module dlangide.ui.wspanel;
|
|
|
|
import std.string;
|
|
import dlangui;
|
|
import dlangide.workspace.workspace;
|
|
import dlangide.workspace.project;
|
|
import dlangide.ui.commands;
|
|
|
|
enum ProjectItemType : int {
|
|
None,
|
|
SourceFile,
|
|
SourceFolder,
|
|
Project,
|
|
Workspace
|
|
}
|
|
|
|
interface SourceFileSelectionHandler {
|
|
bool onSourceFileSelected(ProjectSourceFile file, bool activate);
|
|
}
|
|
|
|
interface WorkspaceActionHandler {
|
|
bool onWorkspaceAction(const Action a);
|
|
}
|
|
|
|
class WorkspacePanel : DockWindow {
|
|
protected Workspace _workspace;
|
|
protected TreeWidget _tree;
|
|
|
|
/// handle source file selection change
|
|
Signal!SourceFileSelectionHandler sourceFileSelectionListener;
|
|
Signal!WorkspaceActionHandler workspaceActionListener;
|
|
|
|
this(string id) {
|
|
super(id);
|
|
workspace = null;
|
|
//layoutWidth = 200;
|
|
_caption.text = "Workspace Explorer"d;
|
|
acceleratorMap.add([ACTION_PROJECT_FOLDER_EXPAND_ALL, ACTION_PROJECT_FOLDER_COLLAPSE_ALL,
|
|
ACTION_PROJECT_FOLDER_REFRESH, ACTION_PROJECT_FOLDER_RENAME_ITEM,
|
|
ACTION_PROJECT_FOLDER_REMOVE_ITEM, ACTION_PROJECT_FOLDER_OPEN_ITEM]);
|
|
}
|
|
|
|
bool selectItem(ProjectItem projectItem) {
|
|
if (projectItem) {
|
|
TreeItem item = _tree.findItemById(projectItem.filename);
|
|
if (item) {
|
|
if (item.parent && !item.parent.isFullyExpanded)
|
|
_tree.items.toggleExpand(item.parent);
|
|
_tree.makeItemVisible(item);
|
|
_tree.selectItem(item);
|
|
return true;
|
|
}
|
|
} else {
|
|
_tree.clearSelection();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated) {
|
|
if (!selectedItem)
|
|
return;
|
|
if (_workspace) {
|
|
// save selected item id
|
|
ProjectItem item = cast(ProjectItem)selectedItem.objectParam;
|
|
if (item) {
|
|
string id = item.filename;
|
|
if (id)
|
|
_workspace.selectedWorkspaceItem = id;
|
|
}
|
|
}
|
|
if (selectedItem.intParam == ProjectItemType.SourceFile) {
|
|
// file selected
|
|
if (sourceFileSelectionListener.assigned) {
|
|
ProjectSourceFile sourceFile = cast(ProjectSourceFile)selectedItem.objectParam;
|
|
if (sourceFile) {
|
|
sourceFileSelectionListener(sourceFile, activated);
|
|
}
|
|
}
|
|
} else if (selectedItem.intParam == ProjectItemType.SourceFolder) {
|
|
// folder selected
|
|
}
|
|
}
|
|
|
|
override protected Widget createBodyWidget() {
|
|
_tree = new TreeWidget("wstree", ScrollBarMode.Auto, ScrollBarMode.Auto);
|
|
_tree.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT);
|
|
_tree.selectionChange = &onTreeItemSelected;
|
|
_tree.expandedChange.connect(&onTreeExpandedStateChange);
|
|
_tree.fontSize = 16;
|
|
_tree.noCollapseForSingleTopLevelItem = true;
|
|
_tree.popupMenu = &onTreeItemPopupMenu;
|
|
|
|
_workspacePopupMenu = new MenuItem();
|
|
_workspacePopupMenu.add(ACTION_FILE_NEW_PROJECT,
|
|
ACTION_PROJECT_FOLDER_REFRESH,
|
|
ACTION_FILE_WORKSPACE_CLOSE,
|
|
ACTION_PROJECT_FOLDER_EXPAND_ALL,
|
|
ACTION_PROJECT_FOLDER_COLLAPSE_ALL
|
|
);
|
|
|
|
_projectPopupMenu = new MenuItem();
|
|
_projectPopupMenu.add(ACTION_PROJECT_SET_STARTUP,
|
|
ACTION_PROJECT_FOLDER_REFRESH,
|
|
//ACTION_FILE_NEW_DIRECTORY,
|
|
//ACTION_PROJECT_FOLDER_OPEN_ITEM,
|
|
ACTION_PROJECT_BUILD,
|
|
ACTION_PROJECT_REBUILD,
|
|
ACTION_PROJECT_CLEAN,
|
|
ACTION_PROJECT_UPDATE_DEPENDENCIES,
|
|
ACTION_PROJECT_REVEAL_IN_EXPLORER,
|
|
ACTION_PROJECT_SETTINGS,
|
|
ACTION_PROJECT_FOLDER_EXPAND_ALL,
|
|
ACTION_PROJECT_FOLDER_COLLAPSE_ALL
|
|
//ACTION_PROJECT_FOLDER_REMOVE_ITEM
|
|
);
|
|
|
|
_folderPopupMenu = new MenuItem();
|
|
_folderPopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE,
|
|
ACTION_FILE_NEW_DIRECTORY,
|
|
ACTION_PROJECT_FOLDER_REFRESH, ACTION_PROJECT_FOLDER_OPEN_ITEM,
|
|
ACTION_PROJECT_FOLDER_EXPAND_ALL, ACTION_PROJECT_FOLDER_COLLAPSE_ALL
|
|
//ACTION_PROJECT_FOLDER_REMOVE_ITEM,
|
|
//ACTION_PROJECT_FOLDER_RENAME_ITEM
|
|
);
|
|
|
|
_filePopupMenu = new MenuItem();
|
|
_filePopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE,
|
|
ACTION_PROJECT_FOLDER_REFRESH,
|
|
ACTION_PROJECT_FOLDER_OPEN_ITEM,
|
|
ACTION_PROJECT_FOLDER_REMOVE_ITEM,
|
|
//ACTION_PROJECT_FOLDER_RENAME_ITEM
|
|
);
|
|
return _tree;
|
|
}
|
|
|
|
protected MenuItem _workspacePopupMenu;
|
|
protected MenuItem _projectPopupMenu;
|
|
protected MenuItem _folderPopupMenu;
|
|
protected MenuItem _filePopupMenu;
|
|
protected string _popupMenuSelectedItemId;
|
|
protected TreeItem _popupMenuSelectedItem;
|
|
protected void onPopupMenuItem(MenuItem item) {
|
|
if (item.action)
|
|
handleAction(item.action);
|
|
}
|
|
|
|
protected MenuItem onTreeItemPopupMenu(TreeItems source, TreeItem selectedItem) {
|
|
MenuItem menu = null;
|
|
_popupMenuSelectedItemId = selectedItem.id;
|
|
_popupMenuSelectedItem = selectedItem;
|
|
if (selectedItem.intParam == ProjectItemType.SourceFolder) {
|
|
menu = _folderPopupMenu;
|
|
} else if (selectedItem.intParam == ProjectItemType.SourceFile) {
|
|
menu = _filePopupMenu;
|
|
} else if (selectedItem.intParam == ProjectItemType.Project) {
|
|
menu = _projectPopupMenu;
|
|
} else if (selectedItem.intParam == ProjectItemType.Workspace) {
|
|
menu = _workspacePopupMenu;
|
|
}
|
|
if (menu && menu.subitemCount) {
|
|
for (int i = 0; i < menu.subitemCount; i++) {
|
|
Action a = menu.subitem(i).action.clone();
|
|
a.objectParam = selectedItem.objectParam;
|
|
menu.subitem(i).action = a;
|
|
//menu.subitem(i).menuItemAction = &handleAction;
|
|
}
|
|
//menu.onMenuItem = &onPopupMenuItem;
|
|
//menu.menuItemClick = &onPopupMenuItem;
|
|
menu.menuItemAction = &handleAction;
|
|
menu.updateActionState(this);
|
|
return menu;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@property Workspace workspace() {
|
|
return _workspace;
|
|
}
|
|
|
|
/// returns currently selected project item
|
|
@property ProjectItem selectedProjectItem() {
|
|
TreeItem ti = _tree.items.selectedItem;
|
|
if (!ti)
|
|
return null;
|
|
Object obj = ti.objectParam;
|
|
if (!obj)
|
|
return null;
|
|
return cast(ProjectItem)obj;
|
|
}
|
|
|
|
ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true, dstring projectName=null) {
|
|
if (_workspace)
|
|
return _workspace.findSourceFileItem(filename, fullFileName, projectName);
|
|
return null;
|
|
}
|
|
|
|
/// Adding elements to the tree
|
|
void addProjectItems(TreeItem root, ProjectItem items) {
|
|
for (int i = 0; i < items.childCount; i++) {
|
|
ProjectItem child = items.child(i);
|
|
if (child.isFolder) {
|
|
TreeItem p = root.newChild(child.filename, child.name, "folder");
|
|
p.intParam = ProjectItemType.SourceFolder;
|
|
p.objectParam = child;
|
|
if (restoreItemState(child.filename))
|
|
p.expand();
|
|
else
|
|
p.collapse();
|
|
addProjectItems(p, child);
|
|
} else {
|
|
string icon = "text-other";
|
|
if (child.isDSourceFile)
|
|
icon = "text-d";
|
|
if (child.isJsonFile)
|
|
icon = "text-json";
|
|
if (child.isDMLFile)
|
|
icon = "text-dml";
|
|
TreeItem p = root.newChild(child.filename, child.name, icon);
|
|
p.intParam = ProjectItemType.SourceFile;
|
|
p.objectParam = child;
|
|
}
|
|
}
|
|
}
|
|
|
|
void updateDefault() {
|
|
TreeItem defaultItem = null;
|
|
if (_workspace && _tree.items.childCount && _workspace.startupProject) {
|
|
for (int i = 0; i < _tree.items.child(0).childCount; i++) {
|
|
TreeItem p = _tree.items.child(0).child(i);
|
|
if (p.objectParam is _workspace.startupProject)
|
|
defaultItem = p;
|
|
}
|
|
}
|
|
_tree.items.setDefaultItem(defaultItem);
|
|
}
|
|
|
|
/// map key to action
|
|
override Action findKeyAction(uint keyCode, uint flags) {
|
|
Action action = _acceleratorMap.findByKey(keyCode, flags);
|
|
if (action) {
|
|
if (TreeItem ti = _tree.items.selectedItem) {
|
|
_popupMenuSelectedItem = ti;
|
|
action.objectParam = ti.objectParam;
|
|
} else {
|
|
return null;
|
|
}
|
|
return action;
|
|
}
|
|
return super.findKeyAction(keyCode, flags);
|
|
}
|
|
|
|
void expandAll(const Action a) {
|
|
if (!_workspace)
|
|
return;
|
|
if (_popupMenuSelectedItem)
|
|
_popupMenuSelectedItem.expandAll();
|
|
}
|
|
|
|
void collapseAll(const Action a) {
|
|
if (!_workspace)
|
|
return;
|
|
if (_popupMenuSelectedItem)
|
|
_popupMenuSelectedItem.collapseAll();
|
|
}
|
|
|
|
protected bool[string] _itemStates;
|
|
protected bool _itemStatesDirty;
|
|
protected void readExpandedStateFromWorkspace() {
|
|
_itemStates.clear();
|
|
if (_workspace) {
|
|
string[] items = _workspace.expandedItems;
|
|
foreach(item; items)
|
|
_itemStates[item] = true;
|
|
}
|
|
}
|
|
|
|
/// Saving items collapse/expand state
|
|
protected void saveItemState(string itemPath, bool expanded) {
|
|
bool changed = restoreItemState(itemPath) != expanded;
|
|
if (!_itemStatesDirty && changed)
|
|
_itemStatesDirty = true;
|
|
if (changed) {
|
|
if (expanded) {
|
|
_itemStates[itemPath] = true;
|
|
} else {
|
|
_itemStates.remove(itemPath);
|
|
}
|
|
string[] items;
|
|
items.assumeSafeAppend;
|
|
foreach(k,v; _itemStates) {
|
|
items ~= k;
|
|
}
|
|
_workspace.expandedItems = items;
|
|
debug Log.d("stored Expanded state ", expanded, " for ", itemPath);
|
|
}
|
|
}
|
|
/// Is need to expand item?
|
|
protected bool restoreItemState(string itemPath) {
|
|
if (auto p = itemPath in _itemStates) {
|
|
// Item itself must expand, but upper items may be collapsed
|
|
return *p;
|
|
/*
|
|
auto path = itemPath;
|
|
while (path.length > 0 && !path.endsWith("src") && path in _itemStates) {
|
|
auto pos = lastIndexOf(path, '/');
|
|
path = pos > -1 ? path[ 0 .. pos ] : "";
|
|
}
|
|
if (path.length == 0 || path.endsWith("src")) {
|
|
debug Log.d("restored Expanded state for ", itemPath);
|
|
return *p;
|
|
}
|
|
*/
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void onTreeExpandedStateChange(TreeItems source, TreeItem item) {
|
|
bool expanded = item.expanded;
|
|
ProjectItem prjItem = cast(ProjectItem)item.objectParam;
|
|
if (prjItem) {
|
|
string fn = prjItem.filename;
|
|
debug Log.d("onTreeExpandedStateChange expanded=", expanded, " fn=", fn);
|
|
saveItemState(fn, expanded);
|
|
}
|
|
}
|
|
|
|
void reloadItems() {
|
|
_tree.expandedChange.disconnect(&onTreeExpandedStateChange);
|
|
_tree.selectionChange.disconnect(&onTreeItemSelected);
|
|
_tree.clearAllItems();
|
|
|
|
if (_workspace) {
|
|
TreeItem defaultItem = null;
|
|
TreeItem root = _tree.items.newChild(_workspace.filename, _workspace.name, "project-development");
|
|
root.intParam = ProjectItemType.Workspace;
|
|
foreach(project; _workspace.projects) {
|
|
TreeItem p = root.newChild(project.filename, project.name, project.isDependency ? "project-d-dependency" : "project-d");
|
|
p.intParam = ProjectItemType.Project;
|
|
p.objectParam = project;
|
|
if (restoreItemState(project.filename))
|
|
p.expand();
|
|
else
|
|
p.collapse();
|
|
if (project && _workspace.startupProject is project)
|
|
defaultItem = p;
|
|
addProjectItems(p, project.items);
|
|
}
|
|
_tree.items.setDefaultItem(defaultItem);
|
|
} else {
|
|
_tree.items.newChild("none", "No workspace"d, "project-development");
|
|
}
|
|
_tree.expandedChange.connect(&onTreeExpandedStateChange);
|
|
_tree.selectionChange.connect(&onTreeItemSelected);
|
|
|
|
// expand default project if no information about expanded items
|
|
if (!_itemStates.length) {
|
|
if (_workspace && _workspace.startupProject) {
|
|
string fn = _workspace.startupProject.filename;
|
|
TreeItem startupProjectItem = _tree.items.findItemById(fn);
|
|
if (startupProjectItem) {
|
|
startupProjectItem.expand();
|
|
saveItemState(fn, true);
|
|
}
|
|
}
|
|
}
|
|
if (_workspace) {
|
|
// restore selection
|
|
string id = _workspace.selectedWorkspaceItem;
|
|
_tree.selectItem(id);
|
|
}
|
|
|
|
updateDefault();
|
|
}
|
|
|
|
@property void workspace(Workspace w) {
|
|
_workspace = w;
|
|
readExpandedStateFromWorkspace();
|
|
reloadItems();
|
|
}
|
|
|
|
/// override to handle specific actions
|
|
override bool handleAction(const Action a) {
|
|
if (workspaceActionListener.assigned)
|
|
return workspaceActionListener(a);
|
|
return false;
|
|
}
|
|
|
|
override protected bool onCloseButtonClick(Widget source) {
|
|
hide();
|
|
return true;
|
|
}
|
|
|
|
/// hide workspace panel
|
|
void hide() {
|
|
visibility = Visibility.Gone;
|
|
parent.layout(parent.pos);
|
|
}
|
|
|
|
// activate workspace panel if hidden
|
|
void activate() {
|
|
if (visibility == Visibility.Gone) {
|
|
visibility = Visibility.Visible;
|
|
parent.layout(parent.pos);
|
|
}
|
|
setFocus();
|
|
}
|
|
}
|