Skip to content

Commit 4638528

Browse files
authored
Merge pull request #119 from Integration-Automation/dev
Update stable version
2 parents b50cec8 + f6a70c9 commit 4638528

File tree

6 files changed

+302
-17
lines changed

6 files changed

+302
-17
lines changed

exe/auto_py_to_exe_setting.json

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
{
99
"optionDest": "filenames",
10-
"value": "C:/CodeWorkspace/Python/PyBreeze/exe/start_pybreeze.py"
10+
"value": "D:/Codes/PyBreeze/exe/start_pybreeze.py"
1111
},
1212
{
1313
"optionDest": "onefile",
@@ -19,7 +19,7 @@
1919
},
2020
{
2121
"optionDest": "icon_file",
22-
"value": "C:/CodeWorkspace/Python/PyBreeze/exe/pybreeze_icon.ico"
22+
"value": "D:\\Codes/PyBreeze/exe/pybreeze_icon.ico"
2323
},
2424
{
2525
"optionDest": "name",
@@ -58,24 +58,16 @@
5858
"value": false
5959
},
6060
{
61-
"optionDest": "datas",
62-
"value": "C:/CodeWorkspace/Python/PyBreeze/.venv/Lib/site-packages/ipython-9.11.0.dist-info;ipython-9.11.0.dist-info/"
63-
},
64-
{
65-
"optionDest": "datas",
66-
"value": "C:/CodeWorkspace/Python/PyBreeze/.venv/Lib/site-packages/IPython;.IPython/"
67-
},
68-
{
69-
"optionDest": "datas",
70-
"value": "C:/CodeWorkspace/Python/PyBreeze/.venv/Lib/site-packages/ipykernel-7.2.0.dist-info;ipykernel-7.2.0.dist-info/"
61+
"optionDest": "pathex",
62+
"value": "D:\\Codes\\PyBreeze\\.venv"
7163
},
7264
{
73-
"optionDest": "datas",
74-
"value": "C:/CodeWorkspace/Python/PyBreeze/.venv/Lib/site-packages/ipykernel;ipykernel/"
65+
"optionDest": "collect_all",
66+
"value": "ipython"
7567
},
7668
{
77-
"optionDest": "pathex",
78-
"value": "C:/CodeWorkspace/Python/PyBreeze/.venv"
69+
"optionDest": "collect_all",
70+
"value": "debugpy"
7971
}
8072
],
8173
"nonPyinstallerOptions": {

pybreeze/extend_multi_language/extend_english.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@
281281
# Run with Menu
282282
"run_with_menu_label": "Run with...",
283283
"run_with_suffix_mismatch": "Current file ({suffix}) does not match expected suffixes: {expected}",
284+
# File Tree Context Menu
285+
"file_tree_ctx_new_file": "New File",
286+
"file_tree_ctx_new_folder": "New Folder",
287+
"file_tree_ctx_rename": "Rename",
288+
"file_tree_ctx_delete": "Delete",
289+
"file_tree_ctx_copy_path": "Copy Path",
290+
"file_tree_ctx_copy_relative_path": "Copy Relative Path",
291+
"file_tree_ctx_reveal_in_explorer": "Reveal in File Explorer",
292+
"file_tree_ctx_input_file_name": "File name:",
293+
"file_tree_ctx_input_folder_name": "Folder name:",
294+
"file_tree_ctx_input_new_name": "New name for '{name}':",
295+
"file_tree_ctx_error": "Error",
296+
"file_tree_ctx_already_exists": "'{name}' already exists.",
297+
"file_tree_ctx_confirm_delete": "Confirm Delete",
298+
"file_tree_ctx_confirm_delete_message": "Are you sure you want to delete '{name}'?",
284299
# Plugin Browser
285300
"plugin_browser_tab_name": "Plugin Browser",
286301
"plugin_browser_repo_label": "Repository URL:",

pybreeze/extend_multi_language/extend_traditional_chinese.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,21 @@
274274
"jupyterlab_loading": "載入中...",
275275
"jupyterlab_timeout": "JupyterLab 啟動超時",
276276
"jupyterlab_init_failed": "JupyterLab 啟動失敗",
277+
# File Tree Context Menu
278+
"file_tree_ctx_new_file": "新增檔案",
279+
"file_tree_ctx_new_folder": "新增資料夾",
280+
"file_tree_ctx_rename": "重新命名",
281+
"file_tree_ctx_delete": "刪除",
282+
"file_tree_ctx_copy_path": "複製路徑",
283+
"file_tree_ctx_copy_relative_path": "複製相對路徑",
284+
"file_tree_ctx_reveal_in_explorer": "在檔案總管中顯示",
285+
"file_tree_ctx_input_file_name": "檔案名稱:",
286+
"file_tree_ctx_input_folder_name": "資料夾名稱:",
287+
"file_tree_ctx_input_new_name": "'{name}' 的新名稱:",
288+
"file_tree_ctx_error": "錯誤",
289+
"file_tree_ctx_already_exists": "'{name}' 已存在。",
290+
"file_tree_ctx_confirm_delete": "確認刪除",
291+
"file_tree_ctx_confirm_delete_message": "確定要刪除 '{name}' 嗎?",
277292
# Plugin Menu
278293
"plugin_menu_label": "插件",
279294
"plugin_menu_about": "關於",
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import os
2+
import shutil
3+
from pathlib import Path
4+
5+
from PySide6.QtCore import Qt, QModelIndex
6+
from PySide6.QtGui import QCursor
7+
from PySide6.QtWidgets import (
8+
QTreeView, QMenu, QFileSystemModel, QInputDialog,
9+
QMessageBox, QApplication,
10+
)
11+
from je_editor import language_wrapper
12+
from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
13+
14+
15+
def setup_file_tree_context_menu(main_window) -> None:
16+
"""
17+
Attach a right-click context menu to every current and future
18+
EditorWidget's project_treeview.
19+
"""
20+
# Attach to existing tabs
21+
for i in range(main_window.tab_widget.count()):
22+
widget = main_window.tab_widget.widget(i)
23+
if isinstance(widget, EditorWidget) and widget.project_treeview is not None:
24+
_attach_context_menu(widget.project_treeview, main_window)
25+
26+
# Listen for new tabs so future EditorWidgets also get the context menu
27+
original_add_tab = main_window.tab_widget.addTab
28+
29+
def patched_add_tab(*args, **kwargs):
30+
result = original_add_tab(*args, **kwargs)
31+
widget = args[0] if args else None
32+
if isinstance(widget, EditorWidget) and widget.project_treeview is not None:
33+
if widget.project_treeview.contextMenuPolicy() != Qt.ContextMenuPolicy.CustomContextMenu:
34+
_attach_context_menu(widget.project_treeview, main_window)
35+
return result
36+
37+
main_window.tab_widget.addTab = patched_add_tab
38+
39+
40+
def _attach_context_menu(tree_view: QTreeView, main_window) -> None:
41+
tree_view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
42+
tree_view.customContextMenuRequested.connect(
43+
lambda pos, tv=tree_view, mw=main_window: _show_context_menu(pos, tv, mw)
44+
)
45+
46+
47+
def _get_path_from_index(tree_view: QTreeView, index: QModelIndex) -> Path | None:
48+
model: QFileSystemModel = tree_view.model()
49+
if not index.isValid():
50+
return None
51+
return Path(model.filePath(index))
52+
53+
54+
def _get_tree_root_path(tree_view: QTreeView) -> Path:
55+
"""Get the root directory currently shown in the tree view."""
56+
model: QFileSystemModel = tree_view.model()
57+
root_index = tree_view.rootIndex()
58+
if root_index.isValid():
59+
return Path(model.filePath(root_index))
60+
return Path.cwd()
61+
62+
63+
def _show_context_menu(pos, tree_view: QTreeView, main_window) -> None:
64+
word = language_wrapper.language_word_dict
65+
index = tree_view.indexAt(pos)
66+
path = _get_path_from_index(tree_view, index)
67+
68+
menu = QMenu(tree_view)
69+
70+
# --- File / Folder creation ---
71+
new_file_act = menu.addAction(word.get("file_tree_ctx_new_file"))
72+
new_folder_act = menu.addAction(word.get("file_tree_ctx_new_folder"))
73+
menu.addSeparator()
74+
75+
# --- Operations on selected item ---
76+
rename_act = menu.addAction(word.get("file_tree_ctx_rename"))
77+
delete_act = menu.addAction(word.get("file_tree_ctx_delete"))
78+
rename_act.setEnabled(path is not None)
79+
delete_act.setEnabled(path is not None)
80+
menu.addSeparator()
81+
82+
# --- Clipboard ---
83+
copy_path_act = menu.addAction(word.get("file_tree_ctx_copy_path"))
84+
copy_rel_path_act = menu.addAction(word.get("file_tree_ctx_copy_relative_path"))
85+
copy_path_act.setEnabled(path is not None)
86+
copy_rel_path_act.setEnabled(path is not None)
87+
menu.addSeparator()
88+
89+
# --- Explorer ---
90+
reveal_act = menu.addAction(word.get("file_tree_ctx_reveal_in_explorer"))
91+
reveal_act.setEnabled(path is not None)
92+
93+
action = menu.exec(QCursor.pos())
94+
if action is None:
95+
return
96+
97+
if action == new_file_act:
98+
_action_new_file(tree_view, path)
99+
elif action == new_folder_act:
100+
_action_new_folder(tree_view, path)
101+
elif action == rename_act:
102+
_action_rename(tree_view, main_window, path)
103+
elif action == delete_act:
104+
_action_delete(tree_view, main_window, path)
105+
elif action == copy_path_act:
106+
_action_copy_path(tree_view, path, relative=False)
107+
elif action == copy_rel_path_act:
108+
_action_copy_path(tree_view, path, relative=True)
109+
elif action == reveal_act:
110+
_action_reveal_in_explorer(path)
111+
112+
113+
# --------------- actions ---------------
114+
115+
def _resolve_parent_dir(tree_view: QTreeView, path: Path | None) -> Path:
116+
"""Return the directory where a new file/folder should be created."""
117+
if path is not None:
118+
return path if path.is_dir() else path.parent
119+
return _get_tree_root_path(tree_view)
120+
121+
122+
def _action_new_file(tree_view: QTreeView, path: Path | None) -> None:
123+
word = language_wrapper.language_word_dict
124+
parent = _resolve_parent_dir(tree_view, path)
125+
name, ok = QInputDialog.getText(
126+
tree_view,
127+
word.get("file_tree_ctx_new_file"),
128+
word.get("file_tree_ctx_input_file_name"),
129+
)
130+
if not ok or not name.strip():
131+
return
132+
new_path = parent / name.strip()
133+
if new_path.exists():
134+
QMessageBox.warning(
135+
tree_view,
136+
word.get("file_tree_ctx_error"),
137+
word.get("file_tree_ctx_already_exists").format(name=str(new_path)),
138+
)
139+
return
140+
new_path.parent.mkdir(parents=True, exist_ok=True)
141+
new_path.touch()
142+
143+
144+
def _action_new_folder(tree_view: QTreeView, path: Path | None) -> None:
145+
word = language_wrapper.language_word_dict
146+
parent = _resolve_parent_dir(tree_view, path)
147+
name, ok = QInputDialog.getText(
148+
tree_view,
149+
word.get("file_tree_ctx_new_folder"),
150+
word.get("file_tree_ctx_input_folder_name"),
151+
)
152+
if not ok or not name.strip():
153+
return
154+
new_path = parent / name.strip()
155+
if new_path.exists():
156+
QMessageBox.warning(
157+
tree_view,
158+
word.get("file_tree_ctx_error"),
159+
word.get("file_tree_ctx_already_exists").format(name=str(new_path)),
160+
)
161+
return
162+
new_path.mkdir(parents=True)
163+
164+
165+
def _find_editor_for_file(main_window, file_path: Path) -> EditorWidget | None:
166+
"""Find the EditorWidget that has the given file open."""
167+
path_str = str(file_path)
168+
for i in range(main_window.tab_widget.count()):
169+
widget = main_window.tab_widget.widget(i)
170+
if isinstance(widget, EditorWidget) and widget.current_file is not None:
171+
if str(Path(widget.current_file)) == path_str:
172+
return widget
173+
return None
174+
175+
176+
def _action_rename(tree_view: QTreeView, main_window, path: Path | None) -> None:
177+
if path is None:
178+
return
179+
word = language_wrapper.language_word_dict
180+
new_name, ok = QInputDialog.getText(
181+
tree_view,
182+
word.get("file_tree_ctx_rename"),
183+
word.get("file_tree_ctx_input_new_name").format(name=path.name),
184+
text=path.name,
185+
)
186+
if not ok or not new_name.strip() or new_name.strip() == path.name:
187+
return
188+
target = path.parent / new_name.strip()
189+
if target.exists():
190+
QMessageBox.warning(
191+
tree_view,
192+
word.get("file_tree_ctx_error"),
193+
word.get("file_tree_ctx_already_exists").format(name=str(target)),
194+
)
195+
return
196+
197+
# If this file is currently open in an editor tab, update the tab
198+
editor = _find_editor_for_file(main_window, path)
199+
path.rename(target)
200+
if editor is not None and target.is_file():
201+
editor.current_file = str(target)
202+
editor.code_edit.current_file = str(target)
203+
editor.rename_self_tab()
204+
205+
206+
def _action_delete(tree_view: QTreeView, main_window, path: Path | None) -> None:
207+
if path is None:
208+
return
209+
word = language_wrapper.language_word_dict
210+
reply = QMessageBox.question(
211+
tree_view,
212+
word.get("file_tree_ctx_confirm_delete"),
213+
word.get("file_tree_ctx_confirm_delete_message").format(name=str(path)),
214+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
215+
)
216+
if reply != QMessageBox.StandardButton.Yes:
217+
return
218+
219+
# Close editor tab if this file is open
220+
editor = _find_editor_for_file(main_window, path)
221+
if editor is not None:
222+
idx = main_window.tab_widget.indexOf(editor)
223+
if idx >= 0:
224+
editor.close()
225+
main_window.tab_widget.removeTab(idx)
226+
227+
if path.is_dir():
228+
shutil.rmtree(path)
229+
else:
230+
path.unlink()
231+
232+
233+
def _action_copy_path(tree_view: QTreeView, path: Path | None, relative: bool = False) -> None:
234+
if path is None:
235+
return
236+
if relative:
237+
base = _get_tree_root_path(tree_view)
238+
try:
239+
text = str(path.relative_to(base))
240+
except ValueError:
241+
text = str(path)
242+
else:
243+
text = str(path)
244+
clipboard = QApplication.clipboard()
245+
clipboard.setText(text)
246+
247+
248+
def _action_reveal_in_explorer(path: Path | None) -> None:
249+
if path is None:
250+
return
251+
import subprocess
252+
import sys
253+
target = path if path.is_dir() else path.parent
254+
if sys.platform == "win32":
255+
os.startfile(str(target))
256+
elif sys.platform == "darwin":
257+
subprocess.Popen(["open", str(target)])
258+
else:
259+
subprocess.Popen(["xdg-open", str(target)])

pybreeze/pybreeze_ui/editor_main/main_ui.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from qt_material import apply_stylesheet
1313

1414
from pybreeze.extend_multi_language.update_language_dict import update_language_dict
15+
from pybreeze.pybreeze_ui.editor_main.file_tree_context_menu import setup_file_tree_context_menu
1516
from pybreeze.pybreeze_ui.menu.build_menubar import add_menu_to_menubar
1617
from pybreeze.pybreeze_ui.syntax.syntax_extend import \
1718
syntax_extend_package
@@ -66,6 +67,9 @@ def __init__(self, debug_mode: bool = False, show_system_tray_ray: bool = False,
6667
for widget_name, widget in EDITOR_EXTEND_TAB.items():
6768
self.tab_widget.addTab(widget(), widget_name)
6869

70+
# File tree context menu (right-click)
71+
setup_file_tree_context_menu(self)
72+
6973
if debug_mode:
7074
close_timer = QTimer(self)
7175
close_timer.setInterval(10000)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
66

77
[project]
88
name = "pybreeze"
9-
version = "1.0.17"
9+
version = "1.0.18"
1010
authors = [
1111
{ name = "JE-Chen", email = "jechenmailman@gmail.com" },
1212
]

0 commit comments

Comments
 (0)