Skip to content

Commit

Permalink
pyqt: update to the PyQt4 v2 API for QString and friends
Browse files Browse the repository at this point in the history
Eliminate all usage of QString and other PyQt4 v1 API features.
Explicitly set the API to v2 in the new sipcompat module.

Old-style signals and slots are still used.
git-cola will use new-style signals and slots in v3.0.

Related-to: git-cola#232
Signed-off-by: David Aguilar <davvid@gmail.com>
  • Loading branch information
davvid committed Jan 17, 2016
1 parent ab030d8 commit 9a04dc5
Show file tree
Hide file tree
Showing 16 changed files with 87 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ script:
- git config --global user.name "Git Cola"
- git config --global user.email git-cola@localhost.localdomain
# TODO tests currently fail on Python3 due to sip
- env GIT_COLA_NO_SIP_SETAPI=1 GIT_COLA_NO_HISTORY=1 make test flags=--verbose
- env GIT_COLA_NO_HISTORY=1 make test flags=--verbose
# pylint 1.4 uses python2.7+ syntax so skip pylint on python2.6
- test "2.6" = "$TRAVIS_PYTHON_VERSION" || make pylint
3 changes: 3 additions & 0 deletions cola/actions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import absolute_import

from cola import sipcompat
sipcompat.initialize()

from PyQt4.QtCore import SIGNAL

from cola import cmds
Expand Down
11 changes: 2 additions & 9 deletions cola/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,12 @@


try:
import sip
from cola import sipcompat
except ImportError:
sys.stderr.write(errmsg)
sys.exit(EX_UNAVAILABLE)

if not os.getenv('GIT_COLA_NO_SIP_SETAPI', False):
sip.setapi('QString', 1)
sip.setapi('QDate', 1)
sip.setapi('QDateTime', 1)
sip.setapi('QTextStream', 1)
sip.setapi('QTime', 1)
sip.setapi('QUrl', 1)
sip.setapi('QVariant', 1)
sipcompat.initialize()

try:
from PyQt4 import QtCore
Expand Down
20 changes: 12 additions & 8 deletions cola/gravatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import time

from cola import sipcompat
sipcompat.initialize()

from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4 import QtNetwork
Expand All @@ -18,10 +21,12 @@ class Gravatar(object):

@staticmethod
def url_for_email(email, imgsize):
email_hash = hashlib.md5(core.encode(email)).hexdigest()
default_url = b'https://git-cola.github.io/images/git-64x64.jpg'
encoded_url = urllib.quote(default_url, b'')
query = '?s=%d&d=%s' % (imgsize, encoded_url)
email_hash = core.decode(hashlib.md5(core.encode(email)).hexdigest())
# Python2.6 requires byte strings for urllib.quote() so we have
# to force
default_url = 'https://git-cola.github.io/images/git-64x64.jpg'
encoded_url = urllib.quote(core.encode(default_url), core.encode(''))
query = '?s=%d&d=%s' % (imgsize, core.decode(encoded_url))
url = 'https://gravatar.com/avatar/' + email_hash + query
return url

Expand Down Expand Up @@ -76,9 +81,8 @@ def network_finished(self, reply):
header = QtCore.QByteArray('Location')
raw_header = reply.rawHeader(header)
if raw_header:
location = ustr(QtCore.QString(raw_header)).strip()
request_location = ustr(
Gravatar.url_for_email(self.email, self.imgsize))
location = ustr(raw_header).strip()
request_location = Gravatar.url_for_email(self.email, self.imgsize)
relocated = location != request_location
else:
relocated = False
Expand All @@ -103,7 +107,7 @@ def network_finished(self, reply):
# email address. We can't blindly trust self.email else
# we may add cache entries for thee wrong email address.
url = Gravatar.url_for_email(email, self.imgsize)
if url == ustr(reply.url().toString()):
if url == reply.url().toString():
self.pixmaps[email] = pixmap

def set_pixmap_from_response(self):
Expand Down
23 changes: 13 additions & 10 deletions cola/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,29 @@
from cola import resources

_null_translation = _gettext.NullTranslations()
# Python 3 compat
if not hasattr(_null_translation, 'ugettext'):
_null_translation.ugettext = _null_translation.gettext
_null_translation.ungettext = _null_translation.ngettext
_translation = _null_translation


def gettext(s):
txt = _translation.ugettext(s)
try:
txt = _translation.ugettext(s)
except AttributeError:
# Python 3 compat
_translation.ugettext = _translation.gettext
txt = _translation.gettext(s)
if txt[-6:-4] == '@@': # handle @@verb / @@noun
txt = txt[:-6]
return txt


