CPP-33475: Create the Qt plugin

Split a part of QML plugin with core functionality for paths detection and
with icons specific to Qt file types.
For now the functionality is used in CLion and Python plugin.

This change allows to open `.ui` files with detected `designer` tool which didn't
work in CLion before.
The icons are also updated to the more up-to-date format which honors new UI.

IJ-CR-107960

GitOrigin-RevId: dffc1706197ebc568db43ff85f324c1520e919f6
This commit is contained in:
Ivan Donchevskii
2023-06-12 21:35:58 +00:00
committed by intellij-monorepo-bot
parent b6c003a833
commit 5db8fd0e1a
10 changed files with 99 additions and 173 deletions
-4
View File
@@ -19,10 +19,6 @@ public final class PythonIcons {
/** 16x16 */ public static final @NotNull Icon CythonFile = load("icons/com/jetbrains/cython/cythonFile.svg", 2119301841, 0);
}
public static final class Pyqt {
/** 16x16 */ public static final @NotNull Icon UiForm = load("icons/com/jetbrains/pyqt/uiForm.svg", 20186536, 0);
}
public static final class Python {
/** 16x16 */ public static final @NotNull Icon Anaconda = load("icons/com/jetbrains/python/anaconda.svg", 1315162781, 0);
/** 16x16 */ public static final @NotNull Icon CommandQueue = load("icons/com/jetbrains/python/commandQueue.svg", -127964299, 2);
@@ -41,5 +41,6 @@
<orderEntry type="module" module-name="intellij.dev" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.evaluationPlugin" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.evaluationPlugin.languages.python" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.qt.plugin" scope="RUNTIME" />
</component>
</module>
@@ -121,5 +121,6 @@
<orderEntry type="module" module-name="intellij.python.community.core.impl" />
<orderEntry type="module" module-name="intellij.platform.core" />
<orderEntry type="module" module-name="intellij.python.psi" />
<orderEntry type="module" module-name="intellij.qt.common" />
</component>
</module>
@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<polygon fill="#62B543" fill-opacity=".7" points="1 16 16 16 16 9 1 9"/>
<polygon fill="#9AA7B0" fill-opacity=".8" points="7 1 3 5 7 5"/>
<polygon fill="#9AA7B0" fill-opacity=".8" points="8 1 8 6 3 6 3 8 13 8 13 1"/>
<path fill="#231F20" fill-opacity=".7" d="M5.1129,4.4577 L4.5678,5.0649 L3.9882,4.5474 C3.5811,4.8372 3.0774,5.0028 2.5185,5.0028 C1.035,5.0028 0,3.8781 0,2.5188 C0,1.1457 1.0488,0.0072 2.5323,0.0072 C4.0158,0.0072 5.0508,1.1319 5.0508,2.4912 C5.0508,3.057 4.8714,3.5676 4.5609,3.9885 L5.1129,4.4577 Z M2.53187806,4.1162 C2.82986878,4.1162 3.08899114,4.04473548 3.31572321,3.9148 L2.53187806,3.23913548 L3.093645,2.604426 L3.890446,3.332065 C4.0329633,3.08518758 4.0607,2.84283226 4.0607,2.51799355 L4.0607,2.505 C4.0607,1.62143871 3.41937215,0.8938 2.51892194,0.8938 C1.61847173,0.8938 1,1.60844516 1,2.49200645 L1,2.505 C1,3.38856129 1.63142785,4.1162 2.53187806,4.1162 Z" transform="translate(3 10)"/>
<path fill="#231F20" fill-opacity=".7" d="M2.2206,5 C1.562,5 0.9958,4.778 0.9958,3.9196 L0.9958,2 L0.5,2 L0.5,1 L0.9958,1 L0.9958,-4.4408921e-16 L2,-4.4408921e-16 L2,1 L3,1 L3,2 L2,2 L2,3.579 C2,3.9268 2.1466,4 2.45,4 C2.6498,4 3.0224,3.923 3.2,3.8342 L3.2,4.7668 C2.978,4.8926 2.761,5 2.2206,5 Z" transform="translate(8 10)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -615,7 +615,6 @@ action.PyManagePackages.text=Manage Python Packages\u2026
action.RunPythonToolwindowAction.text=Python Console
# qt actions
qt.error.failed.run.qt.designer=Failed to run Qt Designer: {0}
qt.qrc.compiler.select.output.path=Select Output Path:
qt.qrc.compile.files=Compile {0} .qrc Files
qt.qrc.compile=Compile {0}
@@ -623,9 +622,6 @@ qt.compile.qrc.file=Compile .qrc File
qt.run.tab.title.compile.qrc=Compile .qrc
qt.run.error=Error running {0}: {1}
qt.cannot.find.pyrcc4.or.pysidercc=Could not find pyrcc4 or pyside-rcc for the selected Python interpreter
qt.run.designer.error=Error
qt.ui.designer.form.filetype.description=Qt UI Designer form
qt.ui.designer.form.filetype.display.name=Qt UI Designer form
python.module.name=Python Module
python.module.description=Python modules are used for developing <b>Python</b> applications.
+4 -2
View File
@@ -46,8 +46,6 @@
extensions="pyi"
implementationClass="com.jetbrains.python.pyi.PyiFileType"
fieldName="INSTANCE"/>
<fileType name="Qt UI file" extensions="ui" implementationClass="com.jetbrains.pyqt.QtUIFileType" fieldName="INSTANCE"/>
<fileType name="XML" language="XML" extensions="qrc"/>
<editorHighlighterProvider filetype="Python" implementationClass="com.jetbrains.python.PyEditorHighlighterProvider"/>
<lang.syntaxHighlighterFactory language="Python" implementationClass="com.jetbrains.python.highlighting.PySyntaxHighlighterFactory"/>
<lang.braceMatcher language="Python" implementationClass="com.jetbrains.python.PyBraceMatcher"/>
@@ -728,6 +726,10 @@
<PythonPackagingToolwindowActionProvider implementation="com.jetbrains.python.packaging.conda.CondaPackagingToolwindowActionProvider"/>
</extensions>
<extensions defaultExtensionNs="qt">
<pathProvider implementation="com.jetbrains.pyqt.PyQtPathProvider"/>
</extensions>
<actions>
<action overrides="true" id="ForceStepInto" class="com.jetbrains.python.debugger.PyForceStepIntoAction"
icon="PlatformDebuggerImplIcons.Actions.Force_step_into"/>
@@ -21,6 +21,7 @@ import com.jetbrains.python.PyBundle;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.nio.file.Path;
public class CompileQrcAction extends AnAction {
@@ -30,9 +31,9 @@ public class CompileQrcAction extends AnAction {
VirtualFile[] vFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
assert vFiles != null;
Module module = e.getData(PlatformCoreDataKeys.MODULE);
String path = QtFileType.findQtTool(module, "pyrcc4");
Path path = PyQtPathProvider.findQtToolImpl(module, "pyrcc4");
if (path == null) {
path = QtFileType.findQtTool(module, "pyside-rcc");
path = PyQtPathProvider.findQtToolImpl(module, "pyside-rcc");
}
if (path == null) {
//noinspection DialogTitleCapitalization
@@ -46,7 +47,7 @@ public class CompileQrcAction extends AnAction {
return;
}
GeneralCommandLine cmdLine = new GeneralCommandLine(path, "-o", dialog.getOutputPath());
GeneralCommandLine cmdLine = new GeneralCommandLine(path.toString(), "-o", dialog.getOutputPath());
for (VirtualFile vFile : vFiles) {
cmdLine.addParameter(vFile.getPath());
}
@@ -0,0 +1,89 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.pyqt;
import com.intellij.lang.qt.QtPathProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.sdk.PythonSdkUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.List;
public class PyQtPathProvider implements QtPathProvider {
@Override
public @Nullable Path findQtTool(@NotNull Project project, @NotNull VirtualFile file, @NotNull String toolName) {
return findQtToolImpl(ModuleUtilCore.findModuleForFile(file, project), toolName);
}
@Override
public @Nullable Path findQtTool(@NotNull Project project, @NotNull String toolName) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
Module[] allModules = moduleManager.getModules();
for (Module module : allModules) {
if (!module.isLoaded() || PythonSdkUtil.findPythonSdk(module) == null) continue;
@Nullable Path toolPath = findQtToolImpl(module, DEFAULT_TOOL_NAME);
if (toolPath != null) return toolPath;
}
return null;
}
public static @Nullable Path findQtToolImpl(@Nullable Module module, String toolName) {
if (SystemInfo.isWindows) {
if (module == null) {
return null;
}
Sdk sdk = PythonSdkUtil.findPythonSdk(module);
if (sdk == null) {
return null;
}
return ReadAction.compute(() -> {
// In Python 3.10 the package name is PySide2 for Qt5 and PySide6 for Qt6
Path tool = findToolInPackage(toolName, module, "PySide6");
if (tool != null) {
return tool;
}
tool = findToolInPackage(toolName, module, "PySide2");
if (tool != null) {
return tool;
}
tool = findToolInPackage(toolName, module, "PyQt4");
if (tool != null) {
return tool;
}
return findToolInPackage(toolName, module, "PySide");
});
}
// TODO
return null;
}
@Nullable
private static Path findToolInPackage(String toolName, Module module, String name) {
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(name),
PyResolveImportUtil.fromModule(module));
return StreamEx.of(results).select(PsiDirectory.class)
.map(directory -> directory.getVirtualFile().findChild(toolName + ".exe"))
.nonNull()
.map(VirtualFile::toNioPath)
.findFirst()
.orElse(null);
}
}
@@ -1,119 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.pyqt;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.fileTypes.INativeFileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.NlsContexts.Label;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
import com.jetbrains.python.sdk.PythonSdkUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
public abstract class QtFileType extends LanguageFileType implements INativeFileType {
private final String myName;
private final @Nls String myDisplayName;
private final @Label String myDescription;
private final String myDefaultExtension;
QtFileType(@NonNls String name, @NotNull @Nls String displayName, @Label String description, String defaultExtension) {
super(XMLLanguage.INSTANCE, true);
myName = name;
myDisplayName = displayName;
myDescription = description;
myDefaultExtension = defaultExtension;
}
@NotNull
@NonNls
@Override
public String getName() {
return myName;
}
@NotNull
@Label
@Override
public String getDescription() {
return myDescription;
}
@Nls
@Override
public @NotNull String getDisplayName() {
return myDisplayName;
}
@NotNull
@Override
public String getDefaultExtension() {
return myDefaultExtension;
}
@Override
public boolean openFileInAssociatedApplication(Project project, @NotNull VirtualFile file) {
String qtTool = findQtTool(ModuleUtilCore.findModuleForFile(file, project), getToolName());
if (qtTool == null) {
return false;
}
try {
Runtime.getRuntime().exec(new String[]{qtTool, file.getPath()});
}
catch (IOException e) {
Messages.showErrorDialog(project, PyBundle.message("qt.error.failed.run.qt.designer", e.getMessage()),
PyBundle.message("qt.run.designer.error"));
}
return true;
}
public static String findQtTool(Module module, String toolName) {
if (SystemInfo.isWindows) {
if (module == null) {
return null;
}
Sdk sdk = PythonSdkUtil.findPythonSdk(module);
if (sdk == null) {
return null;
}
String tool = findToolInPackage(toolName, module, "PyQt4");
if (tool != null) {
return tool;
}
return findToolInPackage(toolName, module, "PySide");
}
// TODO
return null;
}
@Nullable
private static String findToolInPackage(String toolName, Module module, String name) {
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(name),
PyResolveImportUtil.fromModule(module));
return StreamEx.of(results).select(PsiDirectory.class)
.map(directory -> directory.getVirtualFile().findChild(toolName + ".exe"))
.nonNull()
.map(VirtualFile::getPath)
.findFirst()
.orElse(null);
}
protected abstract String getToolName();
}
@@ -1,32 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.pyqt;
import com.intellij.openapi.fileTypes.FileType;
import com.jetbrains.python.PyBundle;
import icons.PythonIcons;
import javax.swing.*;
public class QtUIFileType extends QtFileType implements FileType {
public static final QtUIFileType INSTANCE = new QtUIFileType();
private QtUIFileType() {
super("Qt UI file", PyBundle.message("qt.ui.designer.form.filetype.display.name"), PyBundle.message("qt.ui.designer.form.filetype.description"), "ui");
}
@Override
public Icon getIcon() {
return PythonIcons.Pyqt.UiForm;
}
@Override
protected String getToolName() {
return "designer";
}
@Override
public boolean useNativeIcon() {
return false;
}
}