Coverage for graphica/gui/shell/header.py: 99.2%

114 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""上部バー: ≡(メニュー)・ロゴ・プロジェクトのタブ・コマンドを検索・モードの切り替え・元に戻す/やり直す・保存・ 

2クイックアクセス・エクスポート。高さ 48px の1段。見本は docs/dev/mockups/gui_redesign/。 

3 

4押したときの行き先(表示中のタブ)は MainAppWindow がつなぐ。ここは部品と並びだけを持つ。 

5""" 

6from PySide6.QtCore import QSize, Qt 

7from PySide6.QtGui import QIcon 

8from PySide6.QtWidgets import (QFrame, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QStackedWidget, QToolButton, 

9 QWidget) 

10 

11from graphica.core.i18n import tr 

12from graphica.gui import ui_mode 

13from graphica.gui.shell.icons import themed_icon 

14from graphica.gui.widgets import SegmentedControl 

15 

16HEADER_HEIGHT = 48 

17SEARCH_WIDTH = 300 

18COMMAND_PALETTE_SHORTCUT_TEXT = "Ctrl+Shift+P" 

19 

20 

21def _header_button(name, tooltip): 

22 button = QToolButton() 

23 button.setObjectName("header_button") 

24 button.setToolTip(tooltip) 

25 button.setIconSize(QSize(18, 18)) 

26 button.setFixedSize(30, 30) 

27 button.setFocusPolicy(Qt.FocusPolicy.NoFocus) 

28 button.setCursor(Qt.CursorShape.PointingHandCursor) 

29 button.setProperty("iconName", name) 

30 button.setIcon(themed_icon(name)) 

31 return button 

32 

33 

34def _separator(): 

35 line = QFrame() 

36 line.setObjectName("header_separator") 

37 line.setFrameShape(QFrame.Shape.VLine) 

38 line.setFixedSize(1, 20) 

39 return line 

40 

41 

42class AppHeader(QWidget): 

43 def __init__(self, tab_bar, app_icon_path, parent=None): 

44 super().__init__(parent) 

45 self.setObjectName("app_header") 

46 # 上の帯の色を QSS で塗るため(素の QWidget は背景を描かない) 

47 self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) 

48 self.setFixedHeight(HEADER_HEIGHT) 

49 layout = QHBoxLayout(self) 

50 layout.setContentsMargins(8, 0, 12, 0) 

51 layout.setSpacing(6) 

52 

53 self.menu_button = _header_button("menu-2", tr("メニュー")) 

54 self.menu_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) 

55 # ▼の印を出さない(≡ だけ) 

56 self.menu_button.setStyleSheet("QToolButton::menu-indicator { image: none; width: 0; }") 

57 layout.addWidget(self.menu_button) 

58 

59 self.logo = QLabel() 

60 self.logo.setObjectName("header_logo") 

61 self.logo.setPixmap(QIcon(app_icon_path).pixmap(22, 22)) 

62 layout.addWidget(self.logo) 

63 self.title_label = QLabel("Graphica") 

64 self.title_label.setObjectName("header_title") 

65 layout.addWidget(self.title_label) 

66 layout.addSpacing(8) 

67 

68 self.tab_bar = tab_bar 

69 layout.addWidget(tab_bar, 0, Qt.AlignmentFlag.AlignVCenter) 

70 self.add_tab_button = _header_button("plus", tr("新しいプロジェクトタブを開く")) 

71 self.add_tab_button.setObjectName("add_tab_button") 

72 layout.addWidget(self.add_tab_button) 

73 

74 layout.addStretch(1) 

75 self.search_button = self._make_search_button() 

76 layout.addWidget(self.search_button) 

77 layout.addStretch(1) 

78 

79 self.mode_switch = SegmentedControl() 

80 self.mode_switch.setObjectName("mode_switch") 

81 for mode in ui_mode.MODES: 

82 self.mode_switch.addItem(ui_mode.mode_label(mode), mode) 

83 self.mode_switch.setFixedWidth(190) 

84 layout.addWidget(self.mode_switch) 

85 layout.addWidget(_separator()) 

86 

87 self.undo_button = _header_button("arrow-back-up", tr("元に戻す")) 

88 self.redo_button = _header_button("arrow-forward-up", tr("やり直し")) 

89 self.save_button = _header_button("device-floppy", tr("上書き保存")) 

90 for button in (self.undo_button, self.redo_button, self.save_button): 

91 layout.addWidget(button) 

92 

93 # 表示中のタブのクイックアクセス(ピン留め)。タブごとのツールバーを重ねて、前のタブのものだけを見せる 

94 self.quick_access_stack = QStackedWidget() 

95 self.quick_access_stack.setObjectName("quick_access_slot") 

96 self.quick_access_stack.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) 

97 layout.addWidget(self.quick_access_stack) 

98 

99 self.export_button = QPushButton(tr("エクスポート")) 

100 self.export_button.setObjectName("header_export_button") 

101 self.export_button.setProperty("primaryButton", True) 

102 self.export_button.setCursor(Qt.CursorShape.PointingHandCursor) 

103 self.export_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) 

104 self.export_button.setIconSize(QSize(16, 16)) 

105 self.export_button.setIcon(themed_icon("download", size=16, token="on_accent")) 

106 layout.addWidget(self.export_button) 

107 

108 def _make_search_button(self): 

109 button = QPushButton() 

110 button.setObjectName("command_search") 

111 button.setCursor(Qt.CursorShape.PointingHandCursor) 

112 button.setFocusPolicy(Qt.FocusPolicy.NoFocus) 

113 button.setFixedSize(SEARCH_WIDTH, 30) 

114 inner = QHBoxLayout(button) 

115 inner.setContentsMargins(10, 0, 8, 0) 

116 inner.setSpacing(6) 

117 self.search_icon = QLabel() 

118 self.search_icon.setPixmap(themed_icon("search", size=15, token="text_muted").pixmap(15, 15)) 

119 text = QLabel(tr("コマンドを検索")) 

120 text.setObjectName("command_search_text") 

121 shortcut = QLabel(COMMAND_PALETTE_SHORTCUT_TEXT) 

122 shortcut.setObjectName("kbd_hint") 

123 for label in (self.search_icon, text, shortcut): 

124 # ボタンの子のラベルが押下を取らないように(ボタン全体で押せる) 

125 label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) 

126 inner.addWidget(self.search_icon) 

127 inner.addWidget(text) 

128 inner.addStretch(1) 

129 inner.addWidget(shortcut) 

130 return button 

131 

132 def set_mode(self, mode): 

133 blocked = self.mode_switch.blockSignals(True) 

134 self.mode_switch.setCurrentIndex(self.mode_switch.findData(mode)) 

135 self.mode_switch.blockSignals(blocked) 

136 

137 def refresh_icons(self): 

138 for button in self.findChildren(QToolButton, "header_button") + [self.add_tab_button]: 

139 name = button.property("iconName") 

140 if name: 140 ↛ 138line 140 didn't jump to line 138 because the condition on line 140 was always true

141 button.setIcon(themed_icon(name)) 

142 self.search_icon.setPixmap(themed_icon("search", size=15, token="text_muted").pixmap(15, 15)) 

143 self.export_button.setIcon(themed_icon("download", size=16, token="on_accent"))