def ngettext(s, p, n):
return _translation.ungettext(s, p, n)
try:
txt = _translation.ungettext(s, p, n)
except AttributeError:
# Python 3 compat
_translation.ungettext = _translation.ngettext
txt = _translation.ngettext(s, p, n)
return txt


def N_(s):
Expand All @@ -45,10 +52,6 @@ def install(locale):
_translation = _gettext.translation('git-cola',
localedir=_get_locale_dir(),
fallback=True)
# Python 3 compat
if not hasattr(_translation, 'ugettext'):
_translation.ugettext = _translation.gettext
_translation.ungettext = _translation.ngettext

def uninstall():
global _translation
Expand Down
5 changes: 4 additions & 1 deletion cola/models/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import collections
import time

from cola import sipcompat
sipcompat.initialize()

from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
Expand Down Expand Up @@ -233,7 +236,7 @@ def _initialize(self):
self.setColumnCount(len(Columns.ALL))
for idx, header in enumerate(Columns.ALL):
text = Columns.text(header)
self.setHeaderData(idx, Qt.Horizontal, QtCore.QVariant(text))
self.setHeaderData(idx, Qt.Horizontal, text)

self._entries = {}
self._dir_rows = collections.defaultdict(int)
Expand Down
3 changes: 3 additions & 0 deletions cola/qtcompat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import division, absolute_import, unicode_literals

from cola import sipcompat
sipcompat.initialize()

from PyQt4 import QtGui
from PyQt4 import QtCore

Expand Down
30 changes: 30 additions & 0 deletions cola/sipcompat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import absolute_import, division, unicode_literals

import sip


class _PyQtSipApi(object):

def __init__(self):
self._initialized = False

def initialize(self):
if self._initialized:
return
sip.setapi('QDate', 2)
sip.setapi('QDateTime', 2)
sip.setapi('QString', 2)
sip.setapi('QTextStream', 2)
sip.setapi('QTime', 2)
sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2)

self._initialized = True

# It's a global, but in this case the API compat level truly
# is a global that can only be set once before PyQt4 is imported.
_pyqt_api = _PyQtSipApi()


def initialize():
_pyqt_api.initialize()
7 changes: 5 additions & 2 deletions cola/widgets/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import re

from cola import sipcompat
sipcompat.initialize()

from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
Expand Down Expand Up @@ -286,7 +289,7 @@ def paint(self, painter, option, index):
if not self.highlight_text:
return QtGui.QStyledItemDelegate.paint(self, painter, option, index)

text = ustr(index.data().toPyObject())
text = index.data()
if self.case_sensitive:
html = text.replace(self.highlight_text,
'<strong>%s</strong>' % self.highlight_text)
Expand All @@ -305,7 +308,7 @@ def paint(self, painter, option, index):
# Painting item without text, Text Document will paint the text
optionV4 = QtGui.QStyleOptionViewItemV4(option)
self.initStyleOption(optionV4, index)
optionV4.text = QtCore.QString()
optionV4.text = ''

style = QtGui.QApplication.style()
style.drawControl(QtGui.QStyle.CE_ItemViewItem, optionV4, painter)
Expand Down
2 changes: 1 addition & 1 deletion cola/widgets/dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ def itemChange(self, change, value):
self.scene().parent().set_selecting(False)

# Cache the pen for use in paint()
if value.toPyObject():
if value:
self.brush = self.commit_selected_color
color = self.selected_outline_color
else:
Expand Down
5 changes: 2 additions & 3 deletions cola/widgets/filetree.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from PyQt4 import QtGui

from cola import icons
from cola.compat import ustr
from cola.widgets import standard


Expand All @@ -26,14 +25,14 @@ def set_filenames(self, filenames, select=False):
item = QtGui.QTreeWidgetItem()
item.setIcon(0, icon)
item.setText(0, filename)
item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(filename))
item.setData(0, QtCore.Qt.UserRole, filename)
items.append(item)
self.addTopLevelItems(items)
if select:
self.setItemSelected(items[0], True)

def filename_from_item(self, item):
return ustr(item.data(0, QtCore.Qt.UserRole).toPyObject())
return item.data(0, QtCore.Qt.UserRole)

def has_selection(self):
return bool(self.selectedItems())
Expand Down
4 changes: 1 addition & 3 deletions cola/widgets/grep.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from cola import utils
from cola import qtutils
from cola.cmds import do
from cola.compat import ustr
from cola.git import git
from cola.i18n import N_
from cola.qtutils import diff_font
Expand Down Expand Up @@ -180,8 +179,7 @@ def done(self, exit_code):

def regexp_mode(self):
idx = self.regexp_combo.currentIndex()
data = self.regexp_combo.itemData(idx, Qt.UserRole).toPyObject()
return ustr(data)
return self.regexp_combo.itemData(idx, Qt.UserRole)

def search(self):
self.edit_group.setEnabled(False)
Expand Down
7 changes: 3 additions & 4 deletions cola/widgets/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def get_patches_from_mimedata(mimedata):
urls = mimedata.urls()
if not urls:
return []
paths = [ustr(x.path()) for x in urls]
paths = [x.path() for x in urls]
return get_patches_from_paths(paths)


Expand Down Expand Up @@ -131,7 +131,7 @@ def apply_patches(self):
items = self.tree.items()
if not items:
return
patches = [ustr(i.data(0, Qt.UserRole).toPyObject()) for i in items]
patches = [i.data(0, Qt.UserRole) for i in items]
cmds.do(cmds.ApplyPatches, patches)
self.accept()

Expand All @@ -141,7 +141,6 @@ def add_files(self):
filter='Patches (*.patch *.mbox)')
if not files:
return
files = [ustr(f) for f in files]
self.curdir = os.path.dirname(files[0])
self.add_paths([os.path.relpath(f) for f in files])

Expand Down Expand Up @@ -181,7 +180,7 @@ def add_paths(self, paths):
item.setFlags(flags)
item.setIcon(0, icon)
item.setText(0, os.path.basename(patch))
item.setData(0, Qt.UserRole, QtCore.QVariant(patch))
item.setData(0, Qt.UserRole, patch)
item.setToolTip(0, patch)
items.append(item)
self.addTopLevelItems(items)
Expand Down
6 changes: 3 additions & 3 deletions cola/widgets/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ def __init__(self, model, parent, source):
self.merge_verbosity = QtGui.QSpinBox()
self.merge_verbosity.setMinimum(0)
self.merge_verbosity.setMaximum(5)
self.merge_verbosity.setProperty('value', QtCore.QVariant(5))
self.merge_verbosity.setProperty('value', 5)

self.diff_context = QtGui.QSpinBox()
self.diff_context.setMinimum(2)
self.diff_context.setMaximum(99)
self.diff_context.setProperty('value', QtCore.QVariant(5))
self.diff_context.setProperty('value', 5)

self.merge_summary = qtutils.checkbox(checked=True)
self.merge_diffstat = qtutils.checkbox(checked=True)
Expand Down Expand Up @@ -156,7 +156,7 @@ def __init__(self, model, parent):

self.font_size = QtGui.QSpinBox()
self.font_size.setMinimum(8)
self.font_size.setProperty('value', QtCore.QVariant(12))
self.font_size.setProperty('value', 12)
self._font_str = None

self.tabwidth = QtGui.QSpinBox()
Expand Down
2 changes: 2 additions & 0 deletions test/gravatar_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import unittest
from cola import gravatar
from cola.compat import ustr


class GravatarTestCase(unittest.TestCase):
Expand All @@ -13,6 +14,7 @@ def test_url_for_email_(self):
expect='https://gravatar.com/avatar/5658ffccee7f0ebfda2b226238b1eb6e?s=64&d=https%3A%2F%2Fgit-cola.github.io%2Fimages%2Fgit-64x64.jpg'
actual = gravatar.Gravatar.url_for_email(email, 64)
self.assertEqual(expect, actual)
self.assertEqual(ustr, type(actual))


if __name__ == '__main__':
Expand Down
21 changes: 2 additions & 19 deletions test/i18n_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
from __future__ import absolute_import, division, unicode_literals

import os
import unittest

import sip

if not os.getenv('GIT_COLA_NO_SIP_SETAPI', False):
sip.setapi('QString', 1)

from PyQt4 import QtCore
from cola import sipcompat
sipcompat.initialize()

from cola import i18n
from cola.i18n import N_
Expand Down Expand Up @@ -62,18 +57,6 @@ def test_translates_random_english(self):
actual = N_('Random')
self.assertEqual(expect, actual)

def test_guards_against_qstring(self):
"""Test that random QString is passed through as-is
"""
i18n.install('en_US.UTF-8')
expect = 'Random'
if hasattr(QtCore, 'QString'):
text = QtCore.QString('Random')
else:
text = 'Random'
actual = i18n.gettext(text)
self.assertEqual(expect, actual)


if __name__ == '__main__':
unittest.main()

0 comments on commit 9a04dc5

Please sign in to comment.