labelimgplusplus 2.0.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libs/settings.py ADDED
@@ -0,0 +1,45 @@
1
+ import os
2
+ import pickle
3
+
4
+
5
+ class Settings(object):
6
+ def __init__(self):
7
+ # Be default, the home will be in the same folder as labelImg
8
+ home = os.path.expanduser("~")
9
+ self.data = {}
10
+ self.path = os.path.join(home, '.labelImgSettings.pkl')
11
+
12
+ def __setitem__(self, key, value):
13
+ self.data[key] = value
14
+
15
+ def __getitem__(self, key):
16
+ return self.data[key]
17
+
18
+ def get(self, key, default=None):
19
+ if key in self.data:
20
+ return self.data[key]
21
+ return default
22
+
23
+ def save(self):
24
+ if self.path:
25
+ with open(self.path, 'wb') as f:
26
+ pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL)
27
+ return True
28
+ return False
29
+
30
+ def load(self):
31
+ try:
32
+ if os.path.exists(self.path):
33
+ with open(self.path, 'rb') as f:
34
+ self.data = pickle.load(f)
35
+ return True
36
+ except:
37
+ print('Loading setting failed')
38
+ return False
39
+
40
+ def reset(self):
41
+ if os.path.exists(self.path):
42
+ os.remove(self.path)
43
+ print('Remove setting pkl file ${0}'.format(self.path))
44
+ self.data = {}
45
+ self.path = None
libs/shape.py ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ try:
6
+ from PyQt5.QtGui import *
7
+ from PyQt5.QtCore import *
8
+ except ImportError:
9
+ from PyQt4.QtGui import *
10
+ from PyQt4.QtCore import *
11
+
12
+ from libs.utils import distance
13
+ import sys
14
+
15
+ DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
16
+ DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128)
17
+ DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
18
+ DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
19
+ DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
20
+ DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)
21
+
22
+
23
+ class Shape(object):
24
+ P_SQUARE, P_ROUND = range(2)
25
+
26
+ MOVE_VERTEX, NEAR_VERTEX = range(2)
27
+
28
+ # The following class variables influence the drawing
29
+ # of _all_ shape objects.
30
+ line_color = DEFAULT_LINE_COLOR
31
+ fill_color = DEFAULT_FILL_COLOR
32
+ select_line_color = DEFAULT_SELECT_LINE_COLOR
33
+ select_fill_color = DEFAULT_SELECT_FILL_COLOR
34
+ vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR
35
+ h_vertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR
36
+ point_type = P_ROUND
37
+ point_size = 16
38
+ scale = 1.0
39
+ label_font_size = 8
40
+
41
+ def __init__(self, label=None, line_color=None, difficult=False, paint_label=False):
42
+ self.label = label
43
+ self.points = []
44
+ self.fill = False
45
+ self.selected = False
46
+ self.difficult = difficult
47
+ self.paint_label = paint_label
48
+
49
+ self._highlight_index = None
50
+ self._highlight_mode = self.NEAR_VERTEX
51
+ self._highlight_settings = {
52
+ self.NEAR_VERTEX: (4, self.P_ROUND),
53
+ self.MOVE_VERTEX: (1.5, self.P_SQUARE),
54
+ }
55
+
56
+ self._closed = False
57
+
58
+ if line_color is not None:
59
+ # Override the class line_color attribute
60
+ # with an object attribute. Currently this
61
+ # is used for drawing the pending line a different color.
62
+ self.line_color = line_color
63
+
64
+ def close(self):
65
+ self._closed = True
66
+
67
+ def reach_max_points(self):
68
+ if len(self.points) >= 4:
69
+ return True
70
+ return False
71
+
72
+ def add_point(self, point):
73
+ if not self.reach_max_points():
74
+ self.points.append(point)
75
+
76
+ def pop_point(self):
77
+ if self.points:
78
+ return self.points.pop()
79
+ return None
80
+
81
+ def is_closed(self):
82
+ return self._closed
83
+
84
+ def set_open(self):
85
+ self._closed = False
86
+
87
+ def paint(self, painter):
88
+ if self.points:
89
+ color = self.select_line_color if self.selected else self.line_color
90
+ pen = QPen(color)
91
+ # Try using integer sizes for smoother drawing(?)
92
+ pen.setWidth(max(1, int(round(2.0 / self.scale))))
93
+ painter.setPen(pen)
94
+
95
+ line_path = QPainterPath()
96
+ vertex_path = QPainterPath()
97
+
98
+ line_path.moveTo(self.points[0])
99
+ # Uncommenting the following line will draw 2 paths
100
+ # for the 1st vertex, and make it non-filled, which
101
+ # may be desirable.
102
+ # self.drawVertex(vertex_path, 0)
103
+
104
+ for i, p in enumerate(self.points):
105
+ line_path.lineTo(p)
106
+ self.draw_vertex(vertex_path, i)
107
+ if self.is_closed():
108
+ line_path.lineTo(self.points[0])
109
+
110
+ painter.drawPath(line_path)
111
+ painter.drawPath(vertex_path)
112
+ painter.fillPath(vertex_path, self.vertex_fill_color)
113
+
114
+ # Draw text at the top-left
115
+ if self.paint_label:
116
+ min_x = sys.maxsize
117
+ min_y = sys.maxsize
118
+ min_y_label = int(1.25 * self.label_font_size)
119
+ for point in self.points:
120
+ min_x = min(min_x, point.x())
121
+ min_y = min(min_y, point.y())
122
+ if min_x != sys.maxsize and min_y != sys.maxsize:
123
+ font = QFont()
124
+ font.setPointSize(self.label_font_size)
125
+ font.setBold(True)
126
+ painter.setFont(font)
127
+ if self.label is None:
128
+ self.label = ""
129
+ if min_y < min_y_label:
130
+ min_y += min_y_label
131
+ painter.drawText(int(min_x), int(min_y), self.label)
132
+
133
+ if self.fill:
134
+ color = self.select_fill_color if self.selected else self.fill_color
135
+ painter.fillPath(line_path, color)
136
+
137
+ def draw_vertex(self, path, i):
138
+ d = self.point_size / self.scale
139
+ shape = self.point_type
140
+ point = self.points[i]
141
+ if i == self._highlight_index:
142
+ size, shape = self._highlight_settings[self._highlight_mode]
143
+ d *= size
144
+ if self._highlight_index is not None:
145
+ self.vertex_fill_color = self.h_vertex_fill_color
146
+ else:
147
+ self.vertex_fill_color = Shape.vertex_fill_color
148
+ if shape == self.P_SQUARE:
149
+ path.addRect(point.x() - d / 2, point.y() - d / 2, d, d)
150
+ elif shape == self.P_ROUND:
151
+ path.addEllipse(point, d / 2.0, d / 2.0)
152
+ else:
153
+ assert False, "unsupported vertex shape"
154
+
155
+ def nearest_vertex(self, point, epsilon):
156
+ index = None
157
+ for i, p in enumerate(self.points):
158
+ dist = distance(p - point)
159
+ if dist <= epsilon:
160
+ index = i
161
+ epsilon = dist
162
+ return index
163
+
164
+ def contains_point(self, point):
165
+ return self.make_path().contains(point)
166
+
167
+ def make_path(self):
168
+ path = QPainterPath(self.points[0])
169
+ for p in self.points[1:]:
170
+ path.lineTo(p)
171
+ return path
172
+
173
+ def bounding_rect(self):
174
+ return self.make_path().boundingRect()
175
+
176
+ def move_by(self, offset):
177
+ self.points = [p + offset for p in self.points]
178
+
179
+ def move_vertex_by(self, i, offset):
180
+ self.points[i] = self.points[i] + offset
181
+
182
+ def highlight_vertex(self, i, action):
183
+ self._highlight_index = i
184
+ self._highlight_mode = action
185
+
186
+ def highlight_clear(self):
187
+ self._highlight_index = None
188
+
189
+ def copy(self):
190
+ shape = Shape("%s" % self.label)
191
+ shape.points = [p for p in self.points]
192
+ shape.fill = self.fill
193
+ shape.selected = self.selected
194
+ shape._closed = self._closed
195
+ if self.line_color != Shape.line_color:
196
+ shape.line_color = self.line_color
197
+ if self.fill_color != Shape.fill_color:
198
+ shape.fill_color = self.fill_color
199
+ shape.difficult = self.difficult
200
+ return shape
201
+
202
+ def __len__(self):
203
+ return len(self.points)
204
+
205
+ def __getitem__(self, key):
206
+ return self.points[key]
207
+
208
+ def __setitem__(self, key, value):
209
+ self.points[key] = value
libs/stringBundle.py ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ if items were added in files in the resources/strings folder,
5
+ then execute "pyrcc5 resources.qrc -o resources.py" in the root directory
6
+ and execute "pyrcc5 ../resources.qrc -o resources.py" in the libs directory
7
+ """
8
+ import re
9
+ import os
10
+ import sys
11
+ import locale
12
+ from libs.ustr import ustr
13
+
14
+ try:
15
+ from PyQt5.QtCore import *
16
+ except ImportError:
17
+ if sys.version_info.major >= 3:
18
+ import sip
19
+ sip.setapi('QVariant', 2)
20
+ from PyQt4.QtCore import *
21
+
22
+
23
+ class StringBundle:
24
+
25
+ __create_key = object()
26
+
27
+ def __init__(self, create_key, locale_str):
28
+ assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle"
29
+ self.id_to_message = {}
30
+ paths = self.__create_lookup_fallback_list(locale_str)
31
+ for path in paths:
32
+ self.__load_bundle(path)
33
+
34
+ @classmethod
35
+ def get_bundle(cls, locale_str=None):
36
+ if locale_str is None:
37
+ try:
38
+ locale_str = locale.getdefaultlocale()[0] if locale.getdefaultlocale() and len(
39
+ locale.getdefaultlocale()) > 0 else os.getenv('LANG')
40
+ except:
41
+ print('Invalid locale')
42
+ locale_str = 'en'
43
+
44
+ return StringBundle(cls.__create_key, locale_str)
45
+
46
+ def get_string(self, string_id):
47
+ assert(string_id in self.id_to_message), "Missing string id : " + string_id
48
+ return self.id_to_message[string_id]
49
+
50
+ def __create_lookup_fallback_list(self, locale_str):
51
+ result_paths = []
52
+ base_path = ":/strings"
53
+ result_paths.append(base_path)
54
+ if locale_str is not None:
55
+ # Don't follow standard BCP47. Simple fallback
56
+ tags = re.split('[^a-zA-Z]', locale_str)
57
+ for tag in tags:
58
+ last_path = result_paths[-1]
59
+ result_paths.append(last_path + '-' + tag)
60
+
61
+ return result_paths
62
+
63
+ def __load_bundle(self, path):
64
+ PROP_SEPERATOR = '='
65
+ f = QFile(path)
66
+ if f.exists():
67
+ if f.open(QIODevice.ReadOnly | QFile.Text):
68
+ text = QTextStream(f)
69
+ text.setCodec("UTF-8")
70
+
71
+ while not text.atEnd():
72
+ line = ustr(text.readLine())
73
+ key_value = line.split(PROP_SEPERATOR)
74
+ key = key_value[0].strip()
75
+ value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"')
76
+ self.id_to_message[key] = value
77
+
78
+ f.close()
libs/styles.py ADDED
@@ -0,0 +1,82 @@
1
+ # libs/styles.py
2
+ """Modern stylesheet definitions for labelImg++."""
3
+
4
+ # Toolbar stylesheet with modern flat design
5
+ TOOLBAR_STYLE = """
6
+ QToolBar {
7
+ background: #f5f5f5;
8
+ border: none;
9
+ border-right: 1px solid #ddd;
10
+ spacing: 2px;
11
+ padding: 4px;
12
+ }
13
+
14
+ QToolBar::separator {
15
+ background: #ddd;
16
+ width: 1px;
17
+ height: 20px;
18
+ margin: 6px 4px;
19
+ }
20
+
21
+ QToolButton {
22
+ background: transparent;
23
+ border: none;
24
+ border-radius: 4px;
25
+ padding: 4px;
26
+ margin: 1px;
27
+ color: #000000;
28
+ }
29
+
30
+ QToolButton:hover {
31
+ background: #e0e0e0;
32
+ }
33
+
34
+ QToolButton:pressed {
35
+ background: #d0d0d0;
36
+ }
37
+
38
+ QToolButton:checked {
39
+ background: #cce5ff;
40
+ color: #004085;
41
+ }
42
+
43
+ QToolButton:disabled {
44
+ color: #999999;
45
+ }
46
+ """
47
+
48
+ # Main window stylesheet
49
+ MAIN_WINDOW_STYLE = """
50
+ QMainWindow {
51
+ background: #ffffff;
52
+ }
53
+
54
+ QDockWidget::title {
55
+ background: #f5f5f5;
56
+ padding: 6px;
57
+ border-bottom: 1px solid #ddd;
58
+ }
59
+
60
+ QListWidget {
61
+ background: #ffffff;
62
+ border: 1px solid #ddd;
63
+ }
64
+
65
+ QListWidget::item:selected {
66
+ background: #cce5ff;
67
+ color: #004085;
68
+ }
69
+ """
70
+
71
+ # Status bar stylesheet
72
+ STATUS_BAR_STYLE = """
73
+ QStatusBar {
74
+ background: #f5f5f5;
75
+ border-top: 1px solid #ddd;
76
+ }
77
+ """
78
+
79
+
80
+ def get_combined_style():
81
+ """Return combined stylesheet for the application."""
82
+ return TOOLBAR_STYLE + MAIN_WINDOW_STYLE + STATUS_BAR_STYLE
libs/toolBar.py ADDED
@@ -0,0 +1,275 @@
1
+ # libs/toolBar.py
2
+ """Custom toolbar and button classes for labelImg++."""
3
+
4
+ try:
5
+ from PyQt5.QtGui import *
6
+ from PyQt5.QtCore import *
7
+ from PyQt5.QtWidgets import *
8
+ except ImportError:
9
+ from PyQt4.QtGui import *
10
+ from PyQt4.QtCore import *
11
+
12
+
13
+ # Base icon size for toolbar buttons (Feather icons are 24x24)
14
+ BASE_ICON_SIZE = 22
15
+ # Minimum and maximum icon sizes for scaling
16
+ MIN_ICON_SIZE = 16
17
+ MAX_ICON_SIZE = 48
18
+
19
+
20
+ def get_dpi_scale_factor():
21
+ """Get the DPI scale factor for the primary screen.
22
+
23
+ Returns:
24
+ float: Scale factor (1.0 for standard 96 DPI, higher for HiDPI displays)
25
+ """
26
+ app = QApplication.instance()
27
+ if app is None:
28
+ return 1.0
29
+
30
+ # Try to get the primary screen
31
+ try:
32
+ screen = app.primaryScreen()
33
+ if screen:
34
+ # Get logical DPI (accounts for user scaling settings)
35
+ logical_dpi = screen.logicalDotsPerInch()
36
+ # Standard DPI is 96 on most systems
37
+ return logical_dpi / 96.0
38
+ except AttributeError:
39
+ # Qt4 fallback
40
+ pass
41
+
42
+ return 1.0
43
+
44
+
45
+ def calculate_icon_size(base_size=BASE_ICON_SIZE):
46
+ """Calculate appropriate icon size based on DPI.
47
+
48
+ Args:
49
+ base_size: Base icon size at standard DPI
50
+
51
+ Returns:
52
+ int: Scaled icon size clamped to min/max bounds
53
+ """
54
+ scale = get_dpi_scale_factor()
55
+ scaled_size = int(base_size * scale)
56
+ return max(MIN_ICON_SIZE, min(MAX_ICON_SIZE, scaled_size))
57
+
58
+
59
+ class ToolBar(QToolBar):
60
+ """Custom toolbar with modern styling and DPI-aware icons."""
61
+
62
+ # Signal emitted when expanded state changes
63
+ expandedChanged = pyqtSignal(bool)
64
+
65
+ def __init__(self, title):
66
+ super(ToolBar, self).__init__(title)
67
+ layout = self.layout()
68
+ layout.setSpacing(0)
69
+ layout.setContentsMargins(2, 2, 2, 2)
70
+ self.setContentsMargins(0, 0, 0, 0)
71
+ self._icon_size = calculate_icon_size()
72
+ self.setIconSize(QSize(self._icon_size, self._icon_size))
73
+ self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
74
+
75
+ # Track tool buttons for icon size updates
76
+ self._tool_buttons = []
77
+
78
+ # Expand/collapse state
79
+ self._expanded = False
80
+ self._collapsed_width = 85
81
+ self._expanded_width = 140
82
+ self._expand_btn = None
83
+
84
+ def addAction(self, action):
85
+ if isinstance(action, QWidgetAction):
86
+ return super(ToolBar, self).addAction(action)
87
+ btn = ToolButton(self._icon_size)
88
+ btn.setDefaultAction(action)
89
+ btn.setToolButtonStyle(self.toolButtonStyle())
90
+ self.addWidget(btn)
91
+ self._tool_buttons.append(btn)
92
+ return btn
93
+
94
+ def addWidget(self, widget):
95
+ """Override to track widgets that support icon sizing."""
96
+ super(ToolBar, self).addWidget(widget)
97
+ if isinstance(widget, (ToolButton, DropdownToolButton)):
98
+ if widget not in self._tool_buttons:
99
+ self._tool_buttons.append(widget)
100
+
101
+ def update_icon_size(self, size=None):
102
+ """Update icon size for toolbar and all buttons.
103
+
104
+ Args:
105
+ size: New icon size, or None to recalculate from DPI
106
+ """
107
+ if size is None:
108
+ size = calculate_icon_size()
109
+
110
+ self._icon_size = size
111
+ self.setIconSize(QSize(size, size))
112
+
113
+ # Update all tracked buttons
114
+ for btn in self._tool_buttons:
115
+ if hasattr(btn, 'update_icon_size'):
116
+ btn.update_icon_size(size)
117
+ else:
118
+ btn.setIconSize(QSize(size, size))
119
+
120
+ def showEvent(self, event):
121
+ """Recalculate icon size when toolbar becomes visible."""
122
+ super(ToolBar, self).showEvent(event)
123
+ # Recalculate in case screen/DPI changed
124
+ new_size = calculate_icon_size()
125
+ if new_size != self._icon_size:
126
+ self.update_icon_size(new_size)
127
+
128
+ def add_expand_button(self):
129
+ """Add expand/collapse toggle button at the bottom of toolbar."""
130
+ from libs.utils import new_icon
131
+
132
+ # Add spacer to push button to bottom
133
+ spacer = QWidget()
134
+ spacer.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
135
+ self.addWidget(spacer)
136
+
137
+ # Add separator
138
+ self.addSeparator()
139
+
140
+ # Create expand button
141
+ self._expand_btn = QToolButton()
142
+ self._expand_btn.setIcon(new_icon('chevron-down'))
143
+ self._expand_btn.setToolTip("Expand toolbar")
144
+ self._expand_btn.setIconSize(QSize(16, 16))
145
+ self._expand_btn.clicked.connect(self.toggle_expanded)
146
+ self._expand_btn.setStyleSheet("""
147
+ QToolButton {
148
+ border: none;
149
+ background: transparent;
150
+ padding: 4px;
151
+ }
152
+ QToolButton:hover {
153
+ background: #e0e0e0;
154
+ border-radius: 4px;
155
+ }
156
+ """)
157
+ self.addWidget(self._expand_btn)
158
+
159
+ # Set initial width
160
+ self.setFixedWidth(self._collapsed_width)
161
+
162
+ def toggle_expanded(self):
163
+ """Toggle between expanded and collapsed state."""
164
+ from libs.utils import new_icon
165
+
166
+ self._expanded = not self._expanded
167
+
168
+ if self._expanded:
169
+ self._expand_btn.setIcon(new_icon('chevron-up'))
170
+ self._expand_btn.setToolTip("Collapse toolbar")
171
+ self.setFixedWidth(self._expanded_width)
172
+ else:
173
+ self._expand_btn.setIcon(new_icon('chevron-down'))
174
+ self._expand_btn.setToolTip("Expand toolbar")
175
+ self.setFixedWidth(self._collapsed_width)
176
+
177
+ self.expandedChanged.emit(self._expanded)
178
+
179
+ def set_expanded(self, expanded):
180
+ """Set the expanded state programmatically."""
181
+ if expanded != self._expanded:
182
+ self.toggle_expanded()
183
+
184
+ def is_expanded(self):
185
+ """Return current expanded state."""
186
+ return self._expanded
187
+
188
+
189
+ class ToolButton(QToolButton):
190
+ """Custom toolbar button with DPI-aware sizing."""
191
+
192
+ def __init__(self, icon_size=None):
193
+ super(ToolButton, self).__init__()
194
+ self._icon_size = icon_size or calculate_icon_size()
195
+ self.setIconSize(QSize(self._icon_size, self._icon_size))
196
+ self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
197
+
198
+ def update_icon_size(self, size):
199
+ """Update the icon size."""
200
+ self._icon_size = size
201
+ self.setIconSize(QSize(size, size))
202
+ self.updateGeometry()
203
+
204
+ def sizeHint(self):
205
+ hint = super(ToolButton, self).sizeHint()
206
+ # Calculate width based on actual text
207
+ fm = self.fontMetrics()
208
+ text = self.text()
209
+ text_width = fm.horizontalAdvance(text) if hasattr(fm, 'horizontalAdvance') else fm.width(text)
210
+ # Add padding for icon and margins
211
+ width = max(hint.width(), text_width + 20, 70)
212
+ height = max(hint.height(), self._icon_size + 30)
213
+ return QSize(width, height)
214
+
215
+ def minimumSizeHint(self):
216
+ # Use actual text width for minimum
217
+ fm = self.fontMetrics()
218
+ text = self.text()
219
+ text_width = fm.horizontalAdvance(text) if hasattr(fm, 'horizontalAdvance') else fm.width(text)
220
+ return QSize(max(text_width + 16, 65), self._icon_size + 24)
221
+
222
+
223
+ class DropdownToolButton(QToolButton):
224
+ """Toolbar button with dropdown menu and DPI-aware sizing."""
225
+
226
+ def __init__(self, text, icon=None, actions=None, icon_size=None):
227
+ super(DropdownToolButton, self).__init__()
228
+ self._icon_size = icon_size or calculate_icon_size()
229
+ self.setText(text)
230
+ if icon:
231
+ self.setIcon(icon)
232
+ self.setIconSize(QSize(self._icon_size, self._icon_size))
233
+ self.setPopupMode(QToolButton.InstantPopup)
234
+ self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
235
+
236
+ # Create menu for dropdown actions
237
+ self.dropdown_menu = QMenu(self)
238
+ if actions:
239
+ for action in actions:
240
+ if action is None:
241
+ self.dropdown_menu.addSeparator()
242
+ else:
243
+ self.dropdown_menu.addAction(action)
244
+ self.setMenu(self.dropdown_menu)
245
+
246
+ def add_action(self, action):
247
+ """Add an action to the dropdown menu."""
248
+ if action is None:
249
+ self.dropdown_menu.addSeparator()
250
+ else:
251
+ self.dropdown_menu.addAction(action)
252
+
253
+ def update_icon_size(self, size):
254
+ """Update the icon size."""
255
+ self._icon_size = size
256
+ self.setIconSize(QSize(size, size))
257
+ self.updateGeometry()
258
+
259
+ def sizeHint(self):
260
+ hint = super(DropdownToolButton, self).sizeHint()
261
+ # Calculate width based on actual text
262
+ fm = self.fontMetrics()
263
+ text = self.text()
264
+ text_width = fm.horizontalAdvance(text) if hasattr(fm, 'horizontalAdvance') else fm.width(text)
265
+ # Add padding for icon, dropdown arrow, and margins
266
+ width = max(hint.width(), text_width + 30, 70)
267
+ height = max(hint.height(), self._icon_size + 30)
268
+ return QSize(width, height)
269
+
270
+ def minimumSizeHint(self):
271
+ # Use actual text width for minimum
272
+ fm = self.fontMetrics()
273
+ text = self.text()
274
+ text_width = fm.horizontalAdvance(text) if hasattr(fm, 'horizontalAdvance') else fm.width(text)
275
+ return QSize(max(text_width + 24, 65), self._icon_size + 24)
libs/ustr.py ADDED
@@ -0,0 +1,17 @@
1
+ import sys
2
+ from libs.constants import DEFAULT_ENCODING
3
+
4
+ def ustr(x):
5
+ """py2/py3 unicode helper"""
6
+
7
+ if sys.version_info < (3, 0, 0):
8
+ from PyQt4.QtCore import QString
9
+ if type(x) == str:
10
+ return x.decode(DEFAULT_ENCODING)
11
+ if type(x) == QString:
12
+ # https://blog.csdn.net/friendan/article/details/51088476
13
+ # https://blog.csdn.net/xxm524/article/details/74937308
14
+ return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore')
15
+ return x
16
+ else:
17
+ return x