1 #!/usr/bin/env python
\r
2 # -*- coding: utf-8 -*-
\r
4 #Pydun.py - mapping tool
\r
5 #copyright (c) 2013 WATAHIKI Hiroyuki
\r
6 #url: http://sourceforge.jp/projects/pydun/
\r
7 #email: hrwatahiki at gmail.com
\r
14 from PySide import QtCore, QtGui
\r
22 projecturl = "http://sourceforge.jp/projects/pydun/"
\r
23 projectversion = "1.0.0"
\r
25 class MainWindow(QtGui.QMainWindow):
\r
27 def __init__(self, parent=None):
\r
31 super(MainWindow, self).__init__(parent)
\r
33 _undomanager = UndoManager()
\r
34 _mapimages = MapImages()
\r
36 _undomanager.changed.connect(self.updateundostate)
\r
38 if len(sys.argv) <= 2:
\r
41 self.open(sys.argv[2])
\r
44 self.mainframe = MainFrame(self)
\r
45 self.setCentralWidget(self.mainframe)
\r
47 self.statusbar = QtGui.QStatusBar(self)
\r
48 self.statusbar.showMessage(u"")
\r
49 self.setStatusBar(self.statusbar)
\r
53 filemenu = self.menuBar().addMenu(u"ファイル(&F)")
\r
55 newact = QtGui.QAction(u"新規(&N)", self)
\r
56 newact.triggered.connect(self.new_triggered)
\r
57 newact.setShortcut(QtGui.QKeySequence.New)
\r
58 filemenu.addAction(newact)
\r
60 openact = QtGui.QAction(u"開く(&O)...", self)
\r
61 openact.triggered.connect(self.open_triggered)
\r
62 openact.setShortcut(QtGui.QKeySequence.Open)
\r
63 filemenu.addAction(openact)
\r
65 saveact = QtGui.QAction(u"保存(&S)", self)
\r
66 saveact.triggered.connect(self.save_triggered)
\r
67 saveact.setShortcut(QtGui.QKeySequence.Save)
\r
68 filemenu.addAction(saveact)
\r
70 saveasact = QtGui.QAction(u"名前をつけて保存(&A)...", self)
\r
71 saveasact.triggered.connect(self.saveas_triggered)
\r
72 saveasact.setShortcut(QtGui.QKeySequence.SaveAs)
\r
73 filemenu.addAction(saveasact)
\r
75 exitact = QtGui.QAction(u"終了(&E)", self)
\r
76 exitact.triggered.connect(self.exit_triggered)
\r
77 exitact.setShortcut(QtGui.QKeySequence.Quit)
\r
78 filemenu.addAction(exitact)
\r
81 editmenu = self.menuBar().addMenu(u"編集(&E)")
\r
82 self.undoact = QtGui.QAction(u"元に戻す(&U)", self)
\r
83 self.undoact.triggered.connect(self.undo_triggered)
\r
84 self.undoact.setShortcut(QtGui.QKeySequence.Undo)
\r
85 editmenu.addAction(self.undoact)
\r
86 self.redoact = QtGui.QAction(u"やり直し(&R)", self)
\r
87 self.redoact.triggered.connect(self.redo_triggered)
\r
88 self.redoact.setShortcut(QtGui.QKeySequence.Redo)
\r
89 editmenu.addAction(self.redoact)
\r
90 editmenu.addSeparator()
\r
91 setmapsizeact = QtGui.QAction(u"マップのサイズ(&S)", self)
\r
92 setmapsizeact.triggered.connect(self.setmapsize_triggered)
\r
93 editmenu.addAction(setmapsizeact)
\r
94 setorigineact = QtGui.QAction(u"座標設定(&O)", self)
\r
95 setorigineact.triggered.connect(self.setorigine_triggered)
\r
96 editmenu.addAction(setorigineact)
\r
99 helpmenu = self.menuBar().addMenu(u"ヘルプ(&H)")
\r
100 tutorialact = QtGui.QAction(u"チュートリアル(&T)", self)
\r
101 tutorialact.triggered.connect(self.tutorial_triggered)
\r
102 tutorialact.setShortcut(QtGui.QKeySequence.HelpContents)
\r
103 helpmenu.addAction(tutorialact)
\r
104 projectact = QtGui.QAction(u"プロジェクトのWebサイト(&W)", self)
\r
105 projectact.triggered.connect(self.project_triggered)
\r
106 helpmenu.addAction(projectact)
\r
107 aboutact = QtGui.QAction(u"Pydunについて(&A)...", self)
\r
108 aboutact.triggered.connect(self.about_triggered)
\r
109 helpmenu.addAction(aboutact)
\r
111 @QtCore.Slot(bool, bool)
\r
112 def updateundostate(self, canundo, canredo):
\r
114 self.undoact.setEnabled(True)
\r
116 self.undoact.setDisabled(True)
\r
118 self.redoact.setEnabled(True)
\r
120 self.redoact.setDisabled(True)
\r
122 def setTitle(self, fileName):
\r
123 if fileName == None:
\r
126 s = os.path.basename(fileName)
\r
128 self.setWindowTitle(s)
\r
131 def new_triggered(self):
\r
132 if QtGui.QMessageBox.Ok == QtGui.QMessageBox.question(
\r
133 self, u"確認", u"新しいマップを作成しますか?",
\r
134 (QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)):
\r
139 _mapengine = MapEngine(20, 20, 1, -1, 0, +19)
\r
140 _undomanager.clear()
\r
141 _undomanager.save(_mapengine.savestring())
\r
142 self.setTitle(None)
\r
144 self.mainframe.mapframe.repaint()
\r
149 def open_triggered(self):
\r
150 fileName = QtGui.QFileDialog.getOpenFileName(
\r
151 filter=u"*.pydun;;*.*", selectedFilter=u"*.pydun")
\r
152 if fileName[0] != u"":
\r
153 self.open(fileName[0])
\r
155 def open(self, fileName):
\r
156 _mapengine.load(fileName)
\r
157 _undomanager.clear()
\r
158 _undomanager.save(_mapengine.savestring())
\r
159 self.setTitle(_mapengine.fileName)
\r
160 self.mainframe.mapframe.repaint()
\r
163 def save_triggered(self):
\r
164 if _mapengine.fileName:
\r
165 self.save(_mapengine.fileName)
\r
167 self.saveas_triggered()
\r
170 def saveas_triggered(self):
\r
171 fileName = QtGui.QFileDialog.getSaveFileName(
\r
172 filter=u"*.pydun;;*.*", selectedFilter=u"*.pydun")
\r
173 if fileName[0] != u"":
\r
174 self.save(fileName[0])
\r
176 def save(self, fileName):
\r
177 _mapengine.save(fileName)
\r
178 self.setTitle(_mapengine.fileName)
\r
181 def exit_triggered(self):
\r
184 def closeEvent(self, event):
\r
191 if QtGui.QMessageBox.Ok == QtGui.QMessageBox.question(
\r
192 self, u"確認", u"終了しますか?",
\r
193 (QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)):
\r
199 def undo_triggered(self):
\r
201 _mapengine.loadfromstring(_undomanager.undo())
\r
202 self.mainframe.mapframe.repaint()
\r
205 def redo_triggered(self):
\r
207 _mapengine.loadfromstring(_undomanager.redo())
\r
208 self.mainframe.mapframe.repaint()
\r
211 def setorigine_triggered(self):
\r
213 if self.mainframe.mapframe.setoriginemode:
\r
214 QtGui.QMessageBox.information(
\r
215 self, title, u"座標設定を中止します。", QtGui.QMessageBox.Ok)
\r
216 self.mainframe.mapframe.setoriginemode = False
\r
218 if QtGui.QMessageBox.Ok == QtGui.QMessageBox.information(
\r
219 self, title, u"基準にする地点をクリックしてください。",
\r
220 (QtGui.QMessageBox.Ok| QtGui.QMessageBox.Cancel)):
\r
221 self.mainframe.mapframe.setoriginemode = True
\r
224 def setmapsize_triggered(self):
\r
225 dlg = SetSizeDialog(self)
\r
226 dlg.setoriginalsize(_mapengine.width, _mapengine.height)
\r
228 if dlg.result() == QtGui.QDialog.Accepted:
\r
229 top, bottom, left, right = dlg.getsize()
\r
230 _mapengine.changesize(top, bottom, left, right)
\r
231 _undomanager.save(_mapengine.savestring())
\r
232 self.mainframe.mapframe.repaint()
\r
235 def tutorial_triggered(self):
\r
236 url = os.path.dirname(os.path.abspath(__file__)) + "/help/index.html"
\r
237 webbrowser.open_new_tab(url)
\r
240 def project_triggered(self):
\r
241 webbrowser.open_new_tab(projecturl)
\r
244 def about_triggered(self):
\r
245 QtGui.QMessageBox.about(self, "Pydun",
\r
246 u"<h1>Pydun.py "+ projectversion + "</h1>"
\r
247 u"<p>Copyright (c) 2013 WATAHIKI Hiroyuki</p>"
\r
248 u"<p>url: <a href='" + projecturl + "'>" + projecturl + "</a></p>"
\r
249 u"<p>e-mail: hrwatahiki at gmail.com</p>"
\r
250 u"<p>このソフトウェアはMITライセンスです。</p>"
\r
251 u"<p>このソフトウェアは以下のソフトウェアを使用しています。: "
\r
252 u"Python, PySide, PyYAML "
\r
253 u"これらの作成者に深く感謝いたします。</p>"
\r
254 u"<p>詳細はLICENCE.txtを参照してください。</p>")
\r
257 class MainFrame(QtGui.QFrame):
\r
258 create_wall_menu_triggered_signal = QtCore.Signal(int, int, str, int)
\r
260 def __init__(self, parent=None):
\r
261 super(MainFrame, self).__init__(parent)
\r
263 self.mapframe = MapFrame(self)
\r
264 scrollarea = QtGui.QScrollArea(self)
\r
265 scrollarea.setWidget(self.mapframe)
\r
267 self.detail = QtGui.QLabel(self)
\r
268 self.detail.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
\r
269 self.detail.setText(u"")
\r
270 self.detail.setMaximumHeight(100)
\r
271 self.detail.setMinimumHeight(100)
\r
273 self.boxdrawbutton = QtGui.QRadioButton(self)
\r
274 self.boxdrawbutton.setText(u"ボックス形式で壁を描画(&B)")
\r
275 self.boxdrawbutton.setChecked(True)
\r
276 self.boxdrawbutton.setSizePolicy(
\r
277 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
279 self.growdrawbutton = QtGui.QRadioButton(self)
\r
280 self.growdrawbutton.setText(u"足跡形式で壁を描画(&G)")
\r
281 self.growdrawbutton.setChecked(False)
\r
282 self.growdrawbutton.setSizePolicy(
\r
283 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
285 self.backcolorbutton = QtGui.QRadioButton(self)
\r
286 self.backcolorbutton.setText(u"背景色(&C)")
\r
287 self.backcolorbutton.setChecked(False)
\r
288 self.backcolorbutton.setSizePolicy(
\r
289 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
291 self.setbackcolorbutton = QtGui.QPushButton(self)
\r
292 self.setbackcolorbutton.setText(u"背景色を設定(&S)...")
\r
293 self.setbackcolorbutton.setSizePolicy(
\r
294 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
296 self.backcolorbox = ColorBox(self)
\r
297 self.backcolorbox.setMinimumSize(30, 30)
\r
298 self.backcolorbox.setSizePolicy(
\r
299 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
302 layout = QtGui.QGridLayout(self)
\r
303 layout.addWidget(scrollarea, 0, 0, 1, 3)
\r
304 layout.addWidget(self.detail, 1, 0, 4, 1)
\r
305 layout.addWidget(self.boxdrawbutton, 1, 1, 1, 2)
\r
306 layout.addWidget(self.growdrawbutton, 2, 1, 1, 2)
\r
307 layout.addWidget(self.backcolorbutton, 3, 1, 1, 2)
\r
308 layout.addWidget(self.setbackcolorbutton, 4, 1, 1, 1)
\r
309 layout.addWidget(self.backcolorbox, 4, 2, 1, 1)
\r
311 self.setLayout(layout)
\r
313 self.h_wall_menu = self.create_wall_menu("h")
\r
314 self.v_wall_menu = self.create_wall_menu("v")
\r
316 self.mapframe.mouse_moved.connect(self.mouse_moved)
\r
317 self.mapframe.mouse_released.connect(self.mouse_released)
\r
318 self.mapframe.mouse_drag_released.connect(self.mouse_drag_released)
\r
319 self.create_wall_menu_triggered_signal.connect(
\r
320 self.create_wall_menu_triggered)
\r
321 self.setbackcolorbutton.clicked.connect(
\r
322 self.setbackcolorbutton_clicked)
\r
324 def create_wall_menu(self, direction):
\r
325 menu = QtGui.QMenu(self)
\r
326 for idx, img in enumerate(_mapimages.wall_icons):
\r
327 act = QtGui.QAction(self)
\r
328 act.setIcon(img[direction])
\r
332 self.create_wall_menu_triggered_signal.emit(menu.x, menu.y, direction, idx)
\r
335 act.triggered.connect(triggerd(idx))
\r
336 menu.addAction(act)
\r
339 @QtCore.Slot(int, int, int)
\r
340 def mouse_moved(self, x=0, y=0, b=QtCore.Qt.MouseButton.NoButton):
\r
341 cood = u"({x}, {y})\n".format(x=_mapengine.viewx(x), y=_mapengine.viewy(y))
\r
342 self.detail.setText(cood + _mapengine.getdetail(x, y))
\r
343 self.mapframe.repaint()
\r
345 @QtCore.Slot(int, int, int, int, int)
\r
346 def mouse_drag_released(self, x1, y1, x2, y2, eraseonly):
\r
347 if self.boxdrawbutton.isChecked():
\r
348 _mapengine.growwall(x1, y1, x2, y2, eraseonly, True)
\r
349 elif self.growdrawbutton.isChecked():
\r
350 _mapengine.growwall(x1, y1, x2, y2, eraseonly, False)
\r
351 elif self.backcolorbutton.isChecked():
\r
355 backcolor = getcolorstring(self.backcolorbox.color)
\r
356 _mapengine.fillbackcolor(x1, y1, x2, y2, backcolor)
\r
357 _undomanager.save(_mapengine.savestring())
\r
358 self.mapframe.repaint()
\r
360 @QtCore.Slot(int, int, str)
\r
361 def mouse_released(self, x1, y1, direction):
\r
363 if self.mapframe.setoriginemode:
\r
364 dlg = SetOrigineDialog(self)
\r
365 dlg.setcurrent(_mapengine.viewx(x1), _mapengine.viewy(y1))
\r
366 dlg.exec_() #showでは処理がとまらない。
\r
367 if dlg.result() == QtGui.QDialog.Accepted:
\r
368 _mapengine.setoffset(
\r
369 dlg.originex - _mapengine.viewx(x1) + _mapengine.offsetx,
\r
370 dlg.originey - _mapengine.viewy(y1) + _mapengine.offsety
\r
372 _undomanager.save(_mapengine.savestring())
\r
373 self.mapframe.setoriginemode = False
\r
376 if direction == "c":
\r
377 dlg = DetailDialog(self)
\r
378 dlg.setvalue(_mapengine.viewx(x1), _mapengine.viewy(y1),
\r
379 _mapengine.getmark(x1, y1), _mapengine.getdetail(x1, y1),
\r
380 getcolorfromstring(_mapengine.getforecolor(x1, y1)))
\r
381 dlg.exec_() #showでは処理がとまらない。
\r
382 if dlg.result() == QtGui.QDialog.Accepted:
\r
383 forecolor = getcolorstring(dlg.forecolorbox.color)
\r
384 _mapengine.setmark(x1, y1, dlg.marktext.text())
\r
385 _mapengine.setdetail(x1, y1, dlg.detailtext.toPlainText())
\r
386 _mapengine.setforecolor(x1, y1, forecolor)
\r
387 _undomanager.save(_mapengine.savestring())
\r
388 self.mapframe.repaint()
\r
392 if direction == "h":
\r
393 menu = self.h_wall_menu
\r
394 elif direction == "v":
\r
395 menu = self.v_wall_menu
\r
398 menu.popup(QtGui.QCursor.pos())
\r
400 @QtCore.Slot(int, int, str, int)
\r
401 def create_wall_menu_triggered(self, x1, y1, direction, wall):
\r
402 _mapengine.setdata(x1, y1, direction, wall)
\r
403 _undomanager.save(_mapengine.savestring())
\r
404 self.mapframe.repaint()
\r
407 def setbackcolorbutton_clicked(self):
\r
408 dlg = QtGui.QColorDialog(self)
\r
410 if dlg.result() == QtGui.QDialog.Accepted:
\r
411 self.backcolorbox.color = dlg.currentColor()
\r
412 self.backcolorbutton.setChecked(True)
\r
415 class MapFrame(QtGui.QFrame):
\r
416 mouse_moved = QtCore.Signal(int, int, int)
\r
417 mouse_released = QtCore.Signal(int, int, str)
\r
418 mouse_drag_released = QtCore.Signal(int, int, int, int, int)
\r
422 def __init__(self, parent=None):
\r
423 super(MapFrame, self).__init__(parent)
\r
424 self._pressedbutton = QtCore.Qt.MouseButton.NoButton
\r
433 self._dragging = False
\r
434 self.setoriginemode = False
\r
435 self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
437 _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,
\r
438 _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2
\r
441 def paintEvent(self, event):
\r
442 painter = QtGui.QPainter(self)
\r
443 painter.fillRect(0, 0, self.width(), self.height(), QtGui.QColor(255, 255, 255))
\r
444 w = _mapimages.width
\r
445 v = _mapimages.height
\r
446 ho = _mapimages.heightoffset
\r
447 wo = _mapimages.widthoffset
\r
451 _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,
\r
452 _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2
\r
456 for x in range(_mapengine.width):
\r
458 for y in range(_mapengine.height):
\r
460 backcolor = _mapengine.getbackcolor(x, y)
\r
462 painter.fillRect(wo + xx, ho + yy, w, v,
\r
463 getcolorfromstring(backcolor))
\r
466 for x in range(_mapengine.width + 1):
\r
468 for y in range(_mapengine.height + 1):
\r
470 if x != _mapengine.width:
\r
471 painter.drawImage(wo + xx, yy,
\r
472 _mapimages.wall(_mapengine.getdata(x, y, "h"), "h"))
\r
473 if y != _mapengine.height:
\r
474 painter.drawImage(xx, ho + yy,
\r
475 _mapimages.wall(_mapengine.getdata(x, y, "v"), "v"))
\r
476 mark = _mapengine.getmark(x, y)
\r
478 painter.setPen(getcolorfromstring(_mapengine.getforecolor(x, y)))
\r
479 painter.drawText(wo + xx + 1, ho + yy + 1, w - 2, v - 2,
\r
480 QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter,
\r
483 #座標設定中はdrawing box を表示しない。
\r
484 if self.setoriginemode:
\r
488 if self._pressedbutton != QtCore.Qt.MouseButton.NoButton:
\r
489 if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:
\r
490 if self._x1 == self._x2 and self._y1 == self._y2:
\r
491 painter.setPen(QtGui.QColor(255, 0, 0))
\r
492 elif self._x1 == self._x2 or self._y1 == self._y2:
\r
493 painter.setPen(QtGui.QColor(0, 255, 0))
\r
495 painter.setPen(QtGui.QColor(255, 0, 0))
\r
496 elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:
\r
497 painter.setPen(QtGui.QColor(0, 0, 255))
\r
498 painter.drawRect(self._px1, self._py1,
\r
499 self._px2 - self._px1, self._py2 - self._py1)
\r
501 def eventFilter(self, obj, event):
\r
503 return ((event.pos().x() - _mapimages.widthoffset) // _mapimages.width)
\r
506 return ((event.pos().y() - _mapimages.heightoffset) // _mapimages.height)
\r
511 if et == QtCore.QEvent.MouseButtonPress:
\r
514 self._pos1 = event.pos()
\r
515 self._px1 = event.pos().x()
\r
516 self._py1 = event.pos().y()
\r
519 self._px2 = event.pos().x()
\r
520 self._py2 = event.pos().y()
\r
521 self._pressedbutton = event.buttons()
\r
522 self._dragging = False
\r
525 elif et == QtCore.QEvent.MouseMove:
\r
528 self._px2 = event.pos().x()
\r
529 self._py2 = event.pos().y()
\r
530 if (self._pressedbutton != QtCore.Qt.MouseButton.NoButton and
\r
531 (event.pos() - self._pos1).manhattanLength() >=
\r
532 QtGui.QApplication.startDragDistance()):
\r
533 self._dragging = True
\r
534 self.mouse_moved.emit(self._x2, self._y2, event.buttons())
\r
537 elif et == QtCore.QEvent.MouseButtonRelease:
\r
539 release_emit = False
\r
542 if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:
\r
544 elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:
\r
547 release_emit = True
\r
548 if self.setoriginemode:
\r
549 release_emit = True
\r
551 self._pressedbutton = QtCore.Qt.MouseButton.NoButton
\r
552 self._dragging = False
\r
554 self.mouse_drag_released.emit(
\r
555 self._x1, self._y1, self._x2, self._y2, eraseonly)
\r
557 rpx = self._px2 - self._x2 * _mapimages.width - _mapimages.widthoffset
\r
558 rpy = self._py2 - self._y2 * _mapimages.height - _mapimages.heightoffset
\r
559 if rpx <= _mapimages.widthoffset:
\r
563 elif rpx >= _mapimages.width - _mapimages.widthoffset:
\r
567 elif rpy <= _mapimages.heightoffset:
\r
571 elif rpy >= _mapimages.height - _mapimages.heightoffset:
\r
579 self.mouse_released.emit(rx, ry, d)
\r
585 # pass the event on to the parent class
\r
589 class ColorBox(QtGui.QFrame):
\r
590 def __init__(self, parent=None):
\r
591 super(ColorBox, self).__init__(parent)
\r
592 self.color = QtGui.QColor(255, 255, 255)
\r
593 self.bordercolor = QtGui.QColor(0, 0, 0)
\r
595 def paintEvent(self, event):
\r
596 painter = QtGui.QPainter(self)
\r
597 painter.fillRect(0, 0, self.width(), self.height(), self.color)
\r
598 painter.setPen(self.bordercolor)
\r
599 painter.drawRect(0, 0, self.width() - 1, self.height() - 1)
\r
602 class DetailDialog(QtGui.QDialog):
\r
603 def __init__(self, parent=None):
\r
604 super(DetailDialog, self).__init__(parent)
\r
606 marklabel = QtGui.QLabel(self)
\r
607 marklabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
\r
608 marklabel.setText(u"マーク(&M)")
\r
609 marklabel.setSizePolicy(
\r
610 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
612 self.marktext = QtGui.QLineEdit(self)
\r
613 self.marktext.setMaxLength(1)
\r
614 self.marktext.setText(u"")
\r
615 self.marktext.setMinimumWidth(20)
\r
616 self.marktext.setSizePolicy(
\r
617 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
618 marklabel.setBuddy(self.marktext)
\r
620 self.forecolorbutton = QtGui.QPushButton(self)
\r
621 self.forecolorbutton.setText(u"文字色(&C)...")
\r
622 self.forecolorbutton.clicked.connect(self.forecolorbutton_clicked)
\r
624 self.forecolorbox = ColorBox(self)
\r
625 self.forecolorbox.setMinimumSize(30, 30)
\r
626 self.forecolorbox.setSizePolicy(
\r
627 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
\r
629 detaillabel = QtGui.QLabel(self)
\r
630 detaillabel.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight)
\r
631 detaillabel.setText(u"詳細(&D)")
\r
633 self.detailtext = QtGui.QTextEdit(self)
\r
634 self.detailtext.setText(u"")
\r
635 detaillabel.setBuddy(self.detailtext)
\r
637 self.buttonBox = QtGui.QDialogButtonBox(
\r
638 QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
\r
639 self.buttonBox.accepted.connect(self.accept)
\r
640 self.buttonBox.rejected.connect(self.reject)
\r
642 layout = QtGui.QGridLayout()
\r
643 layout.addWidget(marklabel, 0, 0, 1, 1)
\r
644 layout.addWidget(self.marktext, 0, 1, 1, 1)
\r
645 layout.addWidget(self.forecolorbutton, 0, 2, 1, 1)
\r
646 layout.addWidget(self.forecolorbox, 0, 3, 1, 1)
\r
647 layout.addWidget(detaillabel, 1, 0, 1, 1)
\r
648 layout.addWidget(self.detailtext, 1, 1, 1, 3)
\r
649 layout.addWidget(self.buttonBox, 2, 0, 1, 4)
\r
650 self.setLayout(layout)
\r
651 self.setModal(True)
\r
653 def setvalue(self, x, y, mark, detail, color):
\r
654 self.setWindowTitle("({x},{y})".format(x=x, y=y))
\r
655 self.marktext.setText(mark)
\r
656 self.detailtext.setText(detail)
\r
657 self.forecolorbox.color = color
\r
659 def forecolorbutton_clicked(self):
\r
660 dlg = QtGui.QColorDialog(self)
\r
662 if dlg.result() == QtGui.QDialog.Accepted:
\r
663 self.forecolorbox.color = dlg.currentColor()
\r
665 class SetOrigineDialog(QtGui.QDialog):
\r
666 def __init__(self, parent=None):
\r
667 super(SetOrigineDialog, self).__init__(parent)
\r
668 self.setWindowTitle(u"座標設定")
\r
670 promptlabel = QtGui.QLabel(self)
\r
671 promptlabel.setAlignment(
\r
672 QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)
\r
673 promptlabel.setText(u"この地点の座標を入力してください。")
\r
675 self.currentlabel = QtGui.QLabel(self)
\r
676 self.currentlabel.setAlignment(
\r
677 QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
\r
678 self.currentlabel.setText(u"")
\r
680 xlabel = QtGui.QLabel(self)
\r
681 xlabel.setAlignment(
\r
682 QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
\r
683 xlabel.setText(u"&X")
\r
685 self.xbox = QtGui.QSpinBox(self)
\r
686 self.xbox.setRange(-999, +999)
\r
687 self.xbox.setSingleStep(1)
\r
688 self.xbox.setValue(0)
\r
689 xlabel.setBuddy(self.xbox)
\r
691 ylabel = QtGui.QLabel(self)
\r
692 ylabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
\r
693 ylabel.setText(u"&Y")
\r
695 self.ybox = QtGui.QSpinBox(self)
\r
696 self.ybox.setRange(-999, +999)
\r
697 self.ybox.setSingleStep(1)
\r
698 self.ybox.setValue(0)
\r
699 ylabel.setBuddy(self.ybox)
\r
701 self.buttonBox = QtGui.QDialogButtonBox(
\r
702 QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
\r
703 self.buttonBox.accepted.connect(self.accept)
\r
704 self.buttonBox.rejected.connect(self.reject)
\r
706 layout = QtGui.QGridLayout()
\r
707 layout.addWidget(promptlabel, 0, 0, 1, 4)
\r
708 layout.addWidget(self.currentlabel, 1, 0, 1, 4)
\r
709 layout.addWidget(xlabel, 2, 0, 1, 1)
\r
710 layout.addWidget(self.xbox, 2, 1, 1, 1)
\r
711 layout.addWidget(ylabel, 2, 2, 1, 1)
\r
712 layout.addWidget(self.ybox, 2, 3, 1, 1)
\r
713 layout.addWidget(self.buttonBox, 3, 0, 1, 4)
\r
714 self.setLayout(layout)
\r
715 self.setModal(True)
\r
717 def setcurrent(self, x, y):
\r
718 self.xbox.setValue(x)
\r
719 self.ybox.setValue(y)
\r
720 self.currentlabel.setText(u"現在の座標 ({x}, {y})".format(x=x, y=y))
\r
723 def originex(self):
\r
724 return self.xbox.value()
\r
727 def originey(self):
\r
728 return self.ybox.value()
\r
731 class SetSizeDialog(QtGui.QDialog):
\r
732 def __init__(self, parent=None):
\r
733 super(SetSizeDialog, self).__init__(parent)
\r
734 self.setWindowTitle(u"マップのサイズ")
\r
736 self.topbutton = QtGui.QRadioButton(self)
\r
737 self.topbutton.setText(u"上(&T)")
\r
738 self.topbutton.clicked.connect(self.updatewidgets)
\r
740 self.topsize = QtGui.QSpinBox(self)
\r
741 self.topsize.setSingleStep(1)
\r
742 self.topsize.setValue(0)
\r
743 self.topsize.valueChanged.connect(self.updatewidgets)
\r
745 self.bottombutton = QtGui.QRadioButton(self)
\r
746 self.bottombutton.setText(u"下(&B)")
\r
747 self.bottombutton.clicked.connect(self.updatewidgets)
\r
749 self.bottomsize = QtGui.QSpinBox(self)
\r
750 self.bottomsize.setSingleStep(1)
\r
751 self.bottomsize.setValue(0)
\r
752 self.bottomsize.valueChanged.connect(self.updatewidgets)
\r
754 self.leftbutton = QtGui.QRadioButton(self)
\r
755 self.leftbutton.setText(u"左(&L)")
\r
756 self.leftbutton.clicked.connect(self.updatewidgets)
\r
758 self.leftsize = QtGui.QSpinBox(self)
\r
759 self.leftsize.setSingleStep(1)
\r
760 self.leftsize.setValue(0)
\r
761 self.leftsize.valueChanged.connect(self.updatewidgets)
\r
763 self.rightbutton = QtGui.QRadioButton(self)
\r
764 self.rightbutton.setText(u"右(&R)")
\r
765 self.rightbutton.clicked.connect(self.updatewidgets)
\r
767 self.rightsize = QtGui.QSpinBox(self)
\r
768 self.rightsize.setSingleStep(1)
\r
769 self.rightsize.setValue(0)
\r
770 self.rightsize.valueChanged.connect(self.updatewidgets)
\r
772 self.sizelabel = QtGui.QLabel(self)
\r
773 self.sizelabel .setAlignment(
\r
774 QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)
\r
775 self.sizelabel.setText(u"この地点の座標を入力してください。")
\r
777 self.buttonbox = QtGui.QDialogButtonBox(
\r
778 QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
\r
779 self.buttonbox.accepted.connect(self.accept)
\r
780 self.buttonbox.rejected.connect(self.reject)
\r
782 verticalgroup = QtGui.QButtonGroup(self)
\r
783 verticalgroup.addButton(self.topbutton)
\r
784 verticalgroup.addButton(self.bottombutton)
\r
786 holizontalgroup = QtGui.QButtonGroup(self)
\r
787 holizontalgroup.addButton(self.leftbutton)
\r
788 holizontalgroup.addButton(self.rightbutton)
\r
790 self.topbutton.setChecked(True)
\r
791 self.bottombutton.setChecked(False)
\r
792 self.leftbutton.setChecked(True)
\r
793 self.rightbutton.setChecked(False)
\r
795 layout = QtGui.QGridLayout(self)
\r
796 layout.addWidget(self.topbutton, 0, 2, 1, 1)
\r
797 layout.addWidget(self.topsize, 0, 3, 1, 1)
\r
798 layout.addWidget(self.leftbutton, 1, 0, 1, 1)
\r
799 layout.addWidget(self.leftsize, 1, 1, 1, 1)
\r
800 layout.addWidget(self.sizelabel, 1, 2, 1, 2)
\r
801 layout.addWidget(self.rightbutton, 1, 4, 1, 1)
\r
802 layout.addWidget(self.rightsize, 1, 5, 1, 1)
\r
803 layout.addWidget(self.bottombutton, 2, 2, 1, 1)
\r
804 layout.addWidget(self.bottomsize, 2, 3, 1, 1)
\r
805 layout.addWidget(self.buttonbox, 3, 0, 1, 6)
\r
806 self.setLayout(layout)
\r
807 self.setModal(True)
\r
809 def setoriginalsize(self, width, height):
\r
810 self._width = width
\r
811 self._height = height
\r
812 self.topsize.setRange(-height+1, +100)
\r
813 self.bottomsize.setRange(-height+1, +100)
\r
814 self.leftsize.setRange(-width+1, +100)
\r
815 self.rightsize.setRange(-width+1, +100)
\r
816 self.updatewidgets()
\r
818 def updatewidgets(self):
\r
822 if self.topbutton.isChecked():
\r
823 dh = self.topsize.value()
\r
824 self.topsize.setEnabled(True)
\r
825 self.bottomsize.setDisabled(True)
\r
826 elif self.bottombutton.isChecked():
\r
827 dh = self.bottomsize.value()
\r
828 self.topsize.setDisabled(True)
\r
829 self.bottomsize.setEnabled(True)
\r
830 if self.leftbutton.isChecked():
\r
831 dw = self.leftsize.value()
\r
832 self.leftsize.setEnabled(True)
\r
833 self.rightsize.setDisabled(True)
\r
834 elif self.rightbutton.isChecked():
\r
835 dw = self.rightsize.value()
\r
836 self.leftsize.setDisabled(True)
\r
837 self.rightsize.setEnabled(True)
\r
839 self.sizelabel.setText(
\r
840 u"変更前のサイズ: {w1} x {h1}\n変更後のサイズ: {w2} x {h2}".format(
\r
841 w1=self._width, h1=self._height,
\r
842 w2=self._width+dw, h2=self._height+dh))
\r
849 if self.topbutton.isChecked():
\r
850 top = self.topsize.value()
\r
851 elif self.bottombutton.isChecked():
\r
852 bottom = self.bottomsize.value()
\r
853 if self.leftbutton.isChecked():
\r
854 left = self.leftsize.value()
\r
855 elif self.rightbutton.isChecked():
\r
856 right = self.rightsize.value()
\r
857 return (top, bottom, left, right)
\r
859 class MapImages(object):
\r
860 def __init__(self):
\r
861 self.wall_images = list()
\r
862 self.wall_icons = list()
\r
863 for index in range(10):
\r
864 self.wall_images.append(dict())
\r
865 self.wall_icons.append(dict())
\r
866 for direction in ["v", "h"]:
\r
867 fileName = os.path.join(
\r
868 os.path.dirname(os.path.abspath(__file__)),
\r
870 u"wall_{direction}_{index:02}.png".format(
\r
871 direction=direction, index=index))
\r
872 self.wall_images[index][direction] = QtGui.QImage()
\r
873 self.wall_images[index][direction].load(fileName)
\r
874 self.wall_icons[index][direction] = QtGui.QIcon(fileName)
\r
879 return self.wall_images[0]["h"].width()
\r
883 return self.wall_images[0]["v"].height()
\r
886 def widthoffset(self):
\r
887 return self.wall_images[0]["v"].width()//2
\r
890 def heightoffset(self):
\r
891 return self.wall_images[0]["h"].height()//2
\r
893 def wall(self, index, direction):
\r
894 return self.wall_images[index][direction]
\r
897 class MapEngine(object):
\r
898 hwall = " -#WMwmHVA"
\r
899 vwall = " |#PCpc=DG"
\r
901 def __init__(self, width, height, signx, signy, offsetx, offsety):
\r
902 self._width = width
\r
903 self._height = height
\r
904 self._signx = signx
\r
905 self._signy = signy
\r
906 self._offsetx = offsetx
\r
907 self._offsety = offsety
\r
908 self.fileName = None
\r
912 self._note = dict()
\r
914 def initdata(self):
\r
915 width = self.width + 1
\r
916 height = self.height + 1
\r
917 self._data = self.initialdata(width, height)
\r
919 def initialdata(self, width, height):
\r
921 for x in range(width):
\r
923 for y in range(height):
\r
924 dt[x].append(dict())
\r
925 for d in ["h", "v"]:
\r
929 def inityaml(self):
\r
930 #yaml !python/Unicode出力抑止おまじない
\r
931 def represent_unicode(dumper, data):
\r
932 return dumper.represent_scalar("tag:yaml.org,2002:str", data)
\r
933 def construct_unicode(loader, node):
\r
934 return unicode(loader.construct_scalar(node))
\r
935 yaml.add_representer(unicode, represent_unicode)
\r
936 yaml.add_constructor("tag:yaml.org,2002:str", construct_unicode)
\r
938 def getdata(self, x, y, direction):
\r
939 return self._data[x][y][direction]
\r
941 def setdata(self, x, y, direction, value):
\r
942 self._data[x][y][direction] = value
\r
950 return self._height
\r
962 return self._offsetx
\r
966 return self._offsety
\r
968 def setoffset(self, x, y):
\r
972 def getmark(self, x, y):
\r
973 return self.unescape(self.getnote(x, y)["mark"])
\r
975 def getdetail(self, x, y):
\r
976 return self.unescape(self.getnote(x, y)["detail"])
\r
978 def getforecolor(self, x, y):
\r
979 return self.getnote(x, y)["forecolor"]
\r
981 def getbackcolor(self, x, y):
\r
982 return self.getnote(x, y)["backcolor"]
\r
984 def getnote(self, x, y):
\r
985 return self._note.get(
\r
986 self.coodtokey(x, y), {"mark":u"", "detail":u"", "forecolor":u"#000000", "backcolor":u""})
\r
988 def coodtokey(self, x, y):
\r
989 return u"{x:+05d}_{y:+05d}".format(x=x, y=y)
\r
991 def keytocood(self, key):
\r
992 return map(int, key.split("_"))
\r
994 def setmark(self, x, y, mark):
\r
995 note = self.getnote(x, y)
\r
996 note["mark"] = self.escape(mark)
\r
997 self.setnote(x, y, note)
\r
999 def setdetail(self, x, y, detail):
\r
1000 note = self.getnote(x, y)
\r
1001 note["detail"] = self.escape(detail)
\r
1002 self.setnote(x, y, note)
\r
1004 def setforecolor(self, x, y, color):
\r
1005 note = self.getnote(x, y)
\r
1006 note["forecolor"] = color
\r
1007 self.setnote(x, y, note)
\r
1009 def setbackcolor(self, x, y, color):
\r
1010 note = self.getnote(x, y)
\r
1011 note["backcolor"] = color
\r
1012 self.setnote(x, y, note)
\r
1014 def setnote(self, x, y, note):
\r
1015 self._note[self.coodtokey(x, y)] = note
\r
1017 def escape(self, s):
\r
1018 return s.replace("\\", "\\\\").replace("\n", r"\n")
\r
1020 def unescape(self, s):
\r
1021 return s.replace(r"\n", "\n").replace("\\\\", "\\")
\r
1023 def viewx(self, x):
\r
1024 return x * self.signx + self.offsetx
\r
1026 def viewy(self, y):
\r
1027 return y * self.signy + self.offsety
\r
1029 def worldx(self, x):
\r
1030 return (x - self.offsetx) / self.signx
\r
1032 def worldy(self, y):
\r
1033 return (y - self.offsety) / self.signy
\r
1035 def changesize(self, top, bottom, left, right):
\r
1036 oldoffsetx = max(-left, 0)
\r
1037 newoffsetx = max(left, 0)
\r
1038 newwidth = self.width + left + right
\r
1039 oldoffsety = max(-top, 0)
\r
1040 newoffsety = max(top, 0)
\r
1041 newheight = self.height + top + bottom
\r
1043 newdata = self.initialdata(newwidth + 1, newheight + 1)
\r
1045 for x in range(min(self._width, newwidth) + 1):
\r
1046 for y in range(min(self._height, newheight) + 1):
\r
1047 for d in ["h", "v"]:
\r
1048 newdata[x+newoffsetx][y+newoffsety][d] = self._data[x+oldoffsetx][y+oldoffsety][d]
\r
1049 newnote[self.coodtokey(x+newoffsetx, y+newoffsety)] = self.getnote(x+oldoffsetx, y+oldoffsety)
\r
1050 self._width = newwidth
\r
1051 self._height = newheight
\r
1052 self.setoffset(self.offsetx -self.signx * left, self.offsety -self.signy * top)
\r
1053 self._data = newdata
\r
1054 self._note = newnote
\r
1056 def growwall(self, x1, y1, x2, y2, eraseonly, alwaysbox):
\r
1057 stepx, stepy = self.getstep(x1, y1, x2, y2)
\r
1058 offsetx, offsety = self.getoffset(x1, y1, x2, y2)
\r
1060 #delete inner walls.
\r
1061 for x in range(x1, x2+stepx, stepx):
\r
1062 for y in range(y1+stepy+offsety, y2+stepy+offsety, stepy):
\r
1063 self._data[x][y]["h"] = 0
\r
1064 for x in range(x1+stepx+offsetx, x2+stepx+offsetx, stepx):
\r
1065 for y in range(y1, y2+stepy, stepy):
\r
1066 self._data[x][y]["v"] = 0
\r
1069 #draw OUTER wall if it exists.
\r
1070 if alwaysbox or (x1 == x2 and y1 == y2):
\r
1083 for x in range(x1, x2+stepx, stepx):
\r
1084 if not (vline and x == x1):
\r
1086 if self._data[x][y1+offsety]["h"] == 0:
\r
1087 self._data[x][y1+offsety]["h"] = 1
\r
1088 if self._data[x][y2+stepy+offsety]["h"] == 0:
\r
1089 self._data[x][y2+stepy+offsety]["h"] = 1
\r
1090 for y in range(y1, y2+stepy, stepy):
\r
1091 if not (hline and y == y1):
\r
1093 if self._data[x1+offsetx][y]["v"] == 0:
\r
1094 self._data[x1+offsetx][y]["v"] = 1
\r
1095 if self._data[x2+stepx+offsetx][y]["v"] == 0:
\r
1096 self._data[x2+stepx+offsetx][y]["v"] = 1
\r
1098 def fillbackcolor(self, x1, y1, x2, y2, backcolor):
\r
1099 stepx, stepy = self.getstep(x1, y1, x2, y2)
\r
1100 for x in range(x1, x2+stepx, stepx):
\r
1101 for y in range(y1, y2+stepy, stepy):
\r
1102 self.setbackcolor(x, y, backcolor)
\r
1104 def getstep(self, x1, y1, x2, y2):
\r
1113 return (stepx, stepy)
\r
1115 def getoffset(self, x1, y1, x2, y2):
\r
1124 return (offsetx, offsety)
\r
1126 def save(self, fileName):
\r
1127 dt = self.savestring()
\r
1128 with codecs.open(fileName, "w", encoding="utf-8") as f:
\r
1131 def savestring(self):
\r
1133 data["size"] = {"x":self.width, "y":self.height}
\r
1134 data["offset"] = {"x":self.offsetx, "y":self.offsety}
\r
1135 data["sign"] = {"x":self.signx, "y":self.signy}
\r
1136 data["map"] = self.getmapstring()
\r
1140 for nk, ni in self._note.items():
\r
1141 if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"]:
\r
1142 x, y = self.keytocood(nk)
\r
1143 n[self.coodtokey(self.viewx(x), self.viewy(y))] = ni
\r
1145 return yaml.safe_dump(data, allow_unicode=True,
\r
1146 default_flow_style=False, encoding='utf-8')
\r
1148 def getmapstring(self):
\r
1151 for y in range(self.height):
\r
1153 for x in range(self.width):
\r
1155 s.append(self.hwall[self._data[x][y]["h"]])
\r
1158 m.append("".join(s))
\r
1160 for x in range(self.width):
\r
1161 s.append(self.vwall[self._data[x][y]["v"]])
\r
1163 s.append(self.vwall[self._data[self.width][y]["v"]])
\r
1165 m.append("".join(s))
\r
1168 for x in range(self.width):
\r
1170 s.append(self.hwall[self._data[x][y]["h"]])
\r
1173 m.append("".join(s))
\r
1176 def load(self, fileName):
\r
1177 with codecs.open(fileName, "r", encoding="utf-8") as f:
\r
1179 self.loadfromstring(st)
\r
1180 self.fileName = fileName
\r
1182 def loadfromstring(self, st):
\r
1183 data = yaml.safe_load(st)
\r
1186 self._width = data["size"]["x"]
\r
1187 self._height = data["size"]["y"]
\r
1188 self._signx = data["sign"]["x"]
\r
1189 self._signy = data["sign"]["y"]
\r
1190 self._offsetx = data["offset"]["x"]
\r
1191 self._offsety = data["offset"]["y"]
\r
1195 for y in range(self.height):
\r
1196 for x in range(self.width):
\r
1197 self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])
\r
1198 self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])
\r
1200 for y in range(self.height):
\r
1201 self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])
\r
1203 for x in range(self.width):
\r
1204 self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])
\r
1208 for nk, ni in data["note"].items():
\r
1209 if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"] != "":
\r
1210 x, y = self.keytocood(nk)
\r
1211 n[self.coodtokey(self.worldx(x), self.worldy(y))] = ni
\r
1216 class UndoManager(QtCore.QObject):
\r
1217 MAX_UNDO_COUNT = 128
\r
1218 changed = QtCore.Signal(bool, bool)
\r
1220 def __init__(self):
\r
1221 super(UndoManager, self).__init__()
\r
1225 self._undo = [None for x in range(self.MAX_UNDO_COUNT)]
\r
1227 self._undocount = 0
\r
1228 self.changed.emit(self.canundo, self.canredo)
\r
1230 def save(self, obj):
\r
1231 if self._index >= self.MAX_UNDO_COUNT:
\r
1232 self._undo = self._undo[1:]
\r
1234 self._undo.append(None)
\r
1235 self._undo[self._index] = obj
\r
1237 self._undocount = 0
\r
1238 self.changed.emit(self.canundo, self.canredo)
\r
1242 self._undocount += 1
\r
1243 self.changed.emit(self.canundo, self.canredo)
\r
1244 return self._undo[self._index - 1]
\r
1248 self._undocount -= 1
\r
1249 self.changed.emit(self.canundo, self.canredo)
\r
1250 return self._undo[self._index - 1]
\r
1253 def canundo(self):
\r
1254 return (self._index > 1)
\r
1257 def canredo(self):
\r
1258 return (self._undocount > 0)
\r
1261 def getcolorstring(color):
\r
1262 return "#{r:02x}{g:02x}{b:02x}".format(r=color.red(), g=color.green(), b=color.blue())
\r
1264 def getcolorfromstring(colorstring):
\r
1265 return QtGui.QColor.fromRgb(
\r
1266 int(colorstring[1:3], 16),
\r
1267 int(colorstring[3:5], 16),
\r
1268 int(colorstring[5:7], 16))
\r
1272 app = QtGui.QApplication(sys.argv)
\r
1273 mainWin = MainWindow()
\r
1274 app.installEventFilter(mainWin.centralWidget().mapframe)
\r
1276 sys.exit(app.exec_())
\r
1279 if __name__ == '__main__':
\r