1"""Migrate imports of PyQt4 to PyQt wrapper
2"""
3# Author: Juergen E. Fischer
4# Adapted from fix_urllib
5
6# Local imports
7from lib2to3.fixes.fix_imports import alternates, FixImports
8from lib2to3 import fixer_base
9from lib2to3.fixer_util import (Name, Comma, FromImport, Newline,
10                                find_indentation, Node, syms, Leaf)
11
12MAPPING = {
13    "PyQt4.QtGui": [
14        ("qgis.PyQt.QtGui", [
15         "QIcon",
16         "QCursor",
17         "QColor",
18         "QDesktopServices",
19         "QFont",
20         "QFontMetrics",
21         "QKeySequence",
22         "QStandardItemModel",
23         "QStandardItem",
24         "QClipboard",
25         "QPixmap",
26         "QDoubleValidator",
27         "QPainter",
28         "QPen",
29         "QBrush",
30         "QPalette",
31         "QPainterPath",
32         "QImage",
33         "QPolygonF",
34         "QFontMetricsF",
35         "QGradient",
36         "QIntValidator",
37         ]),
38        ("qgis.PyQt.QtWidgets", [
39         "QAbstractButton",
40         "QAbstractGraphicsShapeItem",
41         "QAbstractItemDelegate",
42         "QAbstractItemView",
43         "QAbstractScrollArea",
44         "QAbstractSlider",
45         "QAbstractSpinBox",
46         "QAbstractTableModel",
47         "QAction",
48         "QActionGroup",
49         "QApplication",
50         "QBoxLayout",
51         "QButtonGroup",
52         "QCalendarWidget",
53         "QCheckBox",
54         "QColorDialog",
55         "QColumnView",
56         "QComboBox",
57         "QCommandLinkButton",
58         "QCommonStyle",
59         "QCompleter",
60         "QDataWidgetMapper",
61         "QDateEdit",
62         "QDateTimeEdit",
63         "QDesktopWidget",
64         "QDial",
65         "QDialog",
66         "QDialogButtonBox",
67         "QDirModel",
68         "QDockWidget",
69         "QDoubleSpinBox",
70         "QErrorMessage",
71         "QFileDialog",
72         "QFileIconProvider",
73         "QFileSystemModel",
74         "QFocusFrame",
75         "QFontComboBox",
76         "QFontDialog",
77         "QFormLayout",
78         "QFrame",
79         "QGesture",
80         "QGestureEvent",
81         "QGestureRecognizer",
82         "QGraphicsAnchor",
83         "QGraphicsAnchorLayout",
84         "QGraphicsBlurEffect",
85         "QGraphicsColorizeEffect",
86         "QGraphicsDropShadowEffect",
87         "QGraphicsEffect",
88         "QGraphicsEllipseItem",
89         "QGraphicsGridLayout",
90         "QGraphicsItem",
91         "QGraphicsItemGroup",
92         "QGraphicsLayout",
93         "QGraphicsLayoutItem",
94         "QGraphicsLineItem",
95         "QGraphicsLinearLayout",
96         "QGraphicsObject",
97         "QGraphicsOpacityEffect",
98         "QGraphicsPathItem",
99         "QGraphicsPixmapItem",
100         "QGraphicsPolygonItem",
101         "QGraphicsProxyWidget",
102         "QGraphicsRectItem",
103         "QGraphicsRotation",
104         "QGraphicsScale",
105         "QGraphicsScene",
106         "QGraphicsSceneContextMenuEvent",
107         "QGraphicsSceneDragDropEvent",
108         "QGraphicsSceneEvent",
109         "QGraphicsSceneHelpEvent",
110         "QGraphicsSceneHoverEvent",
111         "QGraphicsSceneMouseEvent",
112         "QGraphicsSceneMoveEvent",
113         "QGraphicsSceneResizeEvent",
114         "QGraphicsSceneWheelEvent",
115         "QGraphicsSimpleTextItem",
116         "QGraphicsTextItem",
117         "QGraphicsTransform",
118         "QGraphicsView",
119         "QGraphicsWidget",
120         "QGridLayout",
121         "QGroupBox",
122         "QHBoxLayout",
123         "QHeaderView",
124         "QInputDialog",
125         "QItemDelegate",
126         "QItemEditorCreatorBase",
127         "QItemEditorFactory",
128         "QKeyEventTransition",
129         "QLCDNumber",
130         "QLabel",
131         "QLayout",
132         "QLayoutItem",
133         "QLineEdit",
134         "QListView",
135         "QListWidget",
136         "QListWidgetItem",
137         "QMainWindow",
138         "QMdiArea",
139         "QMdiSubWindow",
140         "QMenu",
141         "QMenuBar",
142         "QMessageBox",
143         "QMouseEventTransition",
144         "QPanGesture",
145         "QPinchGesture",
146         "QPlainTextDocumentLayout",
147         "QPlainTextEdit",
148         "QProgressBar",
149         "QProgressDialog",
150         "QPushButton",
151         "QRadioButton",
152         "QRubberBand",
153         "QScrollArea",
154         "QScrollBar",
155         "QShortcut",
156         "QSizeGrip",
157         "QSizePolicy",
158         "QSlider",
159         "QSpacerItem",
160         "QSpinBox",
161         "QSplashScreen",
162         "QSplitter",
163         "QSplitterHandle",
164         "QStackedLayout",
165         "QStackedWidget",
166         "QStatusBar",
167         "QStyle",
168         "QStyleFactory",
169         "QStyleHintReturn",
170         "QStyleHintReturnMask",
171         "QStyleHintReturnVariant",
172         "QStyleOption",
173         "QStyleOptionButton",
174         "QStyleOptionComboBox",
175         "QStyleOptionComplex",
176         "QStyleOptionDockWidget",
177         "QStyleOptionFocusRect",
178         "QStyleOptionFrame",
179         "QStyleOptionGraphicsItem",
180         "QStyleOptionGroupBox",
181         "QStyleOptionHeader",
182         "QStyleOptionMenuItem",
183         "QStyleOptionProgressBar",
184         "QStyleOptionRubberBand",
185         "QStyleOptionSizeGrip",
186         "QStyleOptionSlider",
187         "QStyleOptionSpinBox",
188         "QStyleOptionTab",
189         "QStyleOptionTabBarBase",
190         "QStyleOptionTabWidgetFrame",
191         "QStyleOptionTitleBar",
192         "QStyleOptionToolBar",
193         "QStyleOptionToolBox",
194         "QStyleOptionToolButton",
195         "QStyleOptionViewItem",
196         "QStylePainter",
197         "QStyledItemDelegate",
198         "QSwipeGesture",
199         "QSystemTrayIcon",
200         "QTabBar",
201         "QTabWidget",
202         "QTableView",
203         "QTableWidget",
204         "QTableWidgetItem",
205         "QTableWidgetSelectionRange",
206         "QTapAndHoldGesture",
207         "QTapGesture",
208         "QTextBrowser",
209         "QTextEdit",
210         "QTimeEdit",
211         "QToolBar",
212         "QToolBox",
213         "QToolButton",
214         "QToolTip",
215         "QTreeView",
216         "QTreeWidget",
217         "QTreeWidgetItem",
218         "QTreeWidgetItemIterator",
219         "QUndoCommand",
220         "QUndoGroup",
221         "QUndoStack",
222         "QUndoView",
223         "QVBoxLayout",
224         "QWhatsThis",
225         "QWidget",
226         "QWidgetAction",
227         "QWidgetItem",
228         "QWizard",
229         "QWizardPage",
230         "qApp",
231         "qDrawBorderPixmap",
232         "qDrawPlainRect",
233         "qDrawShadeLine",
234         "qDrawShadePanel",
235         "qDrawShadeRect",
236         "qDrawWinButton",
237         "qDrawWinPanel",
238         ]),
239        ("qgis.PyQt.QtPrintSupport", [
240            "QPrinter",
241            "QAbstractPrintDialog",
242            "QPageSetupDialog",
243            "QPrintDialog",
244            "QPrintEngine",
245            "QPrintPreviewDialog",
246            "QPrintPreviewWidget",
247            "QPrinterInfo",
248        ]),
249        ("qgis.PyQt.QtCore", [
250         "QItemSelectionModel",
251         "QSortFilterProxyModel",
252         ]),
253    ],
254    "PyQt4.QtCore": [
255        ("qgis.PyQt.QtCore", [
256            "QAbstractItemModel",
257            "QAbstractTableModel",
258            "QByteArray",
259            "QCoreApplication",
260            "QDataStream",
261            "QDir",
262            "QEvent",
263            "QFile",
264            "QFileInfo",
265            "QIODevice",
266            "QLocale",
267            "QMimeData",
268            "QModelIndex",
269            "QMutex",
270            "QObject",
271            "QProcess",
272            "QSettings",
273            "QSize",
274            "QSizeF",
275            "QTextCodec",
276            "QThread",
277            "QThreadPool",
278            "QTimer",
279            "QTranslator",
280            "QUrl",
281            "Qt",
282            "pyqtProperty",
283            "pyqtWrapperType",
284            "pyqtSignal",
285            "pyqtSlot",
286            "qDebug",
287            "qWarning",
288            "qVersion",
289            "QDate",
290            "QTime",
291            "QDateTime",
292            "QRegExp",
293            "QTemporaryFile",
294            "QTextStream",
295            "QVariant",
296            "QPyNullVariant",
297            "QRect",
298            "QRectF",
299            "QMetaObject",
300            "QPoint",
301            "QPointF",
302            "QDirIterator",
303            "QEventLoop",
304            "NULL",
305        ]),
306        (None, [
307            "SIGNAL",
308            "SLOT",
309        ]),
310    ],
311    "PyQt4.QtNetwork": [
312        ("qgis.PyQt.QtNetwork", [
313            "QNetworkReply",
314            "QNetworkRequest",
315            "QSslCertificate",
316            "QSslKey",
317            "QSsl"
318        ])
319    ],
320    "PyQt4.QtXml": [
321        ("qgis.PyQt.QtXml", [
322            "QDomDocument"
323        ]),
324    ],
325    "PyQt4.Qsci": [
326        ("qgis.PyQt.Qsci", [
327            "QsciAPIs",
328            "QsciLexerCustom",
329            "QsciLexerPython",
330            "QsciScintilla",
331            "QsciLexerSQL",
332            "QsciStyle",
333        ]),
334    ],
335    "PyQt4.QtWebKit": [
336        ("qgis.PyQt.QtWebKitWidgets", [
337            "QGraphicsWebView",
338            "QWebFrame",
339            "QWebHitTestResult",
340            "QWebInspector",
341            "QWebPage",
342            "QWebView",
343        ]),
344    ],
345    "PyQt4.QtTest": [
346        ("qgis.PyQt.QtTest", [
347            "QTest",
348        ]),
349    ],
350    "PyQt4.QtSvg": [
351        ("qgis.PyQt.QtSvg", [
352            "QSvgRenderer",
353            "QSvgGenerator"
354        ]),
355    ],
356    "PyQt4.QtSql": [
357        ("qgis.PyQt.QtSql", [
358            "QSqlDatabase",
359            "QSqlQuery",
360            "QSqlField"
361        ]),
362    ],
363    "PyQt4.uic": [
364        ("qgis.PyQt.uic", [
365            "loadUiType",
366            "loadUi",
367        ]),
368    ],
369    "PyQt4": [
370        ("qgis.PyQt", [
371            "QtCore",
372            "QtGui",
373            "QtNetwork",
374            "QtXml",
375            "QtWebkit",
376            "QtSql",
377            "QtSvg",
378            "Qsci",
379            "uic",
380        ])
381    ],
382}
383
384
385new_mappings = {}
386for key, value in MAPPING.items():
387    match_str = key.replace('PyQt4', '')
388    match_str = '{}{}'.format('qgis.PyQt', match_str)
389    new_mappings[match_str] = value
390
391MAPPING.update(new_mappings)
392
393
394def build_pattern():
395    bare = set()
396    for old_module, changes in list(MAPPING.items()):
397        for change in changes:
398            new_module, members = change
399            members = alternates(members)
400
401            if '.' not in old_module:
402                from_name = "%r" % old_module
403
404            else:
405                dotted = old_module.split('.')
406                if len(dotted) == 3:
407                    from_name = "dotted_name<%r '.' %r '.' %r>" % (dotted[0], dotted[1], dotted[2])
408                else:
409                    assert len(dotted) == 2
410                    from_name = "dotted_name<%r '.' %r>" % (dotted[0], dotted[1])
411
412            yield """import_name< 'import' (module=%s
413                                  | dotted_as_names< any* module=%s any* >) >
414                  """ % (from_name, from_name)
415            yield """import_from< 'from' mod_member=%s 'import'
416                       ( member=%s | import_as_name< member=%s 'as' any > |
417                         import_as_names< members=any*  >) >
418                  """ % (from_name, members, members)
419            yield """import_from< 'from' mod_member=%s 'import' '('
420                       ( member=%s | import_as_name< member=%s 'as' any > |
421                         import_as_names< members=any*  >) ')' >
422                  """ % (from_name, members, members)
423            yield """import_from< 'from' module_star=%s 'import' star='*' >
424                  """ % from_name
425            yield """import_name< 'import'
426                                  dotted_as_name< module_as=%s 'as' any > >
427                  """ % from_name
428            # bare_with_attr has a special significance for FixImports.match().
429            yield """power< bare_with_attr=%s trailer< '.' member=%s > any* >
430                  """ % (from_name, members)
431
432
433class FixPyqt(FixImports):
434
435    def build_pattern(self):
436        return "|".join(build_pattern())
437
438#    def match(self, node):
439#        res = super(FixImports, self).match( node )
440#        print repr(node)
441#        return res
442
443    def transform_import(self, node, results):
444        """Transform for the basic import case. Replaces the old
445           import name with a comma separated list of its
446           replacements.
447        """
448        import_mod = results.get("module")
449        pref = import_mod.prefix
450
451        names = []
452
453        if isinstance(import_mod, Leaf):
454            # create a Node list of the replacement modules
455            for name in MAPPING[import_mod.value][:-1]:
456                names.extend([Name(name[0], prefix=pref), Comma()])
457            names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
458            import_mod.replace(names)
459        else:
460            self.cannot_convert(node, "imports like PyQt4.QtGui or import qgis.PyQt.QtGui are not supported")
461
462    def transform_member(self, node, results):
463        """Transform for imports of specific module elements. Replaces
464           the module to be imported from with the appropriate new
465           module.
466        """
467        mod_member = results.get("mod_member")
468        if isinstance(mod_member, Node):
469            module = ""
470            for l in mod_member.leaves():
471                module += l.value
472            mod_member.value = module
473        pref = mod_member.prefix
474        member = results.get("member")
475
476        missing = False
477
478        # Simple case with only a single member being imported
479        if member:
480            # this may be a list of length one, or just a node
481            if isinstance(member, list):
482                member = member[0]
483            new_name = ''
484            for change in MAPPING[mod_member.value]:
485                if member.value in change[1]:
486                    new_name = change[0]
487                    break
488            if new_name:
489                mod_member.replace(Name(new_name, prefix=pref))
490            elif new_name == '':
491                self.cannot_convert(node, "This is an invalid module element")
492            else:
493                node.remove()
494
495        # Multiple members being imported
496        else:
497            # a dictionary for replacements, order matters
498            modules = []
499            mod_dict = {}
500            members = results["members"]
501            for member in members:
502                # we only care about the actual members
503                if member.type == syms.import_as_name:
504                    as_name = member.children[2].value
505                    member_name = member.children[0].value
506                else:
507                    member_name = member.value
508                    as_name = None
509                if member_name != u",":
510                    found = False
511                    for change in MAPPING[mod_member.value]:
512                        if member_name in change[1]:
513                            if change[0] is not None:
514                                if change[0] not in mod_dict:
515                                    modules.append(change[0])
516                                mod_dict.setdefault(change[0], []).append(member)
517                            found = True
518                    if not found:
519                        f = open("/tmp/missing", "a+")
520                        f.write("member %s of %s not found\n" % (member_name, mod_member.value))
521                        f.close()
522                        missing = True
523
524            new_nodes = []
525            indentation = find_indentation(node)
526            first = True
527
528            def handle_name(name, prefix):
529                if name.type == syms.import_as_name:
530                    kids = [Name(name.children[0].value, prefix=prefix),
531                            name.children[1].clone(),
532                            name.children[2].clone()]
533                    return [Node(syms.import_as_name, kids)]
534                return [Name(name.value, prefix=prefix)]
535
536            for module in modules:
537                elts = mod_dict[module]
538                names = []
539                for elt in elts[:-1]:
540                    names.extend(handle_name(elt, pref))
541                    names.append(Comma())
542                names.extend(handle_name(elts[-1], pref))
543                new = FromImport(module, names)
544                if not first or node.parent.prefix.endswith(indentation):
545                    new.prefix = indentation
546                new_nodes.append(new)
547                first = False
548
549            if new_nodes:
550                nodes = []
551                for new_node in new_nodes[:-1]:
552                    nodes.extend([new_node, Newline()])
553                nodes.append(new_nodes[-1])
554
555                if node.prefix:
556                    nodes[0].prefix = node.prefix
557
558                node.replace(nodes)
559            elif missing:
560                self.cannot_convert(node, "All module elements are invalid")
561            else:
562                node.remove()
563
564    def transform_dot(self, node, results):
565        """Transform for calls to module members in code."""
566        module_dot = results.get("bare_with_attr")
567        member = results.get("member")
568        new_name = None
569        if isinstance(member, list):
570            member = member[0]
571        for change in MAPPING[module_dot.value]:
572            if member.value in change[1]:
573                new_name = change[0]
574                break
575        if new_name:
576            module_dot.replace(Name(new_name,
577                                    prefix=module_dot.prefix))
578        else:
579            self.cannot_convert(node, "This is an invalid module element")
580
581    def transform(self, node, results):
582        if results.get("module"):
583            self.transform_import(node, results)
584        elif results.get("mod_member"):
585            self.transform_member(node, results)
586        elif results.get("bare_with_attr"):
587            self.transform_dot(node, results)
588        # Renaming and star imports are not supported for these modules.
589        elif results.get("module_star"):
590            self.cannot_convert(node, "Cannot handle star imports.")
591        elif results.get("module_as"):
592            self.cannot_convert(node, "This module is now multiple modules")
593