pyscreeps-arena 0.3.6__py3-none-any.whl → 0.5.7.1__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.
- pyscreeps_arena/__init__.py +59 -2
- pyscreeps_arena/compiler.py +568 -60
- pyscreeps_arena/core/config.py +1 -1
- pyscreeps_arena/core/const.py +6 -5
- pyscreeps_arena/localization.py +10 -0
- pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena/ui/P2PY.py +108 -0
- pyscreeps_arena/ui/__init__.py +12 -0
- pyscreeps_arena/ui/creeplogic_edit.py +14 -0
- pyscreeps_arena/ui/map_render.py +705 -0
- pyscreeps_arena/ui/mapviewer.py +14 -0
- pyscreeps_arena/ui/project_ui.py +215 -0
- pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
- pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
- pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +770 -0
- pyscreeps_arena/ui/qmapv/__init__.py +3 -0
- pyscreeps_arena/ui/qmapv/qcinfo.py +567 -0
- pyscreeps_arena/ui/qmapv/qco.py +441 -0
- pyscreeps_arena/ui/qmapv/qmapv.py +728 -0
- pyscreeps_arena/ui/qmapv/test_array_drag.py +191 -0
- pyscreeps_arena/ui/qmapv/test_drag.py +107 -0
- pyscreeps_arena/ui/qmapv/test_qcinfo.py +169 -0
- pyscreeps_arena/ui/qmapv/test_qco_drag.py +7 -0
- pyscreeps_arena/ui/qmapv/test_qmapv.py +224 -0
- pyscreeps_arena/ui/qmapv/test_simple_array.py +303 -0
- pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
- pyscreeps_arena/ui/qrecipe/model.py +434 -0
- pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
- pyscreeps_arena/ui/rs_icon.py +43 -0
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.7.1.dist-info}/METADATA +15 -3
- pyscreeps_arena-0.5.7.1.dist-info/RECORD +40 -0
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.7.1.dist-info}/WHEEL +1 -1
- pyscreeps_arena-0.5.7.1.dist-info/entry_points.txt +4 -0
- pyscreeps_arena-0.3.6.dist-info/RECORD +0 -17
- pyscreeps_arena-0.3.6.dist-info/entry_points.txt +0 -2
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.7.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
QPSA Cell Info Component - 单元格信息组件
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional, List, Dict, Any
|
|
6
|
+
import json
|
|
7
|
+
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
8
|
+
QListWidget, QListWidgetItem, QFrame,
|
|
9
|
+
QSpacerItem, QSizePolicy)
|
|
10
|
+
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QRectF, QMimeData
|
|
11
|
+
from PyQt6.QtGui import QPixmap, QPainter, QPen, QColor, QBrush, QPainterPath, QMouseEvent, QDrag
|
|
12
|
+
|
|
13
|
+
from .qmapv import CellInfo
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class QPSACellObjectItem(QWidget):
|
|
17
|
+
"""Game object item widget for the list."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, obj_id: str, obj_type: str, obj_name: str,
|
|
20
|
+
x: int, y: int, image: Optional[QPixmap] = None, method: str = "", parent=None):
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
self._id = obj_id
|
|
23
|
+
self._name = obj_name
|
|
24
|
+
self._x = x
|
|
25
|
+
self._y = y
|
|
26
|
+
self._type = obj_type
|
|
27
|
+
self._method = method
|
|
28
|
+
self._image = image
|
|
29
|
+
self._init_ui(obj_type)
|
|
30
|
+
|
|
31
|
+
def _init_ui(self, obj_type: str):
|
|
32
|
+
"""Initialize UI components."""
|
|
33
|
+
# Main horizontal layout
|
|
34
|
+
layout = QHBoxLayout()
|
|
35
|
+
layout.setContentsMargins(3, 3, 3, 3)
|
|
36
|
+
|
|
37
|
+
# Left side - shape/icon
|
|
38
|
+
self._icon_label = QLabel()
|
|
39
|
+
self._icon_label.setFixedSize(30, 30) # Increased by 10%
|
|
40
|
+
self._icon_label.setStyleSheet("""
|
|
41
|
+
QLabel {
|
|
42
|
+
border: 1px solid #ccc;
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
background-color: #f0f0f0;
|
|
45
|
+
}
|
|
46
|
+
""")
|
|
47
|
+
self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
48
|
+
self._update_icon()
|
|
49
|
+
layout.addWidget(self._icon_label)
|
|
50
|
+
|
|
51
|
+
# Middle section - vertical layout for type and id
|
|
52
|
+
middle_layout = QVBoxLayout()
|
|
53
|
+
middle_layout.setSpacing(2)
|
|
54
|
+
|
|
55
|
+
# Type label (first row)
|
|
56
|
+
type_label = QLabel(obj_type)
|
|
57
|
+
type_label.setStyleSheet("font-weight: bold; font-size: 11px;") # Increased by 10%
|
|
58
|
+
type_label.setWordWrap(False)
|
|
59
|
+
middle_layout.addWidget(type_label)
|
|
60
|
+
|
|
61
|
+
# ID label (second row)
|
|
62
|
+
id_label = QLabel(self._id)
|
|
63
|
+
id_label.setStyleSheet("font-size: 10px; color: #666;") # Increased by 10%
|
|
64
|
+
id_label.setWordWrap(False)
|
|
65
|
+
middle_layout.addWidget(id_label)
|
|
66
|
+
|
|
67
|
+
layout.addLayout(middle_layout)
|
|
68
|
+
|
|
69
|
+
# Add spacer to push content to the left
|
|
70
|
+
layout.addStretch()
|
|
71
|
+
|
|
72
|
+
self.setLayout(layout)
|
|
73
|
+
|
|
74
|
+
def _update_icon(self):
|
|
75
|
+
"""Update the icon based on current image."""
|
|
76
|
+
if self._image and not self._image.isNull():
|
|
77
|
+
# Get the label size and use 90% of it for the image
|
|
78
|
+
label_width = self._icon_label.width()
|
|
79
|
+
label_height = self._icon_label.height()
|
|
80
|
+
|
|
81
|
+
# Calculate 90% of container size
|
|
82
|
+
target_width = int(label_width * 0.9)
|
|
83
|
+
target_height = int(label_height * 0.9)
|
|
84
|
+
|
|
85
|
+
# Scale image to fit the label with 90% size
|
|
86
|
+
scaled_image = self._image.scaled(
|
|
87
|
+
target_width, target_height, Qt.AspectRatioMode.KeepAspectRatio,
|
|
88
|
+
Qt.TransformationMode.SmoothTransformation
|
|
89
|
+
)
|
|
90
|
+
self._icon_label.setPixmap(scaled_image)
|
|
91
|
+
self._icon_label.setText("")
|
|
92
|
+
print(f"[DEBUG] Set icon size to {target_width}x{target_height} (90% of {label_width}x{label_height})") # 调试输出
|
|
93
|
+
else:
|
|
94
|
+
# Use placeholder text if no image
|
|
95
|
+
self._icon_label.setPixmap(QPixmap())
|
|
96
|
+
self._icon_label.setText("◆") # Placeholder shape
|
|
97
|
+
|
|
98
|
+
# Properties
|
|
99
|
+
@property
|
|
100
|
+
def id(self) -> str:
|
|
101
|
+
"""Get object ID."""
|
|
102
|
+
return self._id
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def name(self) -> str:
|
|
106
|
+
"""Get object name."""
|
|
107
|
+
return self._name
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def image(self) -> Optional[QPixmap]:
|
|
111
|
+
"""Get/set object icon image."""
|
|
112
|
+
return self._image
|
|
113
|
+
|
|
114
|
+
@image.setter
|
|
115
|
+
def image(self, value: Optional[QPixmap]):
|
|
116
|
+
"""Set object icon image."""
|
|
117
|
+
self._image = value
|
|
118
|
+
self._update_icon()
|
|
119
|
+
|
|
120
|
+
def mousePressEvent(self, event: QMouseEvent):
|
|
121
|
+
"""Handle mouse press event to start drag."""
|
|
122
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
123
|
+
print(f"[DEBUG] Starting drag for object: {self._id}")
|
|
124
|
+
# Create drag data
|
|
125
|
+
drag_data = {
|
|
126
|
+
"x": self._x,
|
|
127
|
+
"y": self._y,
|
|
128
|
+
"type": self._type,
|
|
129
|
+
"method": getattr(self, '_method', ''), # Use instance method or default empty
|
|
130
|
+
"id": self._id,
|
|
131
|
+
"name": self._name
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Convert to JSON string
|
|
135
|
+
json_data = json.dumps(drag_data, ensure_ascii=False, indent=2)
|
|
136
|
+
print(f"[DEBUG] Drag data: {json_data}")
|
|
137
|
+
|
|
138
|
+
# Create mime data
|
|
139
|
+
mime_data = QMimeData()
|
|
140
|
+
mime_data.setText(json_data)
|
|
141
|
+
mime_data.setData("application/json", json_data.encode('utf-8'))
|
|
142
|
+
|
|
143
|
+
# Add pixmap if available
|
|
144
|
+
if self._image and not self._image.isNull():
|
|
145
|
+
print(f"[DEBUG] Adding image to drag data")
|
|
146
|
+
mime_data.setImageData(self._image)
|
|
147
|
+
|
|
148
|
+
# Create drag object
|
|
149
|
+
drag = QDrag(self)
|
|
150
|
+
drag.setMimeData(mime_data)
|
|
151
|
+
|
|
152
|
+
# Start drag with multiple drop actions
|
|
153
|
+
drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction)
|
|
154
|
+
|
|
155
|
+
event.accept()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class CoordinateLabel(QLabel):
|
|
159
|
+
"""Custom coordinate label that supports drag and drop."""
|
|
160
|
+
|
|
161
|
+
def __init__(self, parent=None):
|
|
162
|
+
super().__init__(parent)
|
|
163
|
+
self._x = 0
|
|
164
|
+
self._y = 0
|
|
165
|
+
|
|
166
|
+
def set_coordinates(self, x: int, y: int):
|
|
167
|
+
"""Set coordinates and update display."""
|
|
168
|
+
self._x = x
|
|
169
|
+
self._y = y
|
|
170
|
+
self.setText(f"{{x: {x}, y: {y}}}")
|
|
171
|
+
|
|
172
|
+
def mousePressEvent(self, event: QMouseEvent):
|
|
173
|
+
"""Handle mouse press event to start drag."""
|
|
174
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
175
|
+
print(f"[DEBUG] Starting drag for coordinates: ({self._x}, {self._y})")
|
|
176
|
+
# Create drag data for Point type
|
|
177
|
+
drag_data = {
|
|
178
|
+
"x": self._x,
|
|
179
|
+
"y": self._y,
|
|
180
|
+
"type": "Point",
|
|
181
|
+
"method": "Point",
|
|
182
|
+
"id": f"{self._x}◇{self._y}",
|
|
183
|
+
"name": f"p{self._x}◇{self._y}"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Convert to JSON string
|
|
187
|
+
json_data = json.dumps(drag_data, ensure_ascii=False, indent=2)
|
|
188
|
+
print(f"[DEBUG] Drag data: {json_data}")
|
|
189
|
+
|
|
190
|
+
# Create mime data
|
|
191
|
+
mime_data = QMimeData()
|
|
192
|
+
mime_data.setText(json_data)
|
|
193
|
+
mime_data.setData("application/json", json_data.encode('utf-8'))
|
|
194
|
+
|
|
195
|
+
# Create drag object
|
|
196
|
+
drag = QDrag(self)
|
|
197
|
+
drag.setMimeData(mime_data)
|
|
198
|
+
|
|
199
|
+
# Start drag with multiple drop actions
|
|
200
|
+
drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction)
|
|
201
|
+
|
|
202
|
+
event.accept()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class QPSACellInfo(QWidget):
|
|
206
|
+
"""Cell information display component."""
|
|
207
|
+
|
|
208
|
+
def __init__(self, parent=None):
|
|
209
|
+
super().__init__(parent)
|
|
210
|
+
self._data: Optional[CellInfo] = None
|
|
211
|
+
self._x: int = 0
|
|
212
|
+
self._y: int = 0
|
|
213
|
+
self._objects: List[Dict[str, Any]] = []
|
|
214
|
+
self._image: Optional[QPixmap] = None # Map image for object icons
|
|
215
|
+
self._init_ui()
|
|
216
|
+
|
|
217
|
+
def _init_ui(self):
|
|
218
|
+
"""Initialize UI components."""
|
|
219
|
+
# Main vertical layout
|
|
220
|
+
layout = QVBoxLayout()
|
|
221
|
+
layout.setContentsMargins(6, 6, 6, 6) # Further reduced margins
|
|
222
|
+
layout.setSpacing(4) # Further reduced spacing
|
|
223
|
+
|
|
224
|
+
# First row - coordinates
|
|
225
|
+
self._coord_label = CoordinateLabel()
|
|
226
|
+
self._coord_label.setStyleSheet("font-family: monospace; font-size: 21px; font-weight: bold;")
|
|
227
|
+
self._coord_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
228
|
+
layout.addWidget(self._coord_label)
|
|
229
|
+
|
|
230
|
+
# Second row - terrain info with rounded rectangle
|
|
231
|
+
terrain_frame = QFrame()
|
|
232
|
+
terrain_frame.setFrameStyle(QFrame.Shape.NoFrame)
|
|
233
|
+
terrain_layout = QHBoxLayout()
|
|
234
|
+
terrain_layout.setContentsMargins(0, 0, 0, 0)
|
|
235
|
+
|
|
236
|
+
# Terrain type label
|
|
237
|
+
self._terrain_label = QLabel("Plain")
|
|
238
|
+
self._terrain_label.setFixedWidth(50) # Reduced from 60
|
|
239
|
+
self._terrain_label.setStyleSheet("font-size: 12px;")
|
|
240
|
+
terrain_layout.addWidget(self._terrain_label)
|
|
241
|
+
|
|
242
|
+
# Terrain color indicator (custom painted widget)
|
|
243
|
+
self._terrain_indicator = TerrainIndicator()
|
|
244
|
+
self._terrain_indicator.setFixedSize(30, 16) # Reduced from 40x20 (保持不变)
|
|
245
|
+
terrain_layout.addWidget(self._terrain_indicator)
|
|
246
|
+
|
|
247
|
+
# Spacer
|
|
248
|
+
terrain_layout.addSpacerItem(QSpacerItem(10, 0, QSizePolicy.Policy.Expanding)) # Reduced from 20
|
|
249
|
+
|
|
250
|
+
# Cost label
|
|
251
|
+
self._cost_label = QLabel("Cost: 2")
|
|
252
|
+
self._cost_label.setStyleSheet("font-size: 12px; color: #666;")
|
|
253
|
+
terrain_layout.addWidget(self._cost_label)
|
|
254
|
+
|
|
255
|
+
terrain_frame.setLayout(terrain_layout)
|
|
256
|
+
layout.addWidget(terrain_frame)
|
|
257
|
+
|
|
258
|
+
# Third row - objects list
|
|
259
|
+
self._objects_list = QListWidget()
|
|
260
|
+
self._objects_list.setFrameStyle(QFrame.Shape.Box)
|
|
261
|
+
self._objects_list.setMaximumHeight(80) # Approximately 2 items high
|
|
262
|
+
self._objects_list.setStyleSheet("""
|
|
263
|
+
QListWidget {
|
|
264
|
+
border: 1px solid #ccc;
|
|
265
|
+
border-radius: 4px;
|
|
266
|
+
background-color: white;
|
|
267
|
+
}
|
|
268
|
+
QListWidget::item {
|
|
269
|
+
border-bottom: 1px solid #eee;
|
|
270
|
+
}
|
|
271
|
+
QListWidget::item:selected {
|
|
272
|
+
background-color: #e3f2fd;
|
|
273
|
+
}
|
|
274
|
+
""")
|
|
275
|
+
# Enable drag functionality
|
|
276
|
+
self._objects_list.setDragEnabled(True)
|
|
277
|
+
self._objects_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
|
|
278
|
+
|
|
279
|
+
# Enable drop functionality for the widget
|
|
280
|
+
self.setAcceptDrops(True)
|
|
281
|
+
|
|
282
|
+
layout.addWidget(self._objects_list)
|
|
283
|
+
|
|
284
|
+
self.setLayout(layout)
|
|
285
|
+
|
|
286
|
+
# Set minimum size - further reduced to match list item width
|
|
287
|
+
self.setMinimumWidth(180) # Further reduced from 180 (10% reduction)
|
|
288
|
+
self.setMaximumWidth(180)
|
|
289
|
+
self.setMinimumHeight(200) # Increased for larger font
|
|
290
|
+
self.setMaximumHeight(200)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _update_coord_display(self):
|
|
294
|
+
"""Update coordinate display."""
|
|
295
|
+
if self._data:
|
|
296
|
+
self._coord_label.set_coordinates(self._data.x, self._data.y)
|
|
297
|
+
else:
|
|
298
|
+
self._coord_label.set_coordinates(0, 0)
|
|
299
|
+
|
|
300
|
+
def _update_terrain_display(self):
|
|
301
|
+
"""Update terrain display."""
|
|
302
|
+
if self._data:
|
|
303
|
+
terrain_map = {
|
|
304
|
+
'2': 'Plain',
|
|
305
|
+
'A': 'Swamp',
|
|
306
|
+
'X': 'TWall'
|
|
307
|
+
}
|
|
308
|
+
terrain_type = terrain_map.get(self._data.terrain, 'Plain')
|
|
309
|
+
self._terrain_label.setText(terrain_type)
|
|
310
|
+
|
|
311
|
+
# Update terrain color
|
|
312
|
+
terrain_colors = {
|
|
313
|
+
'2': QColor(144, 238, 144), # Light green for plain
|
|
314
|
+
'A': QColor(160, 82, 45), # Brown for swamp
|
|
315
|
+
'X': QColor(128, 128, 128) # Gray for wall
|
|
316
|
+
}
|
|
317
|
+
color = terrain_colors.get(self._data.terrain, QColor(144, 238, 144))
|
|
318
|
+
self._terrain_indicator.set_color(color)
|
|
319
|
+
|
|
320
|
+
# Update cost
|
|
321
|
+
cost = self._data.cost
|
|
322
|
+
self._cost_label.setText(f"Cost: {cost}")
|
|
323
|
+
else:
|
|
324
|
+
self._terrain_label.setText("Plain")
|
|
325
|
+
self._terrain_indicator.set_color(QColor(144, 238, 144))
|
|
326
|
+
self._cost_label.setText("Cost: 2")
|
|
327
|
+
|
|
328
|
+
def _update_objects_list(self):
|
|
329
|
+
"""Update objects list display."""
|
|
330
|
+
self._objects_list.clear()
|
|
331
|
+
|
|
332
|
+
if self._objects:
|
|
333
|
+
for obj in self._objects:
|
|
334
|
+
obj_id = obj.get('id', 'unknown')
|
|
335
|
+
obj_type = obj.get('type', 'Unknown')
|
|
336
|
+
obj_name = obj.get('name', obj_id)
|
|
337
|
+
|
|
338
|
+
# Extract object icon from map image if available
|
|
339
|
+
obj_image = None
|
|
340
|
+
if self._image and not self._image.isNull() and self._data:
|
|
341
|
+
# Calculate cell size based on map image dimensions
|
|
342
|
+
# Assuming map is 100x100 cells (default from qmapv)
|
|
343
|
+
map_width = 100
|
|
344
|
+
map_height = 100
|
|
345
|
+
|
|
346
|
+
cell_width = self._image.width() // map_width
|
|
347
|
+
cell_height = self._image.height() // map_height
|
|
348
|
+
|
|
349
|
+
# Calculate the position of this cell in the image
|
|
350
|
+
cell_x_offset = self._data.x * cell_width
|
|
351
|
+
cell_y_offset = self._data.y * cell_height
|
|
352
|
+
|
|
353
|
+
# Extract the entire cell region
|
|
354
|
+
if (cell_x_offset + cell_width <= self._image.width() and
|
|
355
|
+
cell_y_offset + cell_height <= self._image.height() and
|
|
356
|
+
cell_x_offset >= 0 and cell_y_offset >= 0):
|
|
357
|
+
# Extract the full cell
|
|
358
|
+
cell_image = self._image.copy(cell_x_offset, cell_y_offset, cell_width, cell_height)
|
|
359
|
+
obj_image = cell_image
|
|
360
|
+
|
|
361
|
+
# Get method from object data if available
|
|
362
|
+
method = obj.get('method', '')
|
|
363
|
+
|
|
364
|
+
# Create custom widget for the object
|
|
365
|
+
item_widget = QPSACellObjectItem(
|
|
366
|
+
obj_id, obj_type, obj_name, self._x, self._y, obj_image, method
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Create list widget item and set our custom widget
|
|
370
|
+
list_item = QListWidgetItem()
|
|
371
|
+
list_item.setSizeHint(item_widget.sizeHint())
|
|
372
|
+
|
|
373
|
+
self._objects_list.addItem(list_item)
|
|
374
|
+
self._objects_list.setItemWidget(list_item, item_widget)
|
|
375
|
+
|
|
376
|
+
# Public properties
|
|
377
|
+
@property
|
|
378
|
+
def data(self) -> Optional[CellInfo]:
|
|
379
|
+
"""Get/set cell data."""
|
|
380
|
+
return self._data
|
|
381
|
+
|
|
382
|
+
@data.setter
|
|
383
|
+
def data(self, value: Optional[CellInfo]):
|
|
384
|
+
"""Set cell data and update display."""
|
|
385
|
+
self._data = value
|
|
386
|
+
if value:
|
|
387
|
+
self._x = value.x
|
|
388
|
+
self._y = value.y
|
|
389
|
+
self._objects = value.objects
|
|
390
|
+
else:
|
|
391
|
+
self._x = 0
|
|
392
|
+
self._y = 0
|
|
393
|
+
self._objects = []
|
|
394
|
+
|
|
395
|
+
self._update_coord_display()
|
|
396
|
+
self._update_terrain_display()
|
|
397
|
+
self._update_objects_list()
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def x(self) -> int:
|
|
401
|
+
"""Get X coordinate."""
|
|
402
|
+
return self._x
|
|
403
|
+
|
|
404
|
+
@x.setter
|
|
405
|
+
def x(self, value: int):
|
|
406
|
+
"""Set X coordinate."""
|
|
407
|
+
self._x = value
|
|
408
|
+
self._update_coord_display()
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def y(self) -> int:
|
|
412
|
+
"""Get Y coordinate."""
|
|
413
|
+
return self._y
|
|
414
|
+
|
|
415
|
+
@y.setter
|
|
416
|
+
def y(self, value: int):
|
|
417
|
+
"""Set Y coordinate."""
|
|
418
|
+
self._y = value
|
|
419
|
+
self._update_coord_display()
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def objects(self) -> List[Dict[str, Any]]:
|
|
423
|
+
"""Get objects list."""
|
|
424
|
+
return self._objects.copy()
|
|
425
|
+
|
|
426
|
+
@objects.setter
|
|
427
|
+
def objects(self, value: List[Dict[str, Any]]):
|
|
428
|
+
"""Set objects list."""
|
|
429
|
+
self._objects = value
|
|
430
|
+
self._update_objects_list()
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def image(self) -> Optional[QPixmap]:
|
|
434
|
+
"""Get/set map image for object icons."""
|
|
435
|
+
return self._image
|
|
436
|
+
|
|
437
|
+
@image.setter
|
|
438
|
+
def image(self, value: Optional[QPixmap]):
|
|
439
|
+
"""Set map image for object icons."""
|
|
440
|
+
self._image = value
|
|
441
|
+
self._update_objects_list()
|
|
442
|
+
|
|
443
|
+
# Drag and drop functionality
|
|
444
|
+
def dragEnterEvent(self, event):
|
|
445
|
+
"""Accept drag enter events."""
|
|
446
|
+
print(f"[DEBUG] QPSACellInfo drag enter: hasText={event.mimeData().hasText()}")
|
|
447
|
+
if event.mimeData().hasText() or event.mimeData().hasFormat("application/json"):
|
|
448
|
+
event.acceptProposedAction()
|
|
449
|
+
|
|
450
|
+
def dropEvent(self, event):
|
|
451
|
+
"""Handle drop event to add objects."""
|
|
452
|
+
mime_data = event.mimeData()
|
|
453
|
+
json_data = None
|
|
454
|
+
image = None
|
|
455
|
+
|
|
456
|
+
print(f"[DEBUG] QPSACellInfo drop received: hasText={event.mimeData().hasText()}, hasJSON={event.mimeData().hasFormat('application/json')}, hasImage={event.mimeData().hasImage()}")
|
|
457
|
+
|
|
458
|
+
# Try to get JSON data from different sources
|
|
459
|
+
if mime_data.hasFormat("application/json"):
|
|
460
|
+
try:
|
|
461
|
+
json_bytes = mime_data.data("application/json")
|
|
462
|
+
json_str = json_bytes.data().decode('utf-8')
|
|
463
|
+
print(f"[DEBUG] QPSACellInfo JSON data: {json_str}")
|
|
464
|
+
json_data = json.loads(json_str)
|
|
465
|
+
except Exception as e:
|
|
466
|
+
print(f"[DEBUG] QPSACellInfo failed to parse JSON data: {e}")
|
|
467
|
+
elif mime_data.hasText():
|
|
468
|
+
try:
|
|
469
|
+
json_str = mime_data.text()
|
|
470
|
+
print(f"[DEBUG] QPSACellInfo text data: {json_str}")
|
|
471
|
+
json_data = json.loads(json_str)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
print(f"[DEBUG] QPSACellInfo failed to parse text as JSON: {e}")
|
|
474
|
+
|
|
475
|
+
# Try to get image data if available
|
|
476
|
+
if mime_data.hasImage():
|
|
477
|
+
try:
|
|
478
|
+
image = QPixmap(mime_data.imageData())
|
|
479
|
+
print(f"[DEBUG] QPSACellInfo image data received: {image.size()}")
|
|
480
|
+
except Exception as e:
|
|
481
|
+
print(f"[DEBUG] QPSACellInfo failed to get image data: {e}")
|
|
482
|
+
|
|
483
|
+
if json_data:
|
|
484
|
+
# Handle both single object and array of objects
|
|
485
|
+
if isinstance(json_data, list):
|
|
486
|
+
# Array format: [{x, y, type, method, id, name}, ...]
|
|
487
|
+
print(f"[DEBUG] QPSACellInfo received array of {len(json_data)} objects")
|
|
488
|
+
for obj in json_data:
|
|
489
|
+
self._add_object_to_list(obj, image)
|
|
490
|
+
event.acceptProposedAction()
|
|
491
|
+
print(f"[DEBUG] QPSACellInfo array of objects added successfully")
|
|
492
|
+
elif isinstance(json_data, dict):
|
|
493
|
+
# Single object format: {x, y, type, method, id, name}
|
|
494
|
+
self._add_object_to_list(json_data, image)
|
|
495
|
+
event.acceptProposedAction()
|
|
496
|
+
print(f"[DEBUG] QPSACellInfo single object added successfully: {json_data}")
|
|
497
|
+
else:
|
|
498
|
+
print(f"[DEBUG] QPSACellInfo invalid JSON format: expected dict or list, got {type(json_data)}")
|
|
499
|
+
else:
|
|
500
|
+
print(f"[DEBUG] QPSACellInfo no valid JSON data found")
|
|
501
|
+
|
|
502
|
+
def _add_object_to_list(self, obj_data: dict, image: Optional[QPixmap] = None):
|
|
503
|
+
"""Add a single object to the objects list."""
|
|
504
|
+
# Extract required fields
|
|
505
|
+
obj_id = obj_data.get('id', 'unknown')
|
|
506
|
+
obj_type = obj_data.get('type', 'Unknown')
|
|
507
|
+
obj_name = obj_data.get('name', obj_id)
|
|
508
|
+
x = obj_data.get('x', self._x) # Use current cell x if not specified
|
|
509
|
+
y = obj_data.get('y', self._y) # Use current cell y if not specified
|
|
510
|
+
method = obj_data.get('method', '')
|
|
511
|
+
|
|
512
|
+
# Check for duplicates with same id
|
|
513
|
+
for existing_obj in self._objects:
|
|
514
|
+
existing_id = existing_obj.get('id', 'unknown')
|
|
515
|
+
if existing_id == obj_id:
|
|
516
|
+
print(f"[DEBUG] QPSACellInfo rejected duplicate object: {obj_id}")
|
|
517
|
+
return # Reject duplicate
|
|
518
|
+
|
|
519
|
+
# Add the new object
|
|
520
|
+
new_obj = {
|
|
521
|
+
'id': obj_id,
|
|
522
|
+
'type': obj_type,
|
|
523
|
+
'name': obj_name,
|
|
524
|
+
'x': x,
|
|
525
|
+
'y': y,
|
|
526
|
+
'method': method
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
self._objects.append(new_obj)
|
|
530
|
+
print(f"[DEBUG] QPSACellInfo added object: {new_obj}")
|
|
531
|
+
|
|
532
|
+
# Update the display
|
|
533
|
+
self._update_objects_list()
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class TerrainIndicator(QWidget):
|
|
537
|
+
"""Custom widget for displaying terrain color indicator."""
|
|
538
|
+
|
|
539
|
+
def __init__(self, parent=None):
|
|
540
|
+
super().__init__(parent)
|
|
541
|
+
self._color = QColor(144, 238, 144) # Default light green
|
|
542
|
+
|
|
543
|
+
def set_color(self, color: QColor):
|
|
544
|
+
"""Set the terrain color."""
|
|
545
|
+
self._color = color
|
|
546
|
+
self.update()
|
|
547
|
+
|
|
548
|
+
def paintEvent(self, event):
|
|
549
|
+
"""Custom paint event to draw rounded rectangle."""
|
|
550
|
+
painter = QPainter(self)
|
|
551
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
552
|
+
|
|
553
|
+
# Draw rounded rectangle with border
|
|
554
|
+
rect = self.rect()
|
|
555
|
+
|
|
556
|
+
# Border
|
|
557
|
+
painter.setPen(QPen(QColor(139, 115, 85), 1)) # #8B7355
|
|
558
|
+
|
|
559
|
+
# Fill
|
|
560
|
+
painter.setBrush(QBrush(self._color))
|
|
561
|
+
|
|
562
|
+
# Draw rounded rectangle
|
|
563
|
+
painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 4, 4)
|
|
564
|
+
|
|
565
|
+
def sizeHint(self):
|
|
566
|
+
"""Preferred size."""
|
|
567
|
+
return self.minimumSize()
|