mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add ability to enable a menu bar
This commit is contained in:
parent
1de1e2eec9
commit
45cd751de6
7 changed files with 109 additions and 120 deletions
|
|
@ -29,19 +29,16 @@ from beeref.view import BeeGraphicsView
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BeeRefMainWindow(QtWidgets.QWidget):
|
||||
class BeeRefMainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__()
|
||||
app.setOrganizationName(constants.APPNAME)
|
||||
app.setApplicationName(constants.APPNAME)
|
||||
self.setWindowIcon(BeeAssets().logo)
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.setContentsMargins(QtCore.QMargins(0, 0, 0, 0))
|
||||
self.setLayout(layout)
|
||||
self.resize(500, 300)
|
||||
self.view = BeeGraphicsView(app, self)
|
||||
layout.addWidget(self.view)
|
||||
self.setCentralWidget(self.view)
|
||||
self.show()
|
||||
|
||||
def __del__(self):
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ actions = [
|
|||
},
|
||||
{
|
||||
'id': 'insert_images',
|
||||
'text': '&Insert Images...',
|
||||
'text': '&Images...',
|
||||
'shortcuts': ['Ctrl+I'],
|
||||
'callback': 'on_action_insert_images',
|
||||
},
|
||||
|
|
@ -218,6 +218,13 @@ actions = [
|
|||
'settings': 'View/show_scrollbars',
|
||||
'callback': 'on_action_show_scrollbars',
|
||||
},
|
||||
{
|
||||
'id': 'show_menubar',
|
||||
'text': 'Show &Menu Bar',
|
||||
'checkable': True,
|
||||
'settings': 'View/show_menubar',
|
||||
'callback': 'on_action_show_menubar',
|
||||
},
|
||||
{
|
||||
'id': 'fullscreen',
|
||||
'text': '&Fullscreen',
|
||||
|
|
|
|||
|
|
@ -56,9 +56,15 @@ menu_structure = [
|
|||
'fullscreen',
|
||||
'always_on_top',
|
||||
'show_scrollbars',
|
||||
'show_menubar',
|
||||
],
|
||||
},
|
||||
{
|
||||
'menu': '&Insert',
|
||||
'items': [
|
||||
'insert_images',
|
||||
],
|
||||
},
|
||||
'insert_images',
|
||||
{
|
||||
'menu': '&Transform',
|
||||
'items': [
|
||||
|
|
|
|||
|
|
@ -31,31 +31,27 @@ class ActionsMixin:
|
|||
for action in self.bee_actiongroups[group]:
|
||||
action.setEnabled(value)
|
||||
|
||||
def build_menu_and_actions(self, menu=None):
|
||||
def build_menu_and_actions(self):
|
||||
"""Creates a new menu or rebuilds the given menu."""
|
||||
if not menu:
|
||||
menu = QtWidgets.QMenu(self)
|
||||
self.clear_actions(menu)
|
||||
self._create_actions()
|
||||
menu = self._create_menu(self.bee_actions, menu, menu_structure)
|
||||
|
||||
self.actiongroup_set_enabled(
|
||||
'active_when_can_redo', self.undo_stack.canRedo())
|
||||
self.actiongroup_set_enabled(
|
||||
'active_when_can_undo', self.undo_stack.canUndo())
|
||||
self.actiongroup_set_enabled(
|
||||
'active_when_selection', self.scene.has_selection())
|
||||
|
||||
return menu
|
||||
|
||||
def clear_actions(self, menu):
|
||||
if hasattr(self, 'bee_actions'):
|
||||
for action in self.bee_actions.values():
|
||||
self.removeAction(action)
|
||||
if menu:
|
||||
menu.clear()
|
||||
self.context_menu = QtWidgets.QMenu(self)
|
||||
self.toplevel_menus = []
|
||||
self.bee_actions = {}
|
||||
self.bee_actiongroups = defaultdict(list)
|
||||
self._post_create_functions = []
|
||||
self._create_actions()
|
||||
self._create_menu(self.bee_actions, self.context_menu, menu_structure)
|
||||
for func, arg in self._post_create_functions:
|
||||
func(arg)
|
||||
del self._post_create_functions
|
||||
|
||||
def update_menu_and_actions(self):
|
||||
self._build_recent_files()
|
||||
|
||||
def create_menubar(self):
|
||||
menu_bar = QtWidgets.QMenuBar()
|
||||
for menu in self.toplevel_menus:
|
||||
menu_bar.addMenu(menu)
|
||||
return menu_bar
|
||||
|
||||
def _store_checkable_setting(self, key, value):
|
||||
self.settings.setValue(key, value)
|
||||
|
|
@ -68,7 +64,7 @@ class ActionsMixin:
|
|||
if settings_key:
|
||||
val = self.settings.value(settings_key, False, type=bool)
|
||||
qaction.setChecked(val)
|
||||
callback(val)
|
||||
self._post_create_functions.append((callback, val))
|
||||
qaction.toggled.connect(
|
||||
partial(self._store_checkable_setting, settings_key))
|
||||
|
||||
|
|
@ -86,10 +82,12 @@ class ActionsMixin:
|
|||
self.bee_actions[action['id']] = qaction
|
||||
if 'group' in action:
|
||||
self.bee_actiongroups[action['group']].append(qaction)
|
||||
qaction.setEnabled(False)
|
||||
|
||||
def _create_menu(self, actions, menu, items):
|
||||
if isinstance(items, str):
|
||||
items = getattr(self, items)()
|
||||
getattr(self, items)(menu)
|
||||
return menu
|
||||
for item in items:
|
||||
if isinstance(item, str):
|
||||
menu.addAction(actions[item])
|
||||
|
|
@ -97,11 +95,17 @@ class ActionsMixin:
|
|||
menu.addSeparator()
|
||||
if isinstance(item, dict):
|
||||
submenu = menu.addMenu(item['menu'])
|
||||
if menu == self.context_menu:
|
||||
self.toplevel_menus.append(submenu)
|
||||
self._create_menu(actions, submenu, item['items'])
|
||||
|
||||
return menu
|
||||
|
||||
def _build_recent_files(self):
|
||||
def _build_recent_files(self, menu=None):
|
||||
if menu:
|
||||
self._recent_files_submenu = menu
|
||||
self._clear_recent_files()
|
||||
|
||||
files = config.BeeSettings().get_recent_files(existing_only=True)
|
||||
items = []
|
||||
for i, filename in enumerate(files):
|
||||
|
|
@ -111,6 +115,14 @@ class ActionsMixin:
|
|||
qaction.setShortcuts([f'Ctrl+{key}'])
|
||||
qaction.triggered.connect(partial(self.open_from_file, filename))
|
||||
self.addAction(qaction)
|
||||
self._recent_files_submenu.addAction(qaction)
|
||||
self.bee_actions[f'recent_files_{i}'] = qaction
|
||||
items.append(f'recent_files_{i}')
|
||||
return items
|
||||
|
||||
def _clear_recent_files(self):
|
||||
for action in self._recent_files_submenu.actions():
|
||||
self.removeAction(action)
|
||||
self._recent_files_submenu.clear()
|
||||
for key in list(self.bee_actions.keys()):
|
||||
if key.startswith('recent_files_'):
|
||||
self.bee_actions.pop(key)
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self.setScene(self.scene)
|
||||
|
||||
# Context menu and actions
|
||||
self.build_menu_and_actions()
|
||||
self.setContextMenuPolicy(
|
||||
Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.on_context_menu)
|
||||
self.context_menu = self.build_menu_and_actions()
|
||||
|
||||
self.welcome_overlay = gui.WelcomeOverlay(self)
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self.update_window_title()
|
||||
if value:
|
||||
self.settings.update_recent_files(value)
|
||||
self.build_menu_and_actions(self.context_menu)
|
||||
self.update_menu_and_actions()
|
||||
|
||||
def update_window_title(self):
|
||||
clean = self.undo_stack.isClean()
|
||||
|
|
@ -176,7 +176,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
|
||||
def on_action_always_on_top(self, checked):
|
||||
self.parent().setWindowFlag(
|
||||
Qt.WindowFlags.WindowStaysOnTopHint, on=checked)
|
||||
Qt.WindowType.WindowStaysOnTopHint, on=checked)
|
||||
self.parent().destroy()
|
||||
self.parent().create()
|
||||
self.parent().show()
|
||||
|
|
@ -193,6 +193,12 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self.setVerticalScrollBarPolicy(
|
||||
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
||||
def on_action_show_menubar(self, checked):
|
||||
if checked:
|
||||
self.parent().setMenuBar(self.create_menubar())
|
||||
else:
|
||||
self.parent().setMenuBar(None)
|
||||
|
||||
def on_action_undo(self):
|
||||
logger.debug('Undo: %s' % self.undo_stack.undoText())
|
||||
self.undo_stack.undo()
|
||||
|
|
|
|||
|
|
@ -101,18 +101,6 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
assert toggle_mock.connect.call_count == 2
|
||||
callback_mock.assert_called_once_with(True)
|
||||
|
||||
def test_create_actions_enabled_false(self):
|
||||
self.actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'enabled': False,
|
||||
}]
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
self.widget.build_menu_and_actions()
|
||||
qaction = self.widget.actions()[0]
|
||||
qaction.isEnabled() is False
|
||||
|
||||
def test_create_actions_with_group(self):
|
||||
self.actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
|
|
@ -135,15 +123,15 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
}]
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
menu = self.widget.build_menu_and_actions()
|
||||
assert isinstance(menu, QtWidgets.QMenu)
|
||||
self.widget.build_menu_and_actions()
|
||||
assert isinstance(self.widget.context_menu, QtWidgets.QMenu)
|
||||
add_mock.assert_called_once_with(self.widget.bee_actions['foo'])
|
||||
|
||||
def test_build_menu_and_actions_with_separator(self):
|
||||
self.menu_mock.__iter__.return_value = [MENU_SEPARATOR]
|
||||
with patch('PyQt6.QtWidgets.QMenu.addSeparator') as sep_mock:
|
||||
menu = self.widget.build_menu_and_actions()
|
||||
assert isinstance(menu, QtWidgets.QMenu)
|
||||
self.widget.build_menu_and_actions()
|
||||
assert isinstance(self.widget.context_menu, QtWidgets.QMenu)
|
||||
sep_mock.assert_called_once_with()
|
||||
|
||||
def test_build_menu_and_actions_with_submenu(self):
|
||||
|
|
@ -158,8 +146,8 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
with patch('PyQt6.QtWidgets.QMenu.addMenu') as addmenu_mock:
|
||||
addmenu_mock.return_value = QtWidgets.QMenu()
|
||||
menu = self.widget.build_menu_and_actions()
|
||||
assert isinstance(menu, QtWidgets.QMenu)
|
||||
self.widget.build_menu_and_actions()
|
||||
assert isinstance(self.widget.context_menu, QtWidgets.QMenu)
|
||||
addmenu_mock.assert_called_once_with('&Bar')
|
||||
add_mock.assert_called_once_with(
|
||||
self.widget.bee_actions['foo'])
|
||||
|
|
@ -182,25 +170,9 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
self.widget.build_menu_and_actions()
|
||||
self.widget.actiongroup_set_enabled('g1', False)
|
||||
assert self.widget.bee_actions['foo'].isEnabled() is False
|
||||
assert self.widget.bee_actions['bar'].isEnabled() is True
|
||||
|
||||
def test_build_menu_and_actions_enables_actiongroups(self):
|
||||
self.widget.scene.has_selection.return_value = True
|
||||
self.actions_mock.__iter__.return_value = [
|
||||
{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
]
|
||||
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
self.widget.build_menu_and_actions()
|
||||
qaction = self.widget.actions()[0]
|
||||
assert qaction.isEnabled() is True
|
||||
self.widget.actiongroup_set_enabled('g1', True)
|
||||
assert self.widget.bee_actions['foo'].isEnabled() is True
|
||||
assert self.widget.bee_actions['bar'].isEnabled() is False
|
||||
|
||||
def test_build_menu_and_actions_disables_actiongroups(self):
|
||||
self.widget.scene.has_selection.return_value = False
|
||||
|
|
@ -220,10 +192,9 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
|
||||
@patch('beeref.config.BeeSettings.get_recent_files')
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
def test_recent_files(self, triggered_mock, files_mock):
|
||||
def test_create_recent_files(self, triggered_mock, files_mock):
|
||||
files_mock.return_value = [
|
||||
os.path.abspath(f'{i}.bee') for i in range(15)]
|
||||
|
||||
self.menu_mock.__iter__.return_value = [{
|
||||
'menu': 'Open &Recent',
|
||||
'items': '_build_recent_files',
|
||||
|
|
@ -231,7 +202,6 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
|
||||
self.widget.build_menu_and_actions()
|
||||
triggered_mock.connect.assert_called()
|
||||
|
||||
assert len(self.widget.actions()) == 15
|
||||
qaction1 = self.widget.actions()[0]
|
||||
assert qaction1.text() == '0.bee'
|
||||
|
|
@ -249,46 +219,30 @@ class ActionsMixinTestCase(BeeTestCase):
|
|||
assert qaction15.isEnabled() is True
|
||||
assert self.widget.bee_actions['recent_files_14'] == qaction15
|
||||
|
||||
def test_build_menu_and_actions_updates_given_menu(self):
|
||||
self.actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'foo',
|
||||
@patch('beeref.config.BeeSettings.get_recent_files')
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
def test_update_recent_files(self, triggered_mock, files_mock):
|
||||
files_mock.return_value = [os.path.abspath('foo.bee')]
|
||||
self.menu_mock.__iter__.return_value = [{
|
||||
'menu': 'Open &Recent',
|
||||
'items': '_build_recent_files',
|
||||
}]
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
menu = self.widget.build_menu_and_actions()
|
||||
qaction = self.widget.actions()[0]
|
||||
assert qaction.text() == '&Foo'
|
||||
|
||||
self.actions_mock.__iter__.return_value = [{
|
||||
'id': 'bar',
|
||||
'text': '&Bar',
|
||||
'callback': 'on_bar',
|
||||
'group': 'bar',
|
||||
}]
|
||||
self.menu_mock.__iter__.return_value = ['bar']
|
||||
self.widget.build_menu_and_actions(menu)
|
||||
self.widget.build_menu_and_actions()
|
||||
triggered_mock.connect.reset_mock()
|
||||
assert len(self.widget.actions()) == 1
|
||||
assert len(menu.actions()) == 1
|
||||
qaction = self.widget.actions()[0]
|
||||
assert qaction.text() == '&Bar'
|
||||
assert self.widget.bee_actions == {'bar': qaction}
|
||||
assert self.widget.bee_actiongroups['bar'] == [qaction]
|
||||
qaction1 = self.widget.actions()[0]
|
||||
assert qaction1.text() == 'foo.bee'
|
||||
|
||||
def test_clear_actions(self):
|
||||
self.actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'bar',
|
||||
}]
|
||||
self.menu_mock.__iter__.return_value = ['foo']
|
||||
menu = self.widget.build_menu_and_actions()
|
||||
assert menu.actions()
|
||||
assert self.widget.actions()
|
||||
self.widget.clear_actions(menu)
|
||||
assert menu.actions() == []
|
||||
assert self.widget.actions() == []
|
||||
assert self.widget.bee_actions == {}
|
||||
assert self.widget.bee_actiongroups == {}
|
||||
files_mock.return_value = [os.path.abspath('bar.bee')]
|
||||
self.widget.update_menu_and_actions()
|
||||
triggered_mock.connect.assert_called()
|
||||
assert len(self.widget.actions()) == 1
|
||||
qaction1 = self.widget.actions()[0]
|
||||
assert qaction1.text() == 'bar.bee'
|
||||
|
||||
def test_create_menubar(self):
|
||||
self.widget.toplevel_menus = [QtWidgets.QMenu('Foo')]
|
||||
menubar = self.widget.create_menubar()
|
||||
assert isinstance(menubar, QtWidgets.QMenuBar)
|
||||
assert len(menubar.actions()) == 1
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ViewBaseTestCase(BeeTestCase):
|
|||
self.config_mock = config_patcher.start()
|
||||
self.config_mock.filename = None
|
||||
self.addCleanup(config_patcher.stop)
|
||||
self.parent = QtWidgets.QWidget()
|
||||
self.parent = QtWidgets.QMainWindow()
|
||||
self.view = BeeGraphicsView(self.app, self.parent)
|
||||
|
||||
|
||||
|
|
@ -32,11 +32,11 @@ class BeeGraphicsViewTestCase(ViewBaseTestCase):
|
|||
self.config_mock = config_patcher.start()
|
||||
self.config_mock.filename = None
|
||||
self.addCleanup(config_patcher.stop)
|
||||
self.parent = QtWidgets.QWidget()
|
||||
self.parent = QtWidgets.QMainWindow()
|
||||
self.view = BeeGraphicsView(self.app, self.parent)
|
||||
|
||||
def test_inits_menu(self):
|
||||
parent = QtWidgets.QWidget()
|
||||
parent = QtWidgets.QMainWindow()
|
||||
view = BeeGraphicsView(self.app, parent)
|
||||
assert isinstance(view.context_menu, QtWidgets.QMenu)
|
||||
assert len(view.actions()) > 0
|
||||
|
|
@ -46,7 +46,7 @@ class BeeGraphicsViewTestCase(ViewBaseTestCase):
|
|||
@patch('beeref.view.BeeGraphicsView.open_from_file')
|
||||
def test_init_without_filename(self, open_file_mock):
|
||||
self.config_mock.filename = None
|
||||
parent = QtWidgets.QWidget()
|
||||
parent = QtWidgets.QMainWindow()
|
||||
view = BeeGraphicsView(self.app, parent)
|
||||
open_file_mock.assert_not_called()
|
||||
assert parent.windowTitle() == 'BeeRef'
|
||||
|
|
@ -55,7 +55,7 @@ class BeeGraphicsViewTestCase(ViewBaseTestCase):
|
|||
@patch('beeref.view.BeeGraphicsView.open_from_file')
|
||||
def test_init_with_filename(self, open_file_mock):
|
||||
self.config_mock.filename = 'test.bee'
|
||||
parent = QtWidgets.QWidget()
|
||||
parent = QtWidgets.QMainWindow()
|
||||
view = BeeGraphicsView(self.app, parent)
|
||||
open_file_mock.assert_called_once_with('test.bee')
|
||||
del view
|
||||
|
|
@ -373,6 +373,13 @@ class BeeGraphicsViewTestCase(ViewBaseTestCase):
|
|||
assert self.view.scene.items() == []
|
||||
assert self.view.undo_stack.isClean() is False
|
||||
|
||||
def test_on_action_show_menubar(self):
|
||||
self.view.toplevel_menus = [QtWidgets.QMenu('Foo')]
|
||||
self.view.on_action_show_menubar(True)
|
||||
assert len(self.view.parent().menuBar().actions()) == 1
|
||||
self.view.on_action_show_menubar(False)
|
||||
assert self.view.parent().menuBar().actions() == []
|
||||
|
||||
def test_on_action_delete_items(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.view.scene.addItem(item)
|
||||
|
|
|
|||
Loading…
Reference in a new issue