QGIS/scripts/qgis_fixes/fix_pyqt.py
2024-11-29 15:38:02 +01:00

638 lines
20 KiB
Python

"""Migrate imports of PyQt4 to PyQt wrapper
"""
# Author: Juergen E. Fischer
# Adapted from fix_urllib
from lib2to3 import fixer_base
from lib2to3.fixer_util import (
Comma,
FromImport,
Leaf,
Name,
Newline,
Node,
find_indentation,
syms,
)
# Local imports
from lib2to3.fixes.fix_imports import FixImports, alternates
MAPPING = {
"PyQt4.QtGui": [
(
"qgis.PyQt.QtGui",
[
"QIcon",
"QCursor",
"QColor",
"QDesktopServices",
"QFont",
"QFontMetrics",
"QKeySequence",
"QStandardItemModel",
"QStandardItem",
"QClipboard",
"QPixmap",
"QDoubleValidator",
"QPainter",
"QPen",
"QBrush",
"QPalette",
"QPainterPath",
"QImage",
"QPolygonF",
"QFontMetricsF",
"QGradient",
"QIntValidator",
],
),
(
"qgis.PyQt.QtWidgets",
[
"QAbstractButton",
"QAbstractGraphicsShapeItem",
"QAbstractItemDelegate",
"QAbstractItemView",
"QAbstractScrollArea",
"QAbstractSlider",
"QAbstractSpinBox",
"QAbstractTableModel",
"QAction",
"QActionGroup",
"QApplication",
"QBoxLayout",
"QButtonGroup",
"QCalendarWidget",
"QCheckBox",
"QColorDialog",
"QColumnView",
"QComboBox",
"QCommandLinkButton",
"QCommonStyle",
"QCompleter",
"QDataWidgetMapper",
"QDateEdit",
"QDateTimeEdit",
"QDesktopWidget",
"QDial",
"QDialog",
"QDialogButtonBox",
"QDirModel",
"QDockWidget",
"QDoubleSpinBox",
"QErrorMessage",
"QFileDialog",
"QFileIconProvider",
"QFileSystemModel",
"QFocusFrame",
"QFontComboBox",
"QFontDialog",
"QFormLayout",
"QFrame",
"QGesture",
"QGestureEvent",
"QGestureRecognizer",
"QGraphicsAnchor",
"QGraphicsAnchorLayout",
"QGraphicsBlurEffect",
"QGraphicsColorizeEffect",
"QGraphicsDropShadowEffect",
"QGraphicsEffect",
"QGraphicsEllipseItem",
"QGraphicsGridLayout",
"QGraphicsItem",
"QGraphicsItemGroup",
"QGraphicsLayout",
"QGraphicsLayoutItem",
"QGraphicsLineItem",
"QGraphicsLinearLayout",
"QGraphicsObject",
"QGraphicsOpacityEffect",
"QGraphicsPathItem",
"QGraphicsPixmapItem",
"QGraphicsPolygonItem",
"QGraphicsProxyWidget",
"QGraphicsRectItem",
"QGraphicsRotation",
"QGraphicsScale",
"QGraphicsScene",
"QGraphicsSceneContextMenuEvent",
"QGraphicsSceneDragDropEvent",
"QGraphicsSceneEvent",
"QGraphicsSceneHelpEvent",
"QGraphicsSceneHoverEvent",
"QGraphicsSceneMouseEvent",
"QGraphicsSceneMoveEvent",
"QGraphicsSceneResizeEvent",
"QGraphicsSceneWheelEvent",
"QGraphicsSimpleTextItem",
"QGraphicsTextItem",
"QGraphicsTransform",
"QGraphicsView",
"QGraphicsWidget",
"QGridLayout",
"QGroupBox",
"QHBoxLayout",
"QHeaderView",
"QInputDialog",
"QItemDelegate",
"QItemEditorCreatorBase",
"QItemEditorFactory",
"QKeyEventTransition",
"QLCDNumber",
"QLabel",
"QLayout",
"QLayoutItem",
"QLineEdit",
"QListView",
"QListWidget",
"QListWidgetItem",
"QMainWindow",
"QMdiArea",
"QMdiSubWindow",
"QMenu",
"QMenuBar",
"QMessageBox",
"QMouseEventTransition",
"QPanGesture",
"QPinchGesture",
"QPlainTextDocumentLayout",
"QPlainTextEdit",
"QProgressBar",
"QProgressDialog",
"QPushButton",
"QRadioButton",
"QRubberBand",
"QScrollArea",
"QScrollBar",
"QShortcut",
"QSizeGrip",
"QSizePolicy",
"QSlider",
"QSpacerItem",
"QSpinBox",
"QSplashScreen",
"QSplitter",
"QSplitterHandle",
"QStackedLayout",
"QStackedWidget",
"QStatusBar",
"QStyle",
"QStyleFactory",
"QStyleHintReturn",
"QStyleHintReturnMask",
"QStyleHintReturnVariant",
"QStyleOption",
"QStyleOptionButton",
"QStyleOptionComboBox",
"QStyleOptionComplex",
"QStyleOptionDockWidget",
"QStyleOptionFocusRect",
"QStyleOptionFrame",
"QStyleOptionGraphicsItem",
"QStyleOptionGroupBox",
"QStyleOptionHeader",
"QStyleOptionMenuItem",
"QStyleOptionProgressBar",
"QStyleOptionRubberBand",
"QStyleOptionSizeGrip",
"QStyleOptionSlider",
"QStyleOptionSpinBox",
"QStyleOptionTab",
"QStyleOptionTabBarBase",
"QStyleOptionTabWidgetFrame",
"QStyleOptionTitleBar",
"QStyleOptionToolBar",
"QStyleOptionToolBox",
"QStyleOptionToolButton",
"QStyleOptionViewItem",
"QStylePainter",
"QStyledItemDelegate",
"QSwipeGesture",
"QSystemTrayIcon",
"QTabBar",
"QTabWidget",
"QTableView",
"QTableWidget",
"QTableWidgetItem",
"QTableWidgetSelectionRange",
"QTapAndHoldGesture",
"QTapGesture",
"QTextBrowser",
"QTextEdit",
"QTimeEdit",
"QToolBar",
"QToolBox",
"QToolButton",
"QToolTip",
"QTreeView",
"QTreeWidget",
"QTreeWidgetItem",
"QTreeWidgetItemIterator",
"QUndoCommand",
"QUndoGroup",
"QUndoStack",
"QUndoView",
"QVBoxLayout",
"QWhatsThis",
"QWidget",
"QWidgetAction",
"QWidgetItem",
"QWizard",
"QWizardPage",
"qApp",
"qDrawBorderPixmap",
"qDrawPlainRect",
"qDrawShadeLine",
"qDrawShadePanel",
"qDrawShadeRect",
"qDrawWinButton",
"qDrawWinPanel",
],
),
(
"qgis.PyQt.QtPrintSupport",
[
"QPrinter",
"QAbstractPrintDialog",
"QPageSetupDialog",
"QPrintDialog",
"QPrintEngine",
"QPrintPreviewDialog",
"QPrintPreviewWidget",
"QPrinterInfo",
],
),
(
"qgis.PyQt.QtCore",
[
"QItemSelectionModel",
"QSortFilterProxyModel",
],
),
],
"PyQt4.QtCore": [
(
"qgis.PyQt.QtCore",
[
"QAbstractItemModel",
"QAbstractTableModel",
"QByteArray",
"QCoreApplication",
"QDataStream",
"QDir",
"QEvent",
"QFile",
"QFileInfo",
"QIODevice",
"QLocale",
"QMimeData",
"QModelIndex",
"QMutex",
"QObject",
"QProcess",
"QSettings",
"QSize",
"QSizeF",
"QTextCodec",
"QThread",
"QThreadPool",
"QTimer",
"QTranslator",
"QUrl",
"Qt",
"pyqtProperty",
"pyqtWrapperType",
"pyqtSignal",
"pyqtSlot",
"qDebug",
"qWarning",
"qVersion",
"QDate",
"QTime",
"QDateTime",
"QRegExp",
"QTemporaryFile",
"QTextStream",
"QVariant",
"QPyNullVariant",
"QRect",
"QRectF",
"QMetaObject",
"QPoint",
"QPointF",
"QDirIterator",
"QEventLoop",
"NULL",
],
),
(
None,
[
"SIGNAL",
"SLOT",
],
),
],
"PyQt4.QtNetwork": [
(
"qgis.PyQt.QtNetwork",
["QNetworkReply", "QNetworkRequest", "QSslCertificate", "QSslKey", "QSsl"],
)
],
"PyQt4.QtXml": [
("qgis.PyQt.QtXml", ["QDomDocument"]),
],
"PyQt4.Qsci": [
(
"qgis.PyQt.Qsci",
[
"QsciAPIs",
"QsciLexerCustom",
"QsciLexerPython",
"QsciScintilla",
"QsciLexerSQL",
"QsciStyle",
],
),
],
"PyQt4.QtWebKit": [
(
"qgis.PyQt.QtWebKitWidgets",
[
"QGraphicsWebView",
"QWebFrame",
"QWebHitTestResult",
"QWebInspector",
"QWebPage",
"QWebView",
],
),
],
"PyQt4.QtTest": [
(
"qgis.PyQt.QtTest",
[
"QTest",
],
),
],
"PyQt4.QtSvg": [
("qgis.PyQt.QtSvg", ["QSvgRenderer", "QSvgGenerator"]),
],
"PyQt4.QtSql": [
("qgis.PyQt.QtSql", ["QSqlDatabase", "QSqlQuery", "QSqlField"]),
],
"PyQt4.uic": [
(
"qgis.PyQt.uic",
[
"loadUiType",
"loadUi",
],
),
],
"PyQt4": [
(
"qgis.PyQt",
[
"QtCore",
"QtGui",
"QtNetwork",
"QtXml",
"QtWebkit",
"QtSql",
"QtSvg",
"Qsci",
"uic",
],
)
],
}
new_mappings = {}
for key, value in MAPPING.items():
match_str = key.replace("PyQt4", "")
match_str = "{}{}".format("qgis.PyQt", match_str)
new_mappings[match_str] = value
MAPPING.update(new_mappings)
def build_pattern():
bare = set()
for old_module, changes in list(MAPPING.items()):
for change in changes:
new_module, members = change
members = alternates(members)
if "." not in old_module:
from_name = "%r" % old_module
else:
dotted = old_module.split(".")
if len(dotted) == 3:
from_name = f"dotted_name<{dotted[0]!r} '.' {dotted[1]!r} '.' {dotted[2]!r}>"
else:
assert len(dotted) == 2
from_name = f"dotted_name<{dotted[0]!r} '.' {dotted[1]!r}>"
yield """import_name< 'import' (module={}
| dotted_as_names< any* module={} any* >) >
""".format(
from_name, from_name
)
yield """import_from< 'from' mod_member={} 'import'
( member={} | import_as_name< member={} 'as' any > |
import_as_names< members=any* >) >
""".format(
from_name, members, members
)
yield """import_from< 'from' mod_member={} 'import' '('
( member={} | import_as_name< member={} 'as' any > |
import_as_names< members=any* >) ')' >
""".format(
from_name, members, members
)
yield """import_from< 'from' module_star=%s 'import' star='*' >
""" % from_name
yield """import_name< 'import'
dotted_as_name< module_as=%s 'as' any > >
""" % from_name
# bare_with_attr has a special significance for FixImports.match().
yield """power< bare_with_attr={} trailer< '.' member={} > any* >
""".format(
from_name, members
)
class FixPyqt(FixImports):
def build_pattern(self):
return "|".join(build_pattern())
# def match(self, node):
# res = super(FixImports, self).match( node )
# print repr(node)
# return res
def transform_import(self, node, results):
"""Transform for the basic import case. Replaces the old
import name with a comma separated list of its
replacements.
"""
import_mod = results.get("module")
pref = import_mod.prefix
names = []
if isinstance(import_mod, Leaf):
# create a Node list of the replacement modules
for name in MAPPING[import_mod.value][:-1]:
names.extend([Name(name[0], prefix=pref), Comma()])
names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
import_mod.replace(names)
else:
self.cannot_convert(
node,
"imports like PyQt4.QtGui or import qgis.PyQt.QtGui are not supported",
)
def transform_member(self, node, results):
"""Transform for imports of specific module elements. Replaces
the module to be imported from with the appropriate new
module.
"""
mod_member = results.get("mod_member")
if isinstance(mod_member, Node):
module = ""
for l in mod_member.leaves():
module += l.value
mod_member.value = module
pref = mod_member.prefix
member = results.get("member")
missing = False
# Simple case with only a single member being imported
if member:
# this may be a list of length one, or just a node
if isinstance(member, list):
member = member[0]
new_name = ""
for change in MAPPING[mod_member.value]:
if member.value in change[1]:
new_name = change[0]
break
if new_name:
mod_member.replace(Name(new_name, prefix=pref))
elif new_name == "":
self.cannot_convert(node, "This is an invalid module element")
else:
node.remove()
# Multiple members being imported
else:
# a dictionary for replacements, order matters
modules = []
mod_dict = {}
members = results["members"]
for member in members:
# we only care about the actual members
if member.type == syms.import_as_name:
as_name = member.children[2].value
member_name = member.children[0].value
else:
member_name = member.value
as_name = None
if member_name != ",":
found = False
for change in MAPPING[mod_member.value]:
if member_name in change[1]:
if change[0] is not None:
if change[0] not in mod_dict:
modules.append(change[0])
mod_dict.setdefault(change[0], []).append(member)
found = True
if not found:
f = open("/tmp/missing", "a+")
f.write(
f"member {member_name} of {mod_member.value} not found\n"
)
f.close()
missing = True
new_nodes = []
indentation = find_indentation(node)
first = True
def handle_name(name, prefix):
if name.type == syms.import_as_name:
kids = [
Name(name.children[0].value, prefix=prefix),
name.children[1].clone(),
name.children[2].clone(),
]
return [Node(syms.import_as_name, kids)]
return [Name(name.value, prefix=prefix)]
for module in modules:
elts = mod_dict[module]
names = []
for elt in elts[:-1]:
names.extend(handle_name(elt, pref))
names.append(Comma())
names.extend(handle_name(elts[-1], pref))
new = FromImport(module, names)
if not first or node.parent.prefix.endswith(indentation):
new.prefix = indentation
new_nodes.append(new)
first = False
if new_nodes:
nodes = []
for new_node in new_nodes[:-1]:
nodes.extend([new_node, Newline()])
nodes.append(new_nodes[-1])
if node.prefix:
nodes[0].prefix = node.prefix
node.replace(nodes)
elif missing:
self.cannot_convert(node, "All module elements are invalid")
else:
node.remove()
def transform_dot(self, node, results):
"""Transform for calls to module members in code."""
module_dot = results.get("bare_with_attr")
member = results.get("member")
new_name = None
if isinstance(member, list):
member = member[0]
for change in MAPPING[module_dot.value]:
if member.value in change[1]:
new_name = change[0]
break
if new_name:
module_dot.replace(Name(new_name, prefix=module_dot.prefix))
else:
self.cannot_convert(node, "This is an invalid module element")
def transform(self, node, results):
if results.get("module"):
self.transform_import(node, results)
elif results.get("mod_member"):
self.transform_member(node, results)
elif results.get("bare_with_attr"):
self.transform_dot(node, results)
# Renaming and star imports are not supported for these modules.
elif results.get("module_star"):
self.cannot_convert(node, "Cannot handle star imports.")
elif results.get("module_as"):
self.cannot_convert(node, "This module is now multiple modules")