diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 40113b5a..1b1a7885 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -62,6 +62,9 @@ except ImportError: get_plugins = False +is_windows = sys.platform.startswith('win') + + # TODO: rewrite def powQueueSize(): """Returns the size of queues.workerQueue including current unfinished work""" @@ -80,7 +83,7 @@ def openKeysFile(): keysfile = os.path.join(state.appdata, 'keys.dat') if 'linux' in sys.platform: subprocess.call(["xdg-open", keysfile]) - elif sys.platform.startswith('win'): + elif is_windows: os.startfile(keysfile) # pylint: disable=no-member @@ -868,7 +871,7 @@ class MyForm(settingsmixin.SMainWindow): """ startonlogon = config.safeGetBoolean( 'bitmessagesettings', 'startonlogon') - if sys.platform.startswith('win'): # Auto-startup for Windows + if is_windows: # Auto-startup for Windows RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" settings = QtCore.QSettings( RUN_PATH, QtCore.QSettings.NativeFormat) @@ -4233,6 +4236,14 @@ class BitmessageQtApplication(QtGui.QApplication): # Unique identifier for this application uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c' + @staticmethod + def get_windowstyle(): + """Get window style set in config or default""" + return config.safeGet( + 'bitmessagesettings', 'windowstyle', + 'Windows' if is_windows else 'GTK+' + ) + def __init__(self, *argv): super(BitmessageQtApplication, self).__init__(*argv) id = BitmessageQtApplication.uuid @@ -4241,6 +4252,14 @@ class BitmessageQtApplication(QtGui.QApplication): QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org") QtCore.QCoreApplication.setApplicationName("pybitmessageqt") + self.setStyle(self.get_windowstyle()) + + font = config.safeGet('bitmessagesettings', 'font') + if font: + # family, size, weight = font.split(',') + family, size = font.split(',') + self.setFont(QtGui.QFont(family, int(size))) + self.server = None self.is_running = False diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 961fc093..bee8fd57 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -61,7 +61,8 @@ class Ui_MainWindow(object): self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) font = QtGui.QFont() - font.setPointSize(9) + base_size = QtGui.QApplication.instance().font().pointSize() + font.setPointSize(int(base_size * 0.75)) self.tabWidget.setFont(font) self.tabWidget.setTabPosition(QtGui.QTabWidget.North) self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 3d05db25..eeb507c7 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -41,14 +41,17 @@ def getSOCKSProxyType(config): class SettingsDialog(QtGui.QDialog): """The "Settings" dialog""" + # pylint: disable=too-many-instance-attributes def __init__(self, parent=None, firstrun=False): super(SettingsDialog, self).__init__(parent) widgets.load('settings.ui', self) + self.app = QtGui.QApplication.instance() self.parent = parent self.firstrun = firstrun self.config = config_obj self.net_restart_needed = False + self.font_setting = None self.timer = QtCore.QTimer() if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): @@ -85,6 +88,15 @@ class SettingsDialog(QtGui.QDialog): def adjust_from_config(self, config): """Adjust all widgets state according to config settings""" # pylint: disable=too-many-branches,too-many-statements + + current_style = self.app.get_windowstyle() + for i, sk in enumerate(QtGui.QStyleFactory.keys()): + self.comboBoxStyle.addItem(sk) + if sk == current_style: + self.comboBoxStyle.setCurrentIndex(i) + + self.save_font_setting(self.app.font()) + if not self.parent.tray.isSystemTrayAvailable(): self.groupBoxTray.setEnabled(False) self.groupBoxTray.setTitle(_translate( @@ -137,7 +149,7 @@ class SettingsDialog(QtGui.QDialog): "MainWindow", "Tray notifications not yet supported on your OS.")) - if 'win' not in sys.platform and not self.parent.desktop: + if not sys.platform.startswith('win') and not self.parent.desktop: self.checkBoxStartOnLogon.setDisabled(True) self.checkBoxStartOnLogon.setText(_translate( "MainWindow", "Start-on-login not yet supported on your OS.")) @@ -322,6 +334,18 @@ class SettingsDialog(QtGui.QDialog): if status == 'success': self.parent.namecoin = nc + def save_font_setting(self, font): + """Save user font setting and set the buttonFont text""" + font_setting = (font.family(), font.pointSize()) + self.buttonFont.setText('{} {}'.format(*font_setting)) + self.font_setting = '{},{}'.format(*font_setting) + + def choose_font(self): + """Show the font selection dialog""" + font, valid = QtGui.QFontDialog.getFont() + if valid: + self.save_font_setting(font) + def accept(self): """A callback for accepted event of buttonBox (OK button pressed)""" # pylint: disable=too-many-branches,too-many-statements @@ -348,6 +372,20 @@ class SettingsDialog(QtGui.QDialog): self.config.set('bitmessagesettings', 'replybelow', str( self.checkBoxReplyBelow.isChecked())) + window_style = str(self.comboBoxStyle.currentText()) + if self.app.get_windowstyle() != window_style or self.config.safeGet( + 'bitmessagesettings', 'font' + ) != self.font_setting: + self.config.set('bitmessagesettings', 'windowstyle', window_style) + self.config.set('bitmessagesettings', 'font', self.font_setting) + queues.UISignalQueue.put(( + 'updateStatusBar', ( + _translate( + "MainWindow", + "You need to restart the application to apply" + " the window style or default font."), 1) + )) + lang = str(self.languageComboBox.itemData( self.languageComboBox.currentIndex()).toString()) self.config.set('bitmessagesettings', 'userlocale', lang) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 1e9a6f09..e7ce1d71 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -147,6 +147,32 @@ + + + + Custom Style + + + + + + + 100 + 0 + + + + + + + + Font + + + + + + @@ -1202,5 +1228,11 @@ + + buttonFont + clicked() + settingsDialog + choose_font + diff --git a/src/bitmessageqt/tests/__init__.py b/src/bitmessageqt/tests/__init__.py index a542abdc..a81ddb04 100644 --- a/src/bitmessageqt/tests/__init__.py +++ b/src/bitmessageqt/tests/__init__.py @@ -1,9 +1,9 @@ """bitmessageqt tests""" -from addressbook import TestAddressbook -from main import TestMain, TestUISignaler -from settings import TestSettings -from support import TestSupport +from .addressbook import TestAddressbook +from .main import TestMain, TestUISignaler +from .settings import TestSettings +from .support import TestSupport __all__ = [ "TestAddressbook", "TestMain", "TestSettings", "TestSupport", diff --git a/src/bitmessageqt/tests/main.py b/src/bitmessageqt/tests/main.py index b3aa67fa..d3fda8aa 100644 --- a/src/bitmessageqt/tests/main.py +++ b/src/bitmessageqt/tests/main.py @@ -1,19 +1,23 @@ """Common definitions for bitmessageqt tests""" -import Queue import sys import unittest from PyQt4 import QtCore, QtGui +from six.moves import queue import bitmessageqt -import queues -from tr import _translate +from bitmessageqt import _translate, config, queues class TestBase(unittest.TestCase): """Base class for bitmessageqt test case""" + @classmethod + def setUpClass(cls): + """Provide the UI test cases with common settings""" + cls.config = config + def setUp(self): self.app = ( QtGui.QApplication.instance() @@ -24,14 +28,21 @@ class TestBase(unittest.TestCase): self.window.appIndicatorInit(self.app) def tearDown(self): + """Search for exceptions in closures called by timer and fail if any""" # self.app.deleteLater() + concerning = [] while True: try: thread, exc = queues.excQueue.get(block=False) - except Queue.Empty: - return + except queue.Empty: + break if thread == 'tests': - self.fail('Exception in the main thread: %s' % exc) + concerning.append(exc) + if concerning: + self.fail( + 'Exceptions found in the main thread:\n%s' % '\n'.join(( + str(e) for e in concerning + ))) class TestMain(unittest.TestCase): diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py index 0dcf8cf3..bad28ed7 100644 --- a/src/bitmessageqt/tests/settings.py +++ b/src/bitmessageqt/tests/settings.py @@ -1,10 +1,14 @@ +"""Tests for PyBitmessage settings""" import threading import time -from main import TestBase +from PyQt4 import QtCore, QtGui, QtTest + from bmconfigparser import config from bitmessageqt import settings +from .main import TestBase + class TestSettings(TestBase): """A test case for the "Settings" dialog""" @@ -14,8 +18,7 @@ class TestSettings(TestBase): def test_udp(self): """Test the effect of checkBoxUDP""" - udp_setting = config.safeGetBoolean( - 'bitmessagesettings', 'udp') + udp_setting = config.safeGetBoolean('bitmessagesettings', 'udp') self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked()) self.dialog.checkBoxUDP.setChecked(not udp_setting) self.dialog.accept() @@ -32,3 +35,44 @@ class TestSettings(TestBase): else: if not udp_setting: self.fail('No Announcer thread found while udp set to True') + + def test_styling(self): + """Test custom windows style and font""" + style_setting = config.safeGet('bitmessagesettings', 'windowstyle') + font_setting = config.safeGet('bitmessagesettings', 'font') + self.assertIs(style_setting, None) + self.assertIs(font_setting, None) + style_control = self.dialog.comboBoxStyle + self.assertEqual( + style_control.currentText(), self.app.get_windowstyle()) + + def call_font_dialog(): + """A function to get the open font dialog and accept it""" + font_dialog = QtGui.QApplication.activeModalWidget() + self.assertTrue(isinstance(font_dialog, QtGui.QFontDialog)) + selected_font = font_dialog.currentFont() + self.assertEqual( + config.safeGet('bitmessagesettings', 'font'), '{},{}'.format( + selected_font.family(), selected_font.pointSize())) + + font_dialog.accept() + self.dialog.accept() + self.assertEqual( + config.safeGet('bitmessagesettings', 'windowstyle'), + style_control.currentText()) + + def click_font_button(): + """Use QtTest to click the button""" + QtTest.QTest.mouseClick( + self.dialog.buttonFont, QtCore.Qt.LeftButton) + + style_count = style_control.count() + self.assertGreater(style_count, 1) + for i in range(style_count): + if i != style_control.currentIndex(): + style_control.setCurrentIndex(i) + break + + QtCore.QTimer.singleShot(30, click_font_button) + QtCore.QTimer.singleShot(60, call_font_dialog) + time.sleep(2)