feat(gui): add Preview Tab Feature (PR #2474)
* feat(gui): created simple preview mode (#756) * feat(gui): fixed opening code preview tab, if open from available tabs * fix(gui): rollback mouse events for tree
This commit is contained in:
@@ -104,6 +104,7 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
|
||||
private boolean showHeapUsageBar = false;
|
||||
private boolean alwaysSelectOpened = false;
|
||||
private boolean enablePreviewTab = false;
|
||||
private boolean useAlternativeFileDialog = false;
|
||||
|
||||
private Map<String, WindowLocation> windowPos = new HashMap<>();
|
||||
@@ -319,6 +320,15 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
partialSync(settings -> settings.alwaysSelectOpened = alwaysSelectOpened);
|
||||
}
|
||||
|
||||
public boolean isEnablePreviewTab() {
|
||||
return enablePreviewTab;
|
||||
}
|
||||
|
||||
public void setEnablePreviewTab(boolean enablePreviewTab) {
|
||||
this.enablePreviewTab = enablePreviewTab;
|
||||
partialSync(settings -> settings.enablePreviewTab = enablePreviewTab);
|
||||
}
|
||||
|
||||
public boolean isUseAlternativeFileDialog() {
|
||||
return useAlternativeFileDialog;
|
||||
}
|
||||
|
||||
@@ -878,11 +878,11 @@ public class MainWindow extends JFrame {
|
||||
JResource res = (JResource) obj;
|
||||
ResourceFile resFile = res.getResFile();
|
||||
if (resFile != null && JResource.isSupportedForView(resFile.getType())) {
|
||||
tabsController.selectTab(res);
|
||||
tabsController.selectTab(res, true);
|
||||
return true;
|
||||
}
|
||||
} else if (obj instanceof JNode) {
|
||||
tabsController.codeJump((JNode) obj);
|
||||
tabsController.codeJump((JNode) obj, true);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -1062,6 +1062,12 @@ public class MainWindow extends JFrame {
|
||||
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), Icons.FLAT_PKG);
|
||||
flatPkgMenuItem.setState(isFlattenPackage);
|
||||
|
||||
JCheckBoxMenuItem enablePreviewTabMenuItem = new JCheckBoxMenuItem(NLS.str("menu.enable_preview_tab"));
|
||||
enablePreviewTabMenuItem.setState(settings.isEnablePreviewTab());
|
||||
enablePreviewTabMenuItem.addActionListener(event -> {
|
||||
settings.setEnablePreviewTab(!settings.isEnablePreviewTab());
|
||||
});
|
||||
|
||||
JCheckBoxMenuItem heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
|
||||
heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar());
|
||||
heapUsageBarMenuItem.addActionListener(event -> {
|
||||
@@ -1153,6 +1159,7 @@ public class MainWindow extends JFrame {
|
||||
view.add(quickTabsAction.makeCheckBoxMenuItem());
|
||||
view.add(flatPkgMenuItem);
|
||||
view.addSeparator();
|
||||
view.add(enablePreviewTabMenuItem);
|
||||
view.add(syncAction);
|
||||
view.add(alwaysSelectOpened);
|
||||
view.addSeparator();
|
||||
|
||||
@@ -9,6 +9,7 @@ public class TabBlueprint {
|
||||
private boolean pinned;
|
||||
private boolean bookmarked;
|
||||
private boolean hidden;
|
||||
private boolean previewTab;
|
||||
|
||||
public TabBlueprint(JNode node) {
|
||||
this.node = Objects.requireNonNull(node);
|
||||
@@ -50,6 +51,14 @@ public class TabBlueprint {
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
public boolean isPreviewTab() {
|
||||
return previewTab;
|
||||
}
|
||||
|
||||
public void setPreviewTab(boolean previewTab) {
|
||||
this.previewTab = previewTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -68,10 +77,11 @@ public class TabBlueprint {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabBlueprint{node=" + node
|
||||
+ ", bookmarked=" + bookmarked
|
||||
+ ", pinned=" + pinned
|
||||
+ ", hidden=" + hidden
|
||||
+ '}';
|
||||
return "TabBlueprint{" + "node="
|
||||
+ node + ", pinned="
|
||||
+ pinned + ", bookmarked="
|
||||
+ bookmarked + ", hidden="
|
||||
+ hidden + ", previewTab="
|
||||
+ previewTab + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class TabComponent extends JPanel {
|
||||
icon = new OverlayIcon(node.getIcon());
|
||||
|
||||
label = new NodeLabel(buildTabTitle(node), node.disableHtml());
|
||||
label.setFont(getLabelFont());
|
||||
makeLabelFont();
|
||||
String toolTip = contentPanel.getTabTooltip();
|
||||
if (toolTip != null) {
|
||||
setToolTipText(toolTip);
|
||||
@@ -174,6 +174,16 @@ public class TabComponent extends JPanel {
|
||||
tabsController.setTabBookmarked(getNode(), bookmarked);
|
||||
}
|
||||
|
||||
private void makeLabelFont() {
|
||||
boolean previewTab = getBlueprint().isPreviewTab();
|
||||
if (previewTab) {
|
||||
Font newLabelFont = new Font(label.getFont().getName(), Font.ITALIC, label.getFont().getSize());
|
||||
label.setFont(newLabelFont);
|
||||
} else {
|
||||
label.setFont(getLabelFont());
|
||||
}
|
||||
}
|
||||
|
||||
private void addListenerForDnd() {
|
||||
if (tabbedPane.getDnd() == null) {
|
||||
return;
|
||||
|
||||
@@ -32,7 +32,6 @@ public class TabsController {
|
||||
private final List<ITabStatesListener> listeners = new ArrayList<>();
|
||||
|
||||
private boolean forceClose;
|
||||
|
||||
private @Nullable TabBlueprint selectedTab;
|
||||
|
||||
public TabsController(MainWindow mainWindow) {
|
||||
@@ -57,15 +56,20 @@ public class TabsController {
|
||||
}
|
||||
|
||||
public TabBlueprint openTab(JNode node) {
|
||||
return openTab(node, false);
|
||||
return openTab(node, false, false);
|
||||
}
|
||||
|
||||
public TabBlueprint openTab(JNode node, boolean hidden) {
|
||||
return openTab(node, hidden, false);
|
||||
}
|
||||
|
||||
public TabBlueprint openTab(JNode node, boolean hidden, boolean preview) {
|
||||
TabBlueprint blueprint = getTabByNode(node);
|
||||
if (blueprint == null) {
|
||||
TabBlueprint newBlueprint = new TabBlueprint(node);
|
||||
tabsMap.put(node, newBlueprint);
|
||||
newBlueprint.setHidden(hidden);
|
||||
newBlueprint.setPreviewTab(preview);
|
||||
tabsMap.put(node, newBlueprint);
|
||||
listeners.forEach(l -> l.onTabOpen(newBlueprint));
|
||||
if (hidden) {
|
||||
listeners.forEach(l -> l.onTabVisibilityChange(newBlueprint));
|
||||
@@ -76,20 +80,45 @@ public class TabsController {
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
public TabBlueprint previewTab(JNode node) {
|
||||
TabBlueprint blueprint = getPreviewTab();
|
||||
if (blueprint != null) {
|
||||
closeTabForce(blueprint);
|
||||
}
|
||||
|
||||
blueprint = openTab(node, false, true);
|
||||
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
public void selectTab(JNode node) {
|
||||
selectTab(node, false);
|
||||
}
|
||||
|
||||
public void selectTab(JNode node, boolean fromTree) {
|
||||
if (selectedTab != null && selectedTab.getNode() == node) {
|
||||
// already selected
|
||||
return;
|
||||
}
|
||||
TabBlueprint blueprint = openTab(node);
|
||||
selectedTab = blueprint;
|
||||
listeners.forEach(l -> l.onTabSelect(blueprint));
|
||||
if (mainWindow.getSettings().isEnablePreviewTab() && fromTree) {
|
||||
selectedTab = previewTab(node);
|
||||
} else {
|
||||
selectedTab = openTab(node);
|
||||
}
|
||||
listeners.forEach(l -> l.onTabSelect(selectedTab));
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump to node definition
|
||||
*/
|
||||
public void codeJump(JNode node) {
|
||||
codeJump(node, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump to node definition
|
||||
*/
|
||||
public void codeJump(JNode node, boolean fromTree) {
|
||||
JClass parentCls = node.getJParent();
|
||||
if (parentCls != null) {
|
||||
JavaClass cls = node.getJParent().getCls();
|
||||
@@ -97,23 +126,23 @@ public class TabsController {
|
||||
JavaClass codeParent = cls.getTopParentClass();
|
||||
if (!Objects.equals(codeParent, origTopCls)) {
|
||||
JClass jumpCls = mainWindow.getCacheObject().getNodeCache().makeFrom(codeParent);
|
||||
loadCodeWithUIAction(jumpCls, () -> jumpToInnerClass(node, codeParent, jumpCls));
|
||||
loadCodeWithUIAction(jumpCls, () -> jumpToInnerClass(node, codeParent, jumpCls, fromTree));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not an inline node, jump normally
|
||||
if (node.getPos() > 0) {
|
||||
codeJump(new JumpPosition(node));
|
||||
codeJump(new JumpPosition(node), fromTree);
|
||||
return;
|
||||
}
|
||||
if (node.getRootClass() == null) {
|
||||
// not a class, select tab without position scroll
|
||||
selectTab(node);
|
||||
selectTab(node, fromTree);
|
||||
return;
|
||||
}
|
||||
// node need loading
|
||||
loadCodeWithUIAction(node.getRootClass(), () -> codeJump(new JumpPosition(node)));
|
||||
loadCodeWithUIAction(node.getRootClass(), () -> codeJump(new JumpPosition(node), fromTree));
|
||||
}
|
||||
|
||||
private void loadCodeWithUIAction(JClass cls, Runnable action) {
|
||||
@@ -129,12 +158,12 @@ public class TabsController {
|
||||
/**
|
||||
* Search and jump to original node in jumpCls
|
||||
*/
|
||||
private void jumpToInnerClass(JNode node, JavaClass codeParent, JClass jumpCls) {
|
||||
private void jumpToInnerClass(JNode node, JavaClass codeParent, JClass jumpCls, boolean fromTree) {
|
||||
codeParent.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||
ICodeNodeRef declNode = ((NodeDeclareRef) ann).getNode();
|
||||
if (declNode.equals(node.getJavaNode().getCodeNodeRef())) {
|
||||
codeJump(new JumpPosition(jumpCls, pos));
|
||||
codeJump(new JumpPosition(jumpCls, pos), fromTree);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -142,13 +171,17 @@ public class TabsController {
|
||||
});
|
||||
}
|
||||
|
||||
public void codeJump(JumpPosition pos) {
|
||||
codeJump(pos, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link TabsController#codeJump(JNode)} method
|
||||
*/
|
||||
public void codeJump(JumpPosition pos) {
|
||||
public void codeJump(JumpPosition pos, boolean fromTree) {
|
||||
JumpPosition currentPosition = mainWindow.getTabbedPane().getCurrentPosition();
|
||||
if (selectedTab == null || selectedTab.getNode() != pos.getNode()) {
|
||||
selectTab(pos.getNode());
|
||||
selectTab(pos.getNode(), fromTree);
|
||||
}
|
||||
listeners.forEach(l -> l.onTabCodeJump(selectedTab, currentPosition, pos));
|
||||
}
|
||||
@@ -298,6 +331,11 @@ public class TabsController {
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public TabBlueprint getPreviewTab() {
|
||||
return tabsMap.values().stream()
|
||||
.filter(TabBlueprint::isPreviewTab).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public void restoreEditorViewState(EditorViewState viewState) {
|
||||
JNode node = viewState.getNode();
|
||||
TabBlueprint blueprint = openTab(node, viewState.isHidden());
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=Keine aktuellen Projekte
|
||||
menu.preferences=Einstellungen
|
||||
menu.sync=Mit Editor synchronisieren
|
||||
menu.flatten=Codepaket erweitern
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=Speicherverbrauchsleiste anzeigen
|
||||
menu.alwaysSelectOpened=Immer geöffnete Datei/Klasse auswählen
|
||||
menu.dock_log=Dock-Protokollanzeige
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=No recent projects
|
||||
menu.preferences=Preferences
|
||||
menu.sync=Select in Tree
|
||||
menu.flatten=Use Flatten Packages
|
||||
menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=Show Memory Usage Bar
|
||||
menu.alwaysSelectOpened=Auto Select in Tree
|
||||
menu.dock_log=Dock Log Viewer
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.view=Vista
|
||||
menu.preferences=Preferencias
|
||||
menu.sync=Sincronizar con el editor
|
||||
menu.flatten=Mostrar paquetes en vista plana
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
#menu.heapUsageBar=Show memory usage bar
|
||||
#menu.alwaysSelectOpened=Always Select Opened File/Class
|
||||
#menu.dock_log=Dock log viewer
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=Tidak ada proyek terbaru
|
||||
menu.preferences=Preferensi
|
||||
menu.sync=Sinkronisasi dengan editor
|
||||
menu.flatten=Tampilkan paket yang diratakan
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=Tampilkan penggunaan memori
|
||||
menu.alwaysSelectOpened=Selalu Pilih Berkas/Kelas yang Terbuka
|
||||
menu.dock_log=Kaitkan pemantau log
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=최근 프로젝트 없음
|
||||
menu.preferences=설정
|
||||
menu.sync=에디터와 동기화
|
||||
menu.flatten=플랫 패키지 표시
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=메모리 사용량 표시
|
||||
menu.alwaysSelectOpened=항상 열린 파일/클래스 선택
|
||||
#menu.dock_log=Dock log viewer
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=Nenhum projeto recente
|
||||
menu.preferences=Preferências
|
||||
menu.sync=Sincronizar com editor
|
||||
menu.flatten=Mostrar pacotes achatados
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=Mostrar uso de memória
|
||||
menu.alwaysSelectOpened=Sempre selecionar arquivo/classe aberta
|
||||
#menu.dock_log=Dock log viewer
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=Нет недавно открытых проектов
|
||||
menu.preferences=Параметры
|
||||
menu.sync=Синхр. файлы с редактором
|
||||
menu.flatten=Плоская структура пакетов
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=Использование ОЗУ
|
||||
menu.alwaysSelectOpened=Выбирать открытый файл/класс
|
||||
menu.dock_log=Просмотр логов в панели
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=没有最近的项目
|
||||
menu.preferences=首选项
|
||||
menu.sync=与编辑器同步
|
||||
menu.flatten=展开显示代码包
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=显示内存使用栏
|
||||
menu.alwaysSelectOpened=始终选中打开的文件/类
|
||||
menu.dock_log=停靠日志查看器
|
||||
|
||||
@@ -7,6 +7,7 @@ menu.no_recent_projects=無近期專案
|
||||
menu.preferences=選項
|
||||
menu.sync=與編輯器同步
|
||||
menu.flatten=展開顯示套件
|
||||
#menu.enable_preview_tab=Enable Preview Tab
|
||||
menu.heapUsageBar=顯示記憶體使用率條
|
||||
menu.alwaysSelectOpened=總是選擇已開啟的檔案/類別
|
||||
menu.dock_log=固定記錄檔檢視器
|
||||
|
||||
Reference in New Issue
Block a user