OSDN Git Service

git-svn-id: https://svn.osdn.net/svnroot/pydun/trunk@1 44a3fe10-d6a8-4042-99bc-5bf971...
[pydun/Pydun.git] / Pydun.py
1 #!/usr/bin/env python\r
2 # -*- coding: utf-8 -*-\r
3 \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
8 \r
9 \r
10 import sys\r
11 import os.path\r
12 import codecs\r
13 import webbrowser\r
14 from PySide import QtCore, QtGui\r
15 import yaml\r
16 \r
17 \r
18 _mapengine = None\r
19 _mapimages = None\r
20 _undomanager = None\r
21 \r
22 projecturl = "http://sourceforge.jp/projects/pydun/"\r
23 projectversion = "1.0.0"\r
24 \r
25 class MainWindow(QtGui.QMainWindow):\r
26 \r
27     def __init__(self, parent=None):\r
28         global _mapengine\r
29         global _mapimages\r
30         global _undomanager\r
31         super(MainWindow, self).__init__(parent)\r
32 \r
33         _undomanager = UndoManager()\r
34         _mapimages = MapImages()\r
35         self.setmenu()\r
36         _undomanager.changed.connect(self.updateundostate)\r
37 \r
38         if len(sys.argv) <= 2:\r
39             self.new()\r
40         else:\r
41             self.open(sys.argv[2])\r
42 \r
43 \r
44         self.mainframe = MainFrame(self)\r
45         self.setCentralWidget(self.mainframe)\r
46 \r
47         self.statusbar = QtGui.QStatusBar(self)\r
48         self.statusbar.showMessage(u"")\r
49         self.setStatusBar(self.statusbar)\r
50 \r
51     def setmenu(self):\r
52         #File menu\r
53         filemenu = self.menuBar().addMenu(u"ファイル(&F)")\r
54 \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
59 \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
64 \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
69 \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
74 \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
79 \r
80         #Edit menu\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
97 \r
98         #Help menu\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
110 \r
111     @QtCore.Slot(bool, bool)\r
112     def updateundostate(self, canundo, canredo):\r
113         if canundo:\r
114             self.undoact.setEnabled(True)\r
115         else:\r
116             self.undoact.setDisabled(True)\r
117         if canredo:\r
118             self.redoact.setEnabled(True)\r
119         else:\r
120             self.redoact.setDisabled(True)\r
121 \r
122     def setTitle(self, fileName):\r
123         if fileName == None:\r
124             s = u"新規作成"\r
125         else:\r
126             s = os.path.basename(fileName)\r
127         s ="Pydun - " + s\r
128         self.setWindowTitle(s)\r
129 \r
130     @QtCore.Slot()\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
135             self.new()\r
136 \r
137     def new(self):\r
138         global _mapengine\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
143         try:\r
144             self.mainframe.mapframe.repaint()\r
145         except:\r
146             pass\r
147 \r
148     @QtCore.Slot()\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
154 \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
161 \r
162     @QtCore.Slot()\r
163     def save_triggered(self):\r
164         if _mapengine.fileName:\r
165             self.save(_mapengine.fileName)\r
166         else:\r
167             self.saveas_triggered()\r
168 \r
169     @QtCore.Slot()\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
175 \r
176     def save(self, fileName):\r
177         _mapengine.save(fileName)\r
178         self.setTitle(_mapengine.fileName)\r
179 \r
180     @QtCore.Slot()\r
181     def exit_triggered(self):\r
182         self.close()\r
183 \r
184     def closeEvent(self, event):\r
185         if self.exit():\r
186             event.accept()\r
187         else:\r
188             event.ignore()\r
189 \r
190     def exit(self):\r
191         if QtGui.QMessageBox.Ok == QtGui.QMessageBox.question(\r
192             self, u"確認", u"終了しますか?",\r
193             (QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)):\r
194             sys.exit()\r
195             return True\r
196         return False\r
197 \r
198     @QtCore.Slot()\r
199     def undo_triggered(self):\r
200         global _mapengine\r
201         _mapengine.loadfromstring(_undomanager.undo())\r
202         self.mainframe.mapframe.repaint()\r
203 \r
204     @QtCore.Slot()\r
205     def redo_triggered(self):\r
206         global _mapengine\r
207         _mapengine.loadfromstring(_undomanager.redo())\r
208         self.mainframe.mapframe.repaint()\r
209 \r
210     @QtCore.Slot()\r
211     def setorigine_triggered(self):\r
212         title = u"座標設定"\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
217         else:\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
222 \r
223     @QtCore.Slot()\r
224     def setmapsize_triggered(self):\r
225         dlg = SetSizeDialog(self)\r
226         dlg.setoriginalsize(_mapengine.width, _mapengine.height)\r
227         dlg.exec_()\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
233 \r
234     @QtCore.Slot()\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
238 \r
239     @QtCore.Slot()\r
240     def project_triggered(self):\r
241         webbrowser.open_new_tab(projecturl)\r
242 \r
243     @QtCore.Slot()\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
255 \r
256 \r
257 class MainFrame(QtGui.QFrame):\r
258     create_wall_menu_triggered_signal = QtCore.Signal(int, int, str, int)\r
259 \r
260     def __init__(self, parent=None):\r
261         super(MainFrame, self).__init__(parent)\r
262 \r
263         self.mapframe = MapFrame(self)\r
264         scrollarea = QtGui.QScrollArea(self)\r
265         scrollarea.setWidget(self.mapframe)\r
266 \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
272 \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
278 \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
284 \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
290 \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
295 \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
300 \r
301 \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
310 \r
311         self.setLayout(layout)\r
312 \r
313         self.h_wall_menu = self.create_wall_menu("h")\r
314         self.v_wall_menu = self.create_wall_menu("v")\r
315 \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
323 \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
329 \r
330             def triggerd(idx):\r
331                 def emit():\r
332                     self.create_wall_menu_triggered_signal.emit(menu.x, menu.y, direction, idx)\r
333                 return emit\r
334 \r
335             act.triggered.connect(triggerd(idx))\r
336             menu.addAction(act)\r
337         return menu\r
338 \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
344 \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
352             if eraseonly:\r
353                 backcolor = ""\r
354             else:\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
359 \r
360     @QtCore.Slot(int, int, str)\r
361     def mouse_released(self, x1, y1, direction):\r
362         #座標設定モード\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
371                 )\r
372                 _undomanager.save(_mapengine.savestring())\r
373             self.mapframe.setoriginemode = False\r
374             return\r
375 \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
389             else:\r
390                 pass\r
391         else:\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
396             menu.x = x1\r
397             menu.y = y1\r
398             menu.popup(QtGui.QCursor.pos())\r
399 \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
405 \r
406     @QtCore.Slot()\r
407     def setbackcolorbutton_clicked(self):\r
408         dlg = QtGui.QColorDialog(self)\r
409         dlg.exec_()\r
410         if dlg.result() == QtGui.QDialog.Accepted:\r
411             self.backcolorbox.color = dlg.currentColor()\r
412             self.backcolorbutton.setChecked(True)\r
413 \r
414 \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
419     global _mapengine\r
420     global _mapimages\r
421 \r
422     def __init__(self, parent=None):\r
423         super(MapFrame, self).__init__(parent)\r
424         self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
425         self._x1 = 0\r
426         self._y1 = 0\r
427         self._x2 = 0\r
428         self._y2 = 0\r
429         self._px1 = 0\r
430         self._py1 = 0\r
431         self._px2 = 0\r
432         self._py2 = 0\r
433         self._dragging = False\r
434         self.setoriginemode = False\r
435         self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
436         self.resize(\r
437             _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,\r
438             _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2\r
439         )\r
440 \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
448 \r
449         #エリアサイズを再計算\r
450         self.resize(\r
451             _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,\r
452             _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2\r
453         )\r
454 \r
455         #backcolor\r
456         for x in range(_mapengine.width):\r
457             xx = x * w\r
458             for y in range(_mapengine.height):\r
459                 yy = y * v\r
460                 backcolor = _mapengine.getbackcolor(x, y)\r
461                 if backcolor:\r
462                     painter.fillRect(wo + xx, ho + yy, w, v,\r
463                         getcolorfromstring(backcolor))\r
464 \r
465         #wall\r
466         for x in range(_mapengine.width + 1):\r
467             xx = x * w\r
468             for y in range(_mapengine.height + 1):\r
469                 yy = y * v\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
477                 if mark != "":\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
481                         mark)\r
482 \r
483         #座標設定中はdrawing box を表示しない。\r
484         if self.setoriginemode:\r
485             return\r
486 \r
487         #drawing box\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
494                 else:\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
500 \r
501     def eventFilter(self, obj, event):\r
502         def xpos():\r
503             return ((event.pos().x() - _mapimages.widthoffset) // _mapimages.width)\r
504 \r
505         def ypos():\r
506             return ((event.pos().y() - _mapimages.heightoffset) // _mapimages.height)\r
507 \r
508         if obj == self:\r
509             et = event.type()\r
510 \r
511             if et == QtCore.QEvent.MouseButtonPress:\r
512                 self._x1 = xpos()\r
513                 self._y1 = ypos()\r
514                 self._pos1 = event.pos()\r
515                 self._px1 = event.pos().x()\r
516                 self._py1 = event.pos().y()\r
517                 self._x2 = xpos()\r
518                 self._y2 = ypos()\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
523                 return True\r
524 \r
525             elif et == QtCore.QEvent.MouseMove:\r
526                 self._x2 = xpos()\r
527                 self._y2 = ypos()\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
535                 return True\r
536 \r
537             elif et == QtCore.QEvent.MouseButtonRelease:\r
538                 drag_emit = False\r
539                 release_emit = False\r
540                 if self._dragging:\r
541                     drag_emit = True\r
542                     if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:\r
543                         eraseonly = False\r
544                     elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:\r
545                         eraseonly = True\r
546                 else:\r
547                     release_emit = True\r
548                 if self.setoriginemode:\r
549                     release_emit = True\r
550 \r
551                 self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
552                 self._dragging = False\r
553                 if drag_emit:\r
554                     self.mouse_drag_released.emit(\r
555                         self._x1, self._y1, self._x2, self._y2, eraseonly)\r
556                 if release_emit:\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
560                         rx = self._x2\r
561                         ry = self._y2\r
562                         d = "v"\r
563                     elif rpx >= _mapimages.width - _mapimages.widthoffset:\r
564                         rx = self._x2 + 1\r
565                         ry = self._y2\r
566                         d = "v"\r
567                     elif rpy <= _mapimages.heightoffset:\r
568                         rx = self._x2\r
569                         ry = self._y2\r
570                         d = "h"\r
571                     elif rpy >= _mapimages.height - _mapimages.heightoffset:\r
572                         rx = self._x2\r
573                         ry = self._y2 + 1\r
574                         d = "h"\r
575                     else:\r
576                         rx = self._x2\r
577                         ry = self._y2\r
578                         d = "c"\r
579                     self.mouse_released.emit(rx, ry, d)\r
580                 return True\r
581 \r
582             else:\r
583                 return False\r
584         else:\r
585             # pass the event on to the parent class\r
586             return False\r
587 \r
588 \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
594 \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
600 \r
601 \r
602 class DetailDialog(QtGui.QDialog):\r
603     def __init__(self, parent=None):\r
604         super(DetailDialog, self).__init__(parent)\r
605 \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
611 \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
619 \r
620         self.forecolorbutton = QtGui.QPushButton(self)\r
621         self.forecolorbutton.setText(u"文字色(&C)...")\r
622         self.forecolorbutton.clicked.connect(self.forecolorbutton_clicked)\r
623 \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
628 \r
629         detaillabel = QtGui.QLabel(self)\r
630         detaillabel.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight)\r
631         detaillabel.setText(u"詳細(&D)")\r
632 \r
633         self.detailtext = QtGui.QTextEdit(self)\r
634         self.detailtext.setText(u"")\r
635         detaillabel.setBuddy(self.detailtext)\r
636 \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
641 \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
652 \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
658 \r
659     def forecolorbutton_clicked(self):\r
660         dlg = QtGui.QColorDialog(self)\r
661         dlg.exec_()\r
662         if dlg.result() == QtGui.QDialog.Accepted:\r
663             self.forecolorbox.color = dlg.currentColor()\r
664 \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
669 \r
670         promptlabel = QtGui.QLabel(self)\r
671         promptlabel.setAlignment(\r
672             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)\r
673         promptlabel.setText(u"この地点の座標を入力してください。")\r
674 \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
679 \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
684 \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
690 \r
691         ylabel = QtGui.QLabel(self)\r
692         ylabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
693         ylabel.setText(u"&Y")\r
694 \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
700 \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
705 \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
716 \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
721 \r
722     @property\r
723     def originex(self):\r
724         return self.xbox.value()\r
725 \r
726     @property\r
727     def originey(self):\r
728         return self.ybox.value()\r
729 \r
730 \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
735 \r
736         self.topbutton = QtGui.QRadioButton(self)\r
737         self.topbutton.setText(u"上(&T)")\r
738         self.topbutton.clicked.connect(self.updatewidgets)\r
739 \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
744 \r
745         self.bottombutton = QtGui.QRadioButton(self)\r
746         self.bottombutton.setText(u"下(&B)")\r
747         self.bottombutton.clicked.connect(self.updatewidgets)\r
748 \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
753 \r
754         self.leftbutton = QtGui.QRadioButton(self)\r
755         self.leftbutton.setText(u"左(&L)")\r
756         self.leftbutton.clicked.connect(self.updatewidgets)\r
757 \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
762 \r
763         self.rightbutton = QtGui.QRadioButton(self)\r
764         self.rightbutton.setText(u"右(&R)")\r
765         self.rightbutton.clicked.connect(self.updatewidgets)\r
766 \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
771 \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
776 \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
781 \r
782         verticalgroup = QtGui.QButtonGroup(self)\r
783         verticalgroup.addButton(self.topbutton)\r
784         verticalgroup.addButton(self.bottombutton)\r
785 \r
786         holizontalgroup = QtGui.QButtonGroup(self)\r
787         holizontalgroup.addButton(self.leftbutton)\r
788         holizontalgroup.addButton(self.rightbutton)\r
789 \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
794 \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
808 \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
817 \r
818     def updatewidgets(self):\r
819         dh = 0\r
820         dw = 0\r
821 \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
838 \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
843 \r
844     def getsize(self):\r
845         top = 0\r
846         bottom = 0\r
847         left = 0\r
848         right = 0\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
858 \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
869                     u"images",\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
875 \r
876 \r
877     @property\r
878     def width(self):\r
879         return self.wall_images[0]["h"].width()\r
880 \r
881     @property\r
882     def height(self):\r
883         return self.wall_images[0]["v"].height()\r
884 \r
885     @property\r
886     def widthoffset(self):\r
887         return self.wall_images[0]["v"].width()//2\r
888 \r
889     @property\r
890     def heightoffset(self):\r
891         return self.wall_images[0]["h"].height()//2\r
892 \r
893     def wall(self, index, direction):\r
894         return self.wall_images[index][direction]\r
895 \r
896 \r
897 class MapEngine(object):\r
898     hwall = " -#WMwmHVA"\r
899     vwall = " |#PCpc=DG"\r
900 \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
909 \r
910         self.initdata()\r
911         self.inityaml()\r
912         self._note = dict()\r
913 \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
918 \r
919     def initialdata(self, width, height):\r
920         dt = list()\r
921         for x in range(width):\r
922             dt.append(list())\r
923             for y in range(height):\r
924                 dt[x].append(dict())\r
925                 for d in ["h", "v"]:\r
926                     dt[x][y][d] = 0\r
927         return dt\r
928 \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
937 \r
938     def getdata(self, x, y, direction):\r
939         return self._data[x][y][direction]\r
940 \r
941     def setdata(self, x, y, direction, value):\r
942         self._data[x][y][direction] = value\r
943 \r
944     @property\r
945     def width(self):\r
946         return self._width\r
947 \r
948     @property\r
949     def height(self):\r
950         return self._height\r
951 \r
952     @property\r
953     def signx(self):\r
954         return self._signx\r
955 \r
956     @property\r
957     def signy(self):\r
958         return self._signy\r
959 \r
960     @property\r
961     def offsetx(self):\r
962         return self._offsetx\r
963 \r
964     @property\r
965     def offsety(self):\r
966         return self._offsety\r
967 \r
968     def setoffset(self, x, y):\r
969         self._offsetx = x\r
970         self._offsety = y\r
971 \r
972     def getmark(self, x, y):\r
973         return self.unescape(self.getnote(x, y)["mark"])\r
974 \r
975     def getdetail(self, x, y):\r
976         return self.unescape(self.getnote(x, y)["detail"])\r
977 \r
978     def getforecolor(self, x, y):\r
979         return self.getnote(x, y)["forecolor"]\r
980 \r
981     def getbackcolor(self, x, y):\r
982         return self.getnote(x, y)["backcolor"]\r
983 \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
987 \r
988     def coodtokey(self, x, y):\r
989         return u"{x:+05d}_{y:+05d}".format(x=x, y=y)\r
990 \r
991     def keytocood(self, key):\r
992         return map(int, key.split("_"))\r
993 \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
998 \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
1003 \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
1008 \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
1013 \r
1014     def setnote(self, x, y, note):\r
1015         self._note[self.coodtokey(x, y)] = note\r
1016 \r
1017     def escape(self, s):\r
1018         return s.replace("\\", "\\\\").replace("\n", r"\n")\r
1019 \r
1020     def unescape(self, s):\r
1021         return s.replace(r"\n", "\n").replace("\\\\", "\\")\r
1022 \r
1023     def viewx(self, x):\r
1024         return x * self.signx + self.offsetx\r
1025 \r
1026     def viewy(self, y):\r
1027         return y * self.signy + self.offsety\r
1028 \r
1029     def worldx(self, x):\r
1030         return (x - self.offsetx) / self.signx\r
1031 \r
1032     def worldy(self, y):\r
1033         return (y - self.offsety) / self.signy\r
1034 \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
1042 \r
1043         newdata = self.initialdata(newwidth + 1, newheight + 1)\r
1044         newnote = dict()\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
1055 \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
1059 \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
1067 \r
1068         if not eraseonly:\r
1069             #draw OUTER wall if it exists.\r
1070             if alwaysbox or (x1 == x2 and y1 == y2):\r
1071                 hline = False\r
1072                 vline = False\r
1073             elif x1 == x2:\r
1074                 hline = True\r
1075                 vline = False\r
1076             elif y1 == y2:\r
1077                 hline = False\r
1078                 vline = True\r
1079             else:\r
1080                 hline = False\r
1081                 vline = False\r
1082 \r
1083             for x in range(x1, x2+stepx, stepx):\r
1084                 if not (vline and x == x1):\r
1085                     if not hline:\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
1092                     if not vline:\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
1097 \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
1103 \r
1104     def getstep(self, x1, y1, x2, y2):\r
1105         if x1 <= x2:\r
1106             stepx = 1\r
1107         else:\r
1108             stepx = -1\r
1109         if y1 <= y2:\r
1110             stepy = 1\r
1111         else:\r
1112             stepy = -1\r
1113         return (stepx, stepy)\r
1114 \r
1115     def getoffset(self, x1, y1, x2, y2):\r
1116         if x1 <= x2:\r
1117             offsetx = 0\r
1118         else:\r
1119             offsetx = 1\r
1120         if y1 <= y2:\r
1121             offsety = 0\r
1122         else:\r
1123             offsety = 1\r
1124         return (offsetx, offsety)\r
1125 \r
1126     def save(self, fileName):\r
1127         dt = self.savestring()\r
1128         with codecs.open(fileName, "w", encoding="utf-8") as f:\r
1129             f.write(dt)\r
1130 \r
1131     def savestring(self):\r
1132         data = dict()\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
1137 \r
1138         #noteは表示用に座標変換する。\r
1139         n = dict()\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
1144         data["note"] = n\r
1145         return yaml.safe_dump(data, allow_unicode=True,\r
1146                 default_flow_style=False, encoding='utf-8')\r
1147 \r
1148     def getmapstring(self):\r
1149         #出力用マップ作成\r
1150         m = []\r
1151         for y in range(self.height):\r
1152             s = [" "]\r
1153             for x in range(self.width):\r
1154                 s.append("+")\r
1155                 s.append(self.hwall[self._data[x][y]["h"]])\r
1156             s.append("+")\r
1157             s.append(" ")\r
1158             m.append("".join(s))\r
1159             s = [" "]\r
1160             for x in range(self.width):\r
1161                 s.append(self.vwall[self._data[x][y]["v"]])\r
1162                 s.append(" ")\r
1163             s.append(self.vwall[self._data[self.width][y]["v"]])\r
1164             s.append(" ")\r
1165             m.append("".join(s))\r
1166         y = self.height\r
1167         s = [" "]\r
1168         for x in range(self.width):\r
1169             s.append("+")\r
1170             s.append(self.hwall[self._data[x][y]["h"]])\r
1171         s.append("+")\r
1172         s.append(" ")\r
1173         m.append("".join(s))\r
1174         return m\r
1175 \r
1176     def load(self, fileName):\r
1177         with codecs.open(fileName, "r", encoding="utf-8") as f:\r
1178             st = f.read()\r
1179         self.loadfromstring(st)\r
1180         self.fileName = fileName\r
1181 \r
1182     def loadfromstring(self, st):\r
1183         data = yaml.safe_load(st)\r
1184 \r
1185         #基本情報\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
1192 \r
1193         #マップ\r
1194         self.initdata()\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
1199         x = self.width\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
1202         y = self.height\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
1205 \r
1206         #noteは内部用に座標変換する。\r
1207         n = dict()\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
1212 \r
1213         self._note = n\r
1214 \r
1215 \r
1216 class UndoManager(QtCore.QObject):\r
1217     MAX_UNDO_COUNT = 128\r
1218     changed = QtCore.Signal(bool, bool)\r
1219 \r
1220     def __init__(self):\r
1221         super(UndoManager, self).__init__()\r
1222         self.clear()\r
1223 \r
1224     def clear(self):\r
1225         self._undo = [None for x in range(self.MAX_UNDO_COUNT)]\r
1226         self._index = 0\r
1227         self._undocount = 0\r
1228         self.changed.emit(self.canundo, self.canredo)\r
1229 \r
1230     def save(self, obj):\r
1231         if self._index >= self.MAX_UNDO_COUNT:\r
1232             self._undo = self._undo[1:]\r
1233             self._index -= 1\r
1234             self._undo.append(None)\r
1235         self._undo[self._index] = obj\r
1236         self._index += 1\r
1237         self._undocount = 0\r
1238         self.changed.emit(self.canundo, self.canredo)\r
1239 \r
1240     def undo(self):\r
1241         self._index -= 1\r
1242         self._undocount += 1\r
1243         self.changed.emit(self.canundo, self.canredo)\r
1244         return self._undo[self._index - 1]\r
1245 \r
1246     def redo(self):\r
1247         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
1251 \r
1252     @property\r
1253     def canundo(self):\r
1254         return (self._index > 1)\r
1255 \r
1256     @property\r
1257     def canredo(self):\r
1258         return (self._undocount > 0)\r
1259 \r
1260 \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
1263 \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
1269 \r
1270 \r
1271 def main():\r
1272     app = QtGui.QApplication(sys.argv)\r
1273     mainWin = MainWindow()\r
1274     app.installEventFilter(mainWin.centralWidget().mapframe)\r
1275     mainWin.show()\r
1276     sys.exit(app.exec_())\r
1277 \r
1278 \r
1279 if __name__ == '__main__':\r
1280     main()\r