diff --git a/docs/source/standard_dialogs.rst b/docs/source/standard_dialogs.rst index dd290f012..00ca62166 100644 --- a/docs/source/standard_dialogs.rst +++ b/docs/source/standard_dialogs.rst @@ -83,6 +83,13 @@ Typical usage for a save dialog looks like this:: print(f"Save to {dialog.path}") +Font Dialog +=========== + +A dialog that takes a |Font| object and displays a standard OS font selection. +A convenience function |get_font| opens the dialog and returns a font value. + + Message Dialog ============== @@ -195,6 +202,7 @@ user's selection or ``None`` if nothing is selected. .. |CANCEL| replace:: :py:obj:`~pyface.constants.CANCEL` .. |Color| replace:: :py:class:`~pyface.color.Color` .. |Executor| replace:: :py:class:`~concurrent.futures.Executor` +.. |Font| replace:: :py:class:`~pyface.font.Font` .. |IDialog| replace:: :py:class:`~pyface.i_dialog.IDialog` .. |NO| replace:: :py:obj:`~pyface.constants.NO` .. |OK| replace:: :py:obj:`~pyface.constants.OK` @@ -203,6 +211,7 @@ user's selection or ``None`` if nothing is selected. .. |confirm| replace:: :py:func:`~pyface.confirmation_dialog.confirm` .. |error| replace:: :py:func:`~pyface.message_dialog.error` .. |get_color| replace:: :py:func:`~pyface.color_dialog.get_color` +.. |get_font| replace:: :py:func:`~pyface.font_dialog.get_font` .. |information| replace:: :py:func:`~pyface.message_dialog.information` .. |open| replace:: :py:meth:`~pyface.i_dialog.IDialog.open` .. |warning| replace:: :py:func:`~pyface.message_dialog.warning` diff --git a/etstool.py b/etstool.py index 18afd55a0..23866eb81 100644 --- a/etstool.py +++ b/etstool.py @@ -84,6 +84,7 @@ supported_combinations = { "3.6": {"pyqt5", "pyside2", "pyside6", "wx"}, + "3.8": {"pyside6"}, } # Traits version requirement (empty string to mean no specific requirement). @@ -211,11 +212,16 @@ def install(edm, runtime, toolkit, environment, editable, source): ] ) elif toolkit == "pyside6": - commands.extend( - [ - "{edm} run -e {environment} -- pip install pyside6", - "{edm} run -e {environment} -- pip install pillow", - ] + if sys.platform == 'darwin': + commands.append( + "{edm} run -e {environment} -- pip install pyside6<6.2.2'" + ) + else: + commands.append( + "{edm} run -e {environment} -- pip install pyside6" + ) + commands.append( + "{edm} run -e {environment} -- pip install pillow" ) elif toolkit == "wx": if sys.platform == "darwin": diff --git a/examples/dialogs/font_dialog.py b/examples/dialogs/font_dialog.py new file mode 100644 index 000000000..2abfeeea7 --- /dev/null +++ b/examples/dialogs/font_dialog.py @@ -0,0 +1,16 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +from pyface.font_dialog import get_font + +# display a font dialog and get the result +font = get_font(None, font='12 pt Helvetica sans-serif') +if font is not None: + print(font) diff --git a/pyface/font_dialog.py b/pyface/font_dialog.py new file mode 100644 index 000000000..484f27256 --- /dev/null +++ b/pyface/font_dialog.py @@ -0,0 +1,41 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +""" The implementation of a dialog that allows the user to select a font. +""" + +from .constant import OK +from .toolkit import toolkit_object + + +FontDialog = toolkit_object("font_dialog:FontDialog") + + +def get_font(parent, font): + """ Convenience function that displays a font dialog. + + Parameters + ---------- + parent : toolkit control + The parent toolkit control for the modal dialog. + font : Font or font description + The initial Font object or string describing the font. + + Returns + ------- + font : Font or None + The selected font, or None if the user made no selection. + """ + dialog = FontDialog(parent=parent, font=font) + result = dialog.open() + if result == OK: + return dialog.font + else: + return None diff --git a/pyface/i_font_dialog.py b/pyface/i_font_dialog.py new file mode 100644 index 000000000..f7d30b46b --- /dev/null +++ b/pyface/i_font_dialog.py @@ -0,0 +1,24 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +""" The interface for a dialog that allows the user to select a color. """ + +from pyface.ui_traits import PyfaceFont +from pyface.i_dialog import IDialog + + +class IFontDialog(IDialog): + """ The interface for a dialog that allows the user to choose a font. + """ + + # 'IFontDialog' interface ---------------------------------------------# + + #: The font in the dialog. + font = PyfaceFont() diff --git a/pyface/tests/test_font_dialog.py b/pyface/tests/test_font_dialog.py new file mode 100644 index 000000000..b274f1215 --- /dev/null +++ b/pyface/tests/test_font_dialog.py @@ -0,0 +1,87 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + + +import unittest + +from ..font import Font +from ..font_dialog import FontDialog, get_font +from ..toolkit import toolkit_object +from ..util.font_parser import simple_parser + +GuiTestAssistant = toolkit_object("util.gui_test_assistant:GuiTestAssistant") +no_gui_test_assistant = GuiTestAssistant.__name__ == "Unimplemented" + +ModalDialogTester = toolkit_object( + "util.modal_dialog_tester:ModalDialogTester" +) +no_modal_dialog_tester = ModalDialogTester.__name__ == "Unimplemented" + + +@unittest.skipIf(no_gui_test_assistant, "No GuiTestAssistant") +class TestFontDialog(unittest.TestCase, GuiTestAssistant): + + def setUp(self): + GuiTestAssistant.setUp(self) + self.dialog = FontDialog(font="12 Helvetica sans-serif") + + def tearDown(self): + if self.dialog.control is not None: + with self.delete_widget(self.dialog.control): + self.dialog.destroy() + del self.dialog + GuiTestAssistant.tearDown(self) + + def test_font(self): + # test that fonts are translated as expected + self.dialog.font = "10 bold condensed Helvetica sans-serif" + + self.assertFontEqual( + self.dialog.font, + Font(**simple_parser("10 bold condensed Helvetica sans-serif")), + ) + + def test_create(self): + # test that creation and destruction works as expected + with self.event_loop(): + self.dialog._create() + with self.event_loop(): + self.dialog.destroy() + + def test_destroy(self): + # test that destroy works even when no control + with self.event_loop(): + self.dialog.destroy() + + def test_close(self): + # test that close works + with self.event_loop(): + self.dialog._create() + with self.event_loop(): + self.dialog.close() + + def assertFontEqual(self, font1, font2): + state1 = font1.trait_get(transient=lambda x: not x) + state2 = font2.trait_get(transient=lambda x: not x) + self.assertEqual(state1, state2) + + +@unittest.skipIf(no_gui_test_assistant, "No GuiTestAssistant") +class TestGetFont(unittest.TestCase, GuiTestAssistant): + + @unittest.skipIf(no_modal_dialog_tester, "ModalDialogTester unavailable") + def test_close(self): + # test that cancel works as expected + tester = ModalDialogTester( + lambda: get_font(None, "12 Helvetica sans-serif") + ) + tester.open_and_run(when_opened=lambda x: x.close(accept=False)) + + self.assertEqual(tester.result, None) diff --git a/pyface/ui/qt4/font_dialog.py b/pyface/ui/qt4/font_dialog.py new file mode 100644 index 000000000..d37b4764d --- /dev/null +++ b/pyface/ui/qt4/font_dialog.py @@ -0,0 +1,58 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +""" A dialog that allows the user to select a font. """ + +from pyface.qt import QtGui + +from traits.api import provides + +from pyface.font import Font +from pyface.ui_traits import PyfaceFont +from pyface.i_font_dialog import IFontDialog +from .dialog import Dialog + + +@provides(IFontDialog) +class FontDialog(Dialog): + """ A dialog that allows the user to choose a font. + """ + + # 'IFontDialog' interface ---------------------------------------------- + + #: The font in the dialog. + font = PyfaceFont() + + # ------------------------------------------------------------------------ + # 'IDialog' interface. + # ------------------------------------------------------------------------ + + def _create_contents(self, parent): + # In PyQt this is a canned dialog so there are no contents. + pass + + # ------------------------------------------------------------------------ + # 'IWindow' interface. + # ------------------------------------------------------------------------ + + def close(self): + if self.control.result() == QtGui.QDialog.Accepted: + qfont = self.control.selectedFont() + self.font = Font.from_toolkit(qfont) + return super(FontDialog, self).close() + + # ------------------------------------------------------------------------ + # 'IWindow' interface. + # ------------------------------------------------------------------------ + + def _create_control(self, parent): + qfont = self.font.to_toolkit() + dialog = QtGui.QFontDialog(qfont, parent) + return dialog diff --git a/pyface/ui/wx/font_dialog.py b/pyface/ui/wx/font_dialog.py new file mode 100644 index 000000000..f288ade09 --- /dev/null +++ b/pyface/ui/wx/font_dialog.py @@ -0,0 +1,63 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +""" The interface for a dialog that allows the user to select a font. """ + +import wx + +from traits.api import provides + +from pyface.font import Font +from pyface.ui_traits import PyfaceFont +from pyface.i_font_dialog import IFontDialog +from .dialog import Dialog + +# The WxPython version in a convenient to compare form. +wx_version = tuple(int(x) for x in wx.__version__.split('.')[:3]) + + +@provides(IFontDialog) +class FontDialog(Dialog): + """ A dialog for selecting fonts. + """ + + # 'IFontDialog' interface ---------------------------------------------- + + #: The font in the dialog. + font = PyfaceFont() + + # ------------------------------------------------------------------------ + # 'IDialog' interface. + # ------------------------------------------------------------------------ + + def _create_contents(self, parent): + # In wx this is a canned dialog. + pass + + # ------------------------------------------------------------------------ + # 'IWindow' interface. + # ------------------------------------------------------------------------ + + def close(self): + font_data = self.control.GetFontData() + wx_font = font_data.GetChosenFont() + self.font = Font.from_toolkit(wx_font) + super(FontDialog, self).close() + + # ------------------------------------------------------------------------ + # 'IWidget' interface. + # ------------------------------------------------------------------------ + + def _create_control(self, parent): + wx_font = self.font.to_toolkit() + data = wx.FontData() + data.SetInitialFont(wx_font) + dialog = wx.FontDialog(parent, data) + return dialog