OSDN Git Service

#31462対応
[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 locale\r
14 import urllib\r
15 import xml.etree.ElementTree\r
16 import webbrowser\r
17 from PySide import QtCore, QtGui\r
18 import yaml\r
19 \r
20 \r
21 _mapengine = None\r
22 _mapimages = None\r
23 _undomanager = None\r
24 \r
25 projecturl = "http://sourceforge.jp/projects/pydun/"\r
26 projectrssurl = "http://sourceforge.jp/projects/pydun/releases/rss"\r
27 projectversion = "1.0.5"\r
28 \r
29 \r
30 class MainWindow(QtGui.QMainWindow):\r
31 \r
32     def __init__(self, parent=None):\r
33         global _mapengine\r
34         global _mapimages\r
35         global _undomanager\r
36         super(MainWindow, self).__init__(parent)\r
37 \r
38         _undomanager = UndoManager()\r
39         _mapimages = MapImages()\r
40         self.setmenu()\r
41         _undomanager.changed.connect(self.updateundostate)\r
42 \r
43         self.new()\r
44         if len(sys.argv) >= 2:\r
45             self.open(unicode(sys.argv[1], locale.getpreferredencoding()))\r
46 \r
47         self.mainframe = MainFrame(self)\r
48         self.setCentralWidget(self.mainframe)\r
49 \r
50         self.statusbar = QtGui.QStatusBar(self)\r
51         self.statusbar.showMessage(u"")\r
52         self.setStatusBar(self.statusbar)\r
53         if "windowSize" in config:\r
54             self.resize(\r
55                 QtCore.QSize(\r
56                     config["windowSize"]["width"],\r
57                     config["windowSize"]["height"]))\r
58 \r
59     def setmenu(self):\r
60         #File menu\r
61         filemenu = self.menuBar().addMenu(u"ファイル(&F)")\r
62 \r
63         newact = QtGui.QAction(u"新規(&N)", self)\r
64         newact.triggered.connect(self.new_triggered)\r
65         newact.setShortcut(QtGui.QKeySequence.New)\r
66         filemenu.addAction(newact)\r
67 \r
68         openact = QtGui.QAction(u"開く(&O)...", self)\r
69         openact.triggered.connect(self.open_triggered)\r
70         openact.setShortcut(QtGui.QKeySequence.Open)\r
71         filemenu.addAction(openact)\r
72 \r
73         saveact = QtGui.QAction(u"上書き保存(&S)", self)\r
74         saveact.triggered.connect(self.save_triggered)\r
75         saveact.setShortcut(QtGui.QKeySequence.Save)\r
76         filemenu.addAction(saveact)\r
77 \r
78         saveasact = QtGui.QAction(u"名前をつけて保存(&A)...", self)\r
79         saveasact.triggered.connect(self.saveas_triggered)\r
80         saveasact.setShortcut(QtGui.QKeySequence.SaveAs)\r
81         filemenu.addAction(saveasact)\r
82 \r
83         exitact = QtGui.QAction(u"終了(&E)", self)\r
84         exitact.triggered.connect(self.exit_triggered)\r
85         exitact.setShortcut(QtGui.QKeySequence.Quit)\r
86         filemenu.addAction(exitact)\r
87 \r
88         #Edit menu\r
89         editmenu = self.menuBar().addMenu(u"編集(&E)")\r
90         self.undoact = QtGui.QAction(u"元に戻す(&U)", self)\r
91         self.undoact.triggered.connect(self.undo_triggered)\r
92         self.undoact.setShortcut(QtGui.QKeySequence.Undo)\r
93         editmenu.addAction(self.undoact)\r
94         self.redoact = QtGui.QAction(u"やり直し(&R)", self)\r
95         self.redoact.triggered.connect(self.redo_triggered)\r
96         self.redoact.setShortcut(QtGui.QKeySequence.Redo)\r
97         editmenu.addAction(self.redoact)\r
98         editmenu.addSeparator()\r
99         setmapsizeact = QtGui.QAction(u"マップのサイズ(&S)", self)\r
100         setmapsizeact.triggered.connect(self.setmapsize_triggered)\r
101         editmenu.addAction(setmapsizeact)\r
102         setorigineact = QtGui.QAction(u"座標設定(&O)", self)\r
103         setorigineact.triggered.connect(self.setorigine_triggered)\r
104         editmenu.addAction(setorigineact)\r
105 \r
106         #Help menu\r
107         helpmenu = self.menuBar().addMenu(u"ヘルプ(&H)")\r
108         tutorialact = QtGui.QAction(u"ヘルプの表示(&H)", self)\r
109         tutorialact.triggered.connect(self.tutorial_triggered)\r
110         tutorialact.setShortcut(QtGui.QKeySequence.HelpContents)\r
111         helpmenu.addAction(tutorialact)\r
112         projectact = QtGui.QAction(u"プロジェクトのWebサイト(&W)", self)\r
113         projectact.triggered.connect(self.project_triggered)\r
114         helpmenu.addAction(projectact)\r
115         aboutact = QtGui.QAction(u"Pydunについて(&A)...", self)\r
116         aboutact.triggered.connect(self.about_triggered)\r
117         helpmenu.addAction(aboutact)\r
118 \r
119     @QtCore.Slot(bool, bool)\r
120     def updateundostate(self, canundo, canredo):\r
121         if canundo:\r
122             self.undoact.setEnabled(True)\r
123         else:\r
124             self.undoact.setDisabled(True)\r
125         if canredo:\r
126             self.redoact.setEnabled(True)\r
127         else:\r
128             self.redoact.setDisabled(True)\r
129 \r
130     def setTitle(self, filename):\r
131         s ="Pydun - " + self.getfilename(filename)\r
132         self.setWindowTitle(s)\r
133 \r
134     def getfilename(self, filename):\r
135         if filename == None:\r
136             s = u"新規作成"\r
137         else:\r
138             s = os.path.basename(filename)\r
139         return s\r
140 \r
141     @QtCore.Slot()\r
142     def new_triggered(self):\r
143         if self.confirmdiscarding():\r
144             self.new()\r
145             return True\r
146         return False\r
147 \r
148     def new(self):\r
149         global _mapengine\r
150         _mapengine = MapEngine(20, 20, 1, -1, 0, +19)\r
151         _undomanager.init(_mapengine.savestring())\r
152         self.setTitle(None)\r
153         try:\r
154             self.mainframe.mapframe.repaint()\r
155         except:\r
156             pass\r
157 \r
158     def confirmdiscarding(self):\r
159         if not _undomanager.commited:\r
160             dlg = PydunAskSaveDialog(self, self.getfilename(_mapengine.filename))\r
161             ret = dlg.exec_()\r
162             if ret == QtGui.QMessageBox.Cancel:\r
163                 return False\r
164             elif ret == QtGui.QMessageBox.Save:\r
165                 saved = self.save_triggered()\r
166                 if not saved:\r
167                     return False\r
168         return True\r
169 \r
170     @QtCore.Slot()\r
171     def open_triggered(self):\r
172         if self.confirmdiscarding():\r
173             d = ""\r
174             try:\r
175                 d = os.path.dirname(_mapengine.filename)\r
176             except:\r
177                 pass\r
178             filename = QtGui.QFileDialog.getOpenFileName(\r
179                 dir=d,\r
180                 filter=u"*.pydun;;*.*", selectedFilter=u"*.pydun")\r
181             if filename[0] != u"":\r
182                 self.open(filename[0])\r
183 \r
184     def open(self, filename):\r
185         _mapengine.load(filename)\r
186         _undomanager.init(_mapengine.savestring())\r
187         self.setTitle(_mapengine.filename)\r
188         try:\r
189             self.mainframe.mapframe.repaint()\r
190         except:\r
191             pass\r
192 \r
193     @QtCore.Slot()\r
194     def save_triggered(self):\r
195         if _mapengine.filename:\r
196             self.save(_mapengine.filename)\r
197             saved = True\r
198         else:\r
199             saved = self.saveas_triggered()\r
200         return saved\r
201 \r
202     @QtCore.Slot()\r
203     def saveas_triggered(self):\r
204         d = ""\r
205         try:\r
206             d = os.path.dirname(_mapengine.filename)\r
207         except:\r
208             pass\r
209         filename = QtGui.QFileDialog.getSaveFileName(\r
210             dir=d,\r
211             filter=u"*.pydun;;*.*", selectedFilter=u"*.pydun")\r
212         if filename[0] != u"":\r
213             self.save(filename[0])\r
214             return True\r
215         else:\r
216             return False\r
217 \r
218     def save(self, filename):\r
219         _mapengine.save(filename)\r
220         _undomanager.commit()\r
221         self.setTitle(_mapengine.filename)\r
222 \r
223     @QtCore.Slot()\r
224     def exit_triggered(self):\r
225         self.close()\r
226 \r
227     def closeEvent(self, event):\r
228         if self.exit():\r
229             event.accept()\r
230         else:\r
231             event.ignore()\r
232 \r
233     def exit(self):\r
234         global config\r
235         global configfilename\r
236         if self.confirmdiscarding():\r
237             config["windowSize"] = dict()\r
238             config["windowSize"]["width"] = self.size().width()\r
239             config["windowSize"]["height"] = self.size().height()\r
240             with open(configfilename, "w") as f:\r
241                 yaml.safe_dump(config, f, default_flow_style=False)\r
242             sys.exit()\r
243             return True\r
244         return False\r
245 \r
246     @QtCore.Slot()\r
247     def undo_triggered(self):\r
248         global _mapengine\r
249         _mapengine.loadfromstring(_undomanager.undo())\r
250         self.mainframe.mapframe.repaint()\r
251 \r
252     @QtCore.Slot()\r
253     def redo_triggered(self):\r
254         global _mapengine\r
255         _mapengine.loadfromstring(_undomanager.redo())\r
256         self.mainframe.mapframe.repaint()\r
257 \r
258     @QtCore.Slot()\r
259     def setorigine_triggered(self):\r
260         title = u"座標設定"\r
261         if self.mainframe.mapframe.setoriginemode:\r
262             QtGui.QMessageBox.information(\r
263                 self, title, u"座標設定を中止します。", QtGui.QMessageBox.Ok)\r
264             self.mainframe.mapframe.setoriginemode = False\r
265         else:\r
266             if QtGui.QMessageBox.Ok == QtGui.QMessageBox.information(\r
267                 self, title, u"基準にする地点をクリックしてください。",\r
268                 (QtGui.QMessageBox.Ok| QtGui.QMessageBox.Cancel)):\r
269                 self.mainframe.mapframe.setoriginemode = True\r
270 \r
271     @QtCore.Slot()\r
272     def setmapsize_triggered(self):\r
273         dlg = SetSizeDialog(self)\r
274         dlg.setoriginalsize(_mapengine.width, _mapengine.height)\r
275         dlg.exec_()\r
276         if dlg.result() == QtGui.QDialog.Accepted:\r
277             top, bottom, left, right = dlg.getsize()\r
278             _mapengine.changesize(top, bottom, left, right)\r
279             _undomanager.save(_mapengine.savestring())\r
280             self.mainframe.mapframe.repaint()\r
281 \r
282     @QtCore.Slot()\r
283     def tutorial_triggered(self):\r
284         url = basedir() + "/help/index.html"\r
285         webbrowser.open_new_tab(url)\r
286 \r
287     @QtCore.Slot()\r
288     def project_triggered(self):\r
289         webbrowser.open_new_tab(projecturl)\r
290 \r
291     @QtCore.Slot()\r
292     def about_triggered(self):\r
293         QtGui.QMessageBox.about(self, "Pydun",\r
294         u"<h1>Pydun.py "+ projectversion + "</h1>"\r
295         u"<p>Copyright (c) 2013 WATAHIKI Hiroyuki</p>"\r
296         u"<p>url: <a href='" + projecturl + "'>" + projecturl + "</a></p>"\r
297         u"<p>e-mail: hrwatahiki at gmail.com</p>"\r
298         u"<p>このソフトウェアはMITライセンスです。</p>"\r
299         u"<p>このソフトウェアは以下のソフトウェアを使用しています。: "\r
300         u"Python, PySide, PyYAML "\r
301         u"これらの作成者に深く感謝いたします。</p>"\r
302         u"<p>詳細はLICENCE.txtを参照してください。</p>")\r
303 \r
304 \r
305 class MainFrame(QtGui.QFrame):\r
306     create_wall_menu_triggered_signal = QtCore.Signal(int, int, str, int)\r
307 \r
308     def __init__(self, parent=None):\r
309         super(MainFrame, self).__init__(parent)\r
310 \r
311         self.mapframe = MapFrame(self)\r
312         scrollarea = QtGui.QScrollArea(self)\r
313         scrollarea.setWidget(self.mapframe)\r
314 \r
315         self.detail = QtGui.QLabel(self)\r
316         self.detail.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)\r
317         self.detail.setText(u"")\r
318         self.detail.setMaximumHeight(100)\r
319         self.detail.setMinimumHeight(100)\r
320 \r
321         self.boxdrawbutton = QtGui.QRadioButton(self)\r
322         self.boxdrawbutton.setText(u"ボックス形式で壁を描画(&B)")\r
323         self.boxdrawbutton.setChecked(True)\r
324         self.boxdrawbutton.setSizePolicy(\r
325             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
326 \r
327         self.growdrawbutton = QtGui.QRadioButton(self)\r
328         self.growdrawbutton.setText(u"足跡形式で壁を描画(&G)")\r
329         self.growdrawbutton.setChecked(False)\r
330         self.growdrawbutton.setSizePolicy(\r
331             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
332 \r
333         self.backcolorbutton = QtGui.QRadioButton(self)\r
334         self.backcolorbutton.setText(u"背景色(&C)")\r
335         self.backcolorbutton.setChecked(False)\r
336         self.backcolorbutton.setSizePolicy(\r
337             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
338 \r
339         self.setbackcolorbutton = QtGui.QPushButton(self)\r
340         self.setbackcolorbutton.setText(u"背景色を設定(&S)...")\r
341         self.setbackcolorbutton.setSizePolicy(\r
342             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
343 \r
344         self.backcolorbox = ColorBox(self)\r
345         self.backcolorbox.setMinimumSize(30, 30)\r
346         self.backcolorbox.setSizePolicy(\r
347             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
348 \r
349         latestversion = getlatestversion()\r
350         if latestversion != projectversion:\r
351             self.update = QtGui.QLabel(self)\r
352             self.update.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)\r
353             self.update.setText(u"<a href='{url}'>最新のPydun({ver})がダウンロードできます。</a>".format(url=projecturl, ver=latestversion))\r
354             self.update.setOpenExternalLinks(True)\r
355 \r
356         layout = QtGui.QGridLayout(self)\r
357         layout.addWidget(scrollarea, 0, 0, 1, 3)\r
358         layout.addWidget(self.detail, 1, 0, 4, 1)\r
359         layout.addWidget(self.boxdrawbutton, 1, 1, 1, 2)\r
360         layout.addWidget(self.growdrawbutton, 2, 1, 1, 2)\r
361         layout.addWidget(self.backcolorbutton, 3, 1, 1, 2)\r
362         layout.addWidget(self.setbackcolorbutton, 4, 1, 1, 1)\r
363         layout.addWidget(self.backcolorbox, 4, 2, 1, 1)\r
364         if latestversion != projectversion:\r
365             layout.addWidget(self.update, 5, 0, 1, 3)\r
366 \r
367         self.setLayout(layout)\r
368 \r
369         self.h_wall_menu = self.create_wall_menu("h")\r
370         self.v_wall_menu = self.create_wall_menu("v")\r
371 \r
372         self.mapframe.mouse_moved.connect(self.mouse_moved)\r
373         self.mapframe.mouse_released.connect(self.mouse_released)\r
374         self.mapframe.mouse_drag_released.connect(self.mouse_drag_released)\r
375         self.create_wall_menu_triggered_signal.connect(\r
376             self.create_wall_menu_triggered)\r
377         self.setbackcolorbutton.clicked.connect(\r
378             self.setbackcolorbutton_clicked)\r
379 \r
380     def create_wall_menu(self, direction):\r
381         menu = QtGui.QMenu(self)\r
382         for idx, img in enumerate(_mapimages.wall_icons):\r
383             act = QtGui.QAction(_mapimages.wall_texts[idx][direction], self)\r
384             act.setIcon(img[direction])\r
385 \r
386             def triggerd(idx):\r
387                 def emit():\r
388                     self.create_wall_menu_triggered_signal.emit(menu.x, menu.y, direction, idx)\r
389                 return emit\r
390 \r
391             act.triggered.connect(triggerd(idx))\r
392             menu.addAction(act)\r
393         return menu\r
394 \r
395     @QtCore.Slot(int, int, int)\r
396     def mouse_moved(self, x=0, y=0, b=QtCore.Qt.MouseButton.NoButton):\r
397         cood = u"({x}, {y})\n".format(x=_mapengine.viewx(x), y=_mapengine.viewy(y))\r
398         self.detail.setText(cood + _mapengine.getdetail(x, y))\r
399         self.mapframe.repaint()\r
400 \r
401     @QtCore.Slot(int, int, int, int, int)\r
402     def mouse_drag_released(self, x1, y1, x2, y2, eraseonly):\r
403         if self.boxdrawbutton.isChecked():\r
404             _mapengine.growwall(x1, y1, x2, y2, eraseonly, True)\r
405         elif self.growdrawbutton.isChecked():\r
406             _mapengine.growwall(x1, y1, x2, y2, eraseonly, False)\r
407         elif self.backcolorbutton.isChecked():\r
408             if eraseonly:\r
409                 backcolor = ""\r
410             else:\r
411                 backcolor = getcolorstring(self.backcolorbox.color)\r
412             _mapengine.fillbackcolor(x1, y1, x2, y2, backcolor)\r
413         _undomanager.save(_mapengine.savestring())\r
414         self.mapframe.repaint()\r
415 \r
416     @QtCore.Slot(int, int, str)\r
417     def mouse_released(self, x1, y1, direction):\r
418         #座標設定モード\r
419         if self.mapframe.setoriginemode:\r
420             dlg = SetOrigineDialog(self)\r
421             dlg.setcurrent(_mapengine.viewx(x1), _mapengine.viewy(y1))\r
422             dlg.exec_() #showでは処理がとまらない。\r
423             if dlg.result() == QtGui.QDialog.Accepted:\r
424                 _mapengine.setoffset(\r
425                     dlg.originex - _mapengine.viewx(x1) + _mapengine.offsetx,\r
426                     dlg.originey - _mapengine.viewy(y1) + _mapengine.offsety\r
427                 )\r
428                 _undomanager.save(_mapengine.savestring())\r
429             self.mapframe.setoriginemode = False\r
430             return\r
431 \r
432         if direction == "c":\r
433             dlg = DetailDialog(self)\r
434             dlg.setvalue(_mapengine.viewx(x1), _mapengine.viewy(y1),\r
435                 _mapengine.getmark(x1, y1), _mapengine.getdetail(x1, y1),\r
436                 getcolorfromstring(_mapengine.getforecolor(x1, y1)))\r
437             dlg.exec_() #showでは処理がとまらない。\r
438             if dlg.result() == QtGui.QDialog.Accepted:\r
439                 forecolor = getcolorstring(dlg.forecolorbox.color)\r
440                 _mapengine.setmark(x1, y1, dlg.marktext.text())\r
441                 _mapengine.setdetail(x1, y1, dlg.detailtext.toPlainText())\r
442                 _mapengine.setforecolor(x1, y1, forecolor)\r
443                 _undomanager.save(_mapengine.savestring())\r
444                 self.mapframe.repaint()\r
445             else:\r
446                 pass\r
447         else:\r
448             if direction == "h":\r
449                 menu = self.h_wall_menu\r
450             elif direction == "v":\r
451                 menu = self.v_wall_menu\r
452             menu.x = x1\r
453             menu.y = y1\r
454             menu.popup(QtGui.QCursor.pos())\r
455 \r
456     @QtCore.Slot(int, int, str, int)\r
457     def create_wall_menu_triggered(self, x1, y1, direction, wall):\r
458         _mapengine.setdata(x1, y1, direction, wall)\r
459         _undomanager.save(_mapengine.savestring())\r
460         self.mapframe.repaint()\r
461 \r
462     @QtCore.Slot()\r
463     def setbackcolorbutton_clicked(self):\r
464         global config\r
465         dlg = PydunColorDialog(self, config.get("customColor", dict()))\r
466         dlg.setCurrentColor(self.backcolorbox.color)\r
467         dlg.exec_()\r
468         config["customColor"] = dlg.config\r
469         if dlg.result() == QtGui.QDialog.Accepted:\r
470             self.backcolorbox.color = dlg.currentColor()\r
471             self.backcolorbutton.setChecked(True)\r
472 \r
473 \r
474 class MapFrame(QtGui.QFrame):\r
475     mouse_moved = QtCore.Signal(int, int, int)\r
476     mouse_released = QtCore.Signal(int, int, str)\r
477     mouse_drag_released = QtCore.Signal(int, int, int, int, int)\r
478     global _mapengine\r
479     global _mapimages\r
480 \r
481     def __init__(self, parent=None):\r
482         super(MapFrame, self).__init__(parent)\r
483         self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
484         self._x1 = 0\r
485         self._y1 = 0\r
486         self._x2 = 0\r
487         self._y2 = 0\r
488         self._px1 = 0\r
489         self._py1 = 0\r
490         self._px2 = 0\r
491         self._py2 = 0\r
492         self._dragging = False\r
493         self.setoriginemode = False\r
494         self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
495         self.resize(\r
496             _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,\r
497             _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2\r
498         )\r
499 \r
500     def paintEvent(self, event):\r
501         painter = QtGui.QPainter(self)\r
502         painter.fillRect(0, 0, self.width(), self.height(), QtGui.QColor(255, 255, 255))\r
503         w = _mapimages.width - 1\r
504         v = _mapimages.height - 1\r
505         ho = _mapimages.heightoffset\r
506         wo = _mapimages.widthoffset\r
507 \r
508         #エリアサイズを再計算\r
509         self.resize(\r
510             w * (_mapengine.width) + _mapimages.widthoffset * 2,\r
511             v * (_mapengine.height) + _mapimages.heightoffset * 2\r
512         )\r
513 \r
514         #backcolor\r
515         for x in range(_mapengine.width):\r
516             xx = x * w\r
517             for y in range(_mapengine.height):\r
518                 yy = y * v\r
519                 backcolor = _mapengine.getbackcolor(x, y)\r
520                 if backcolor:\r
521                     painter.fillRect(wo + xx, ho + yy, w, v,\r
522                         getcolorfromstring(backcolor))\r
523 \r
524         #grid\r
525         for x in range(_mapengine.width + 1):\r
526             xx = x * w\r
527             for y in range(_mapengine.height + 1):\r
528                 yy = y * v\r
529                 if x != _mapengine.width:\r
530                     painter.drawImage(wo + xx, yy,\r
531                         _mapimages.wall(0, "h"))\r
532                 if y != _mapengine.height:\r
533                     painter.drawImage(xx, ho + yy,\r
534                         _mapimages.wall(0, "v"))\r
535 \r
536         #wall(gridは描画しない)\r
537         for x in range(_mapengine.width + 1):\r
538             xx = x * w\r
539             for y in range(_mapengine.height + 1):\r
540                 yy = y * v\r
541                 if x != _mapengine.width and _mapengine.getdata(x, y, "h") != 0:\r
542                     painter.drawImage(wo + xx, yy,\r
543                         _mapimages.wall(_mapengine.getdata(x, y, "h"), "h"))\r
544                 if y != _mapengine.height and _mapengine.getdata(x, y, "v") != 0:\r
545                     painter.drawImage(xx, ho + yy,\r
546                         _mapimages.wall(_mapengine.getdata(x, y, "v"), "v"))\r
547                 mark = _mapengine.getmark(x, y)\r
548                 if mark != "":\r
549                     painter.setPen(getcolorfromstring(_mapengine.getforecolor(x, y)))\r
550                     painter.drawText(wo + xx + 2, ho + yy + 2, w - 2, v - 2,\r
551                         QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter,\r
552                         mark)\r
553 \r
554         #座標設定中はdrawing box を表示しない。\r
555         if self.setoriginemode:\r
556             return\r
557 \r
558         #drawing box\r
559         if self._pressedbutton != QtCore.Qt.MouseButton.NoButton:\r
560             if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:\r
561                 if self._x1 == self._x2 and self._y1 == self._y2:\r
562                     painter.setPen(QtGui.QColor(255, 0, 0))\r
563                 elif self._x1 == self._x2 or self._y1 == self._y2:\r
564                     painter.setPen(QtGui.QColor(0, 255, 0))\r
565                 else:\r
566                     painter.setPen(QtGui.QColor(255, 0, 0))\r
567             elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:\r
568                 painter.setPen(QtGui.QColor(0, 0, 255))\r
569             painter.drawRect(self._px1, self._py1,\r
570                 self._px2 - self._px1, self._py2 - self._py1)\r
571 \r
572     def eventFilter(self, obj, event):\r
573         def xpos():\r
574             return ((event.pos().x() - _mapimages.widthoffset) // (_mapimages.width - 1))\r
575 \r
576         def ypos():\r
577             return ((event.pos().y() - _mapimages.heightoffset) // (_mapimages.height - 1))\r
578 \r
579         if obj == self:\r
580             et = event.type()\r
581 \r
582             if et == QtCore.QEvent.MouseButtonPress:\r
583                 self._x1 = xpos()\r
584                 self._y1 = ypos()\r
585                 self._pos1 = event.pos()\r
586                 self._px1 = event.pos().x()\r
587                 self._py1 = event.pos().y()\r
588                 self._x2 = xpos()\r
589                 self._y2 = ypos()\r
590                 self._px2 = event.pos().x()\r
591                 self._py2 = event.pos().y()\r
592                 self._pressedbutton = event.buttons()\r
593                 self._dragging = False\r
594                 return True\r
595 \r
596             elif et == QtCore.QEvent.MouseMove:\r
597                 self._x2 = xpos()\r
598                 self._y2 = ypos()\r
599                 self._px2 = event.pos().x()\r
600                 self._py2 = event.pos().y()\r
601                 if (self._pressedbutton != QtCore.Qt.MouseButton.NoButton and\r
602                     (event.pos() - self._pos1).manhattanLength() >=\r
603                      QtGui.QApplication.startDragDistance()):\r
604                     self._dragging = True\r
605                 self.mouse_moved.emit(self._x2, self._y2, event.buttons())\r
606                 return True\r
607 \r
608             elif et == QtCore.QEvent.MouseButtonRelease:\r
609                 drag_emit = False\r
610                 release_emit = False\r
611                 if self._dragging:\r
612                     drag_emit = True\r
613                     if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:\r
614                         eraseonly = False\r
615                     elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:\r
616                         eraseonly = True\r
617                 else:\r
618                     release_emit = True\r
619                 if self.setoriginemode:\r
620                     release_emit = True\r
621 \r
622                 self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
623                 self._dragging = False\r
624                 if drag_emit:\r
625                     self.mouse_drag_released.emit(\r
626                         self._x1, self._y1, self._x2, self._y2, eraseonly)\r
627                 if release_emit:\r
628                     rpx = self._px2 - self._x2 * (_mapimages.width - 1) - _mapimages.widthoffset\r
629                     rpy = self._py2 - self._y2 * (_mapimages.height - 1) - _mapimages.heightoffset\r
630                     rdx = rpx - (_mapimages.width - 1) // 2\r
631                     rdy = rpy - (_mapimages.height - 1) // 2\r
632                     if rpx <= _mapimages.widthoffset and abs(rdx) > abs(rdy):\r
633                         rx = self._x2\r
634                         ry = self._y2\r
635                         d = "v"\r
636                     elif rpx >= _mapimages.width - _mapimages.widthoffset and abs(rdx) > abs(rdy):\r
637                         rx = self._x2 + 1\r
638                         ry = self._y2\r
639                         d = "v"\r
640                     elif rpy <= _mapimages.heightoffset and abs(rdx) <= abs(rdy):\r
641                         rx = self._x2\r
642                         ry = self._y2\r
643                         d = "h"\r
644                     elif rpy >= _mapimages.height - _mapimages.heightoffset and abs(rdx) <= abs(rdy):\r
645                         rx = self._x2\r
646                         ry = self._y2 + 1\r
647                         d = "h"\r
648                     else:\r
649                         rx = self._x2\r
650                         ry = self._y2\r
651                         d = "c"\r
652                     self.mouse_released.emit(rx, ry, d)\r
653                 return True\r
654 \r
655             else:\r
656                 return False\r
657         else:\r
658             # pass the event on to the parent class\r
659             return False\r
660 \r
661 \r
662 class ColorBox(QtGui.QFrame):\r
663     def __init__(self, parent=None):\r
664         super(ColorBox, self).__init__(parent)\r
665         self.color = QtGui.QColor(255, 255, 255)\r
666         self.bordercolor = QtGui.QColor(0, 0, 0)\r
667 \r
668     def paintEvent(self, event):\r
669         painter = QtGui.QPainter(self)\r
670         painter.fillRect(0, 0, self.width(), self.height(), self.color)\r
671         painter.setPen(self.bordercolor)\r
672         painter.drawRect(0, 0, self.width() - 1, self.height() - 1)\r
673 \r
674 \r
675 class DetailDialog(QtGui.QDialog):\r
676     def __init__(self, parent=None):\r
677         super(DetailDialog, self).__init__(parent)\r
678 \r
679         marklabel = QtGui.QLabel(self)\r
680         marklabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
681         marklabel.setText(u"マーク(&M)")\r
682         marklabel.setSizePolicy(\r
683             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
684 \r
685         self.marktext = QtGui.QLineEdit(self)\r
686         self.marktext.setMaxLength(1)\r
687         self.marktext.setText(u"")\r
688         self.marktext.setMinimumWidth(20)\r
689         self.marktext.setSizePolicy(\r
690             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
691         marklabel.setBuddy(self.marktext)\r
692 \r
693         self.forecolorbutton = QtGui.QPushButton(self)\r
694         self.forecolorbutton.setText(u"文字色(&C)...")\r
695         self.forecolorbutton.clicked.connect(self.forecolorbutton_clicked)\r
696 \r
697         self.forecolorbox = ColorBox(self)\r
698         self.forecolorbox.setMinimumSize(30, 30)\r
699         self.forecolorbox.setSizePolicy(\r
700             QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)\r
701 \r
702         detaillabel = QtGui.QLabel(self)\r
703         detaillabel.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight)\r
704         detaillabel.setText(u"詳細(&D)")\r
705 \r
706         self.detailtext = QtGui.QTextEdit(self)\r
707         self.detailtext.setText(u"")\r
708         detaillabel.setBuddy(self.detailtext)\r
709 \r
710         self.buttonbox = QtGui.QDialogButtonBox(\r
711             QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)\r
712         self.buttonbox.accepted.connect(self.accept)\r
713         self.buttonbox.rejected.connect(self.reject)\r
714         self.buttonbox.button(QtGui.QDialogButtonBox.Ok).setText(u"OK")\r
715         self.buttonbox.button(QtGui.QDialogButtonBox.Cancel).setText(u"キャンセル")\r
716 \r
717         layout = QtGui.QGridLayout()\r
718         layout.addWidget(marklabel, 0, 0, 1, 1)\r
719         layout.addWidget(self.marktext, 0, 1, 1, 1)\r
720         layout.addWidget(self.forecolorbutton, 0, 2, 1, 1)\r
721         layout.addWidget(self.forecolorbox, 0, 3, 1, 1)\r
722         layout.addWidget(detaillabel, 1, 0, 1, 1)\r
723         layout.addWidget(self.detailtext, 1, 1, 1, 3)\r
724         layout.addWidget(self.buttonbox, 2, 0, 1, 4)\r
725         self.setLayout(layout)\r
726         self.setModal(True)\r
727 \r
728     def setvalue(self, x, y, mark, detail, color):\r
729         self.setWindowTitle("({x}, {y})".format(x=x, y=y))\r
730         self.marktext.setText(mark)\r
731         self.detailtext.setText(detail)\r
732         self.forecolorbox.color = color\r
733 \r
734     def forecolorbutton_clicked(self):\r
735         global config\r
736         dlg = PydunColorDialog(self, config.get("customColor", dict()))\r
737         dlg.setCurrentColor(self.forecolorbox.color)\r
738         dlg.exec_()\r
739         config["customColor"] = dlg.config\r
740         if dlg.result() == QtGui.QDialog.Accepted:\r
741             self.forecolorbox.color = dlg.currentColor()\r
742 \r
743 \r
744 class SetOrigineDialog(QtGui.QDialog):\r
745     def __init__(self, parent=None):\r
746         super(SetOrigineDialog, self).__init__(parent)\r
747         self.setWindowTitle(u"座標設定")\r
748 \r
749         promptlabel = QtGui.QLabel(self)\r
750         promptlabel.setAlignment(\r
751             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)\r
752         promptlabel.setText(u"この地点の座標を入力してください。")\r
753 \r
754         self.currentlabel = QtGui.QLabel(self)\r
755         self.currentlabel.setAlignment(\r
756             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)\r
757         self.currentlabel.setText(u"")\r
758 \r
759         xlabel = QtGui.QLabel(self)\r
760         xlabel.setAlignment(\r
761             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
762         xlabel.setText(u"&X")\r
763 \r
764         self.xbox = QtGui.QSpinBox(self)\r
765         self.xbox.setRange(-999, +999)\r
766         self.xbox.setSingleStep(1)\r
767         self.xbox.setValue(0)\r
768         xlabel.setBuddy(self.xbox)\r
769 \r
770         ylabel = QtGui.QLabel(self)\r
771         ylabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
772         ylabel.setText(u"&Y")\r
773 \r
774         self.ybox = QtGui.QSpinBox(self)\r
775         self.ybox.setRange(-999, +999)\r
776         self.ybox.setSingleStep(1)\r
777         self.ybox.setValue(0)\r
778         ylabel.setBuddy(self.ybox)\r
779 \r
780         self.buttonbox = QtGui.QDialogButtonBox(\r
781             QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)\r
782         self.buttonbox.accepted.connect(self.accept)\r
783         self.buttonbox.rejected.connect(self.reject)\r
784         self.buttonbox.button(QtGui.QDialogButtonBox.Ok).setText(u"OK")\r
785         self.buttonbox.button(QtGui.QDialogButtonBox.Cancel).setText(u"キャンセル")\r
786 \r
787         layout = QtGui.QGridLayout()\r
788         layout.addWidget(promptlabel, 0, 0, 1, 4)\r
789         layout.addWidget(self.currentlabel, 1, 0, 1, 4)\r
790         layout.addWidget(xlabel, 2, 0, 1, 1)\r
791         layout.addWidget(self.xbox, 2, 1, 1, 1)\r
792         layout.addWidget(ylabel, 2, 2, 1, 1)\r
793         layout.addWidget(self.ybox, 2, 3, 1, 1)\r
794         layout.addWidget(self.buttonbox, 3, 0, 1, 4)\r
795         self.setLayout(layout)\r
796         self.setModal(True)\r
797 \r
798     def setcurrent(self, x, y):\r
799         self.xbox.setValue(x)\r
800         self.ybox.setValue(y)\r
801         self.currentlabel.setText(u"現在の座標 ({x}, {y})".format(x=x, y=y))\r
802 \r
803     @property\r
804     def originex(self):\r
805         return self.xbox.value()\r
806 \r
807     @property\r
808     def originey(self):\r
809         return self.ybox.value()\r
810 \r
811 \r
812 class SetSizeDialog(QtGui.QDialog):\r
813     def __init__(self, parent=None):\r
814         super(SetSizeDialog, self).__init__(parent)\r
815         self.setWindowTitle(u"マップのサイズ")\r
816 \r
817         self.topbutton = QtGui.QRadioButton(self)\r
818         self.topbutton.setText(u"上(&T)")\r
819         self.topbutton.clicked.connect(self.updatewidgets)\r
820 \r
821         self.topsize = QtGui.QSpinBox(self)\r
822         self.topsize.setSingleStep(1)\r
823         self.topsize.setValue(0)\r
824         self.topsize.valueChanged.connect(self.updatewidgets)\r
825 \r
826         self.bottombutton = QtGui.QRadioButton(self)\r
827         self.bottombutton.setText(u"下(&B)")\r
828         self.bottombutton.clicked.connect(self.updatewidgets)\r
829 \r
830         self.bottomsize = QtGui.QSpinBox(self)\r
831         self.bottomsize.setSingleStep(1)\r
832         self.bottomsize.setValue(0)\r
833         self.bottomsize.valueChanged.connect(self.updatewidgets)\r
834 \r
835         self.leftbutton = QtGui.QRadioButton(self)\r
836         self.leftbutton.setText(u"左(&L)")\r
837         self.leftbutton.clicked.connect(self.updatewidgets)\r
838 \r
839         self.leftsize = QtGui.QSpinBox(self)\r
840         self.leftsize.setSingleStep(1)\r
841         self.leftsize.setValue(0)\r
842         self.leftsize.valueChanged.connect(self.updatewidgets)\r
843 \r
844         self.rightbutton = QtGui.QRadioButton(self)\r
845         self.rightbutton.setText(u"右(&R)")\r
846         self.rightbutton.clicked.connect(self.updatewidgets)\r
847 \r
848         self.rightsize = QtGui.QSpinBox(self)\r
849         self.rightsize.setSingleStep(1)\r
850         self.rightsize.setValue(0)\r
851         self.rightsize.valueChanged.connect(self.updatewidgets)\r
852 \r
853         self.sizelabel = QtGui.QLabel(self)\r
854         self.sizelabel .setAlignment(\r
855             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)\r
856         self.sizelabel.setText(u"この地点の座標を入力してください。")\r
857 \r
858         self.buttonbox = QtGui.QDialogButtonBox(\r
859         QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)\r
860         self.buttonbox.accepted.connect(self.accept)\r
861         self.buttonbox.rejected.connect(self.reject)\r
862         self.buttonbox.button(QtGui.QDialogButtonBox.Ok).setText(u"OK")\r
863         self.buttonbox.button(QtGui.QDialogButtonBox.Cancel).setText(u"キャンセル")\r
864 \r
865         verticalgroup = QtGui.QButtonGroup(self)\r
866         verticalgroup.addButton(self.topbutton)\r
867         verticalgroup.addButton(self.bottombutton)\r
868 \r
869         holizontalgroup = QtGui.QButtonGroup(self)\r
870         holizontalgroup.addButton(self.leftbutton)\r
871         holizontalgroup.addButton(self.rightbutton)\r
872 \r
873         self.topbutton.setChecked(True)\r
874         self.bottombutton.setChecked(False)\r
875         self.leftbutton.setChecked(True)\r
876         self.rightbutton.setChecked(False)\r
877 \r
878         layout = QtGui.QGridLayout(self)\r
879         layout.addWidget(self.topbutton, 0, 2, 1, 1)\r
880         layout.addWidget(self.topsize, 0, 3, 1, 1)\r
881         layout.addWidget(self.leftbutton, 1, 0, 1, 1)\r
882         layout.addWidget(self.leftsize, 1, 1, 1, 1)\r
883         layout.addWidget(self.sizelabel, 1, 2, 1, 2)\r
884         layout.addWidget(self.rightbutton, 1, 4, 1, 1)\r
885         layout.addWidget(self.rightsize, 1, 5, 1, 1)\r
886         layout.addWidget(self.bottombutton, 2, 2, 1, 1)\r
887         layout.addWidget(self.bottomsize, 2, 3, 1, 1)\r
888         layout.addWidget(self.buttonbox, 3, 0, 1, 6)\r
889         self.setLayout(layout)\r
890         self.setModal(True)\r
891 \r
892     def setoriginalsize(self, width, height):\r
893         self._width = width\r
894         self._height = height\r
895         self.topsize.setRange(-height+1, +100)\r
896         self.bottomsize.setRange(-height+1, +100)\r
897         self.leftsize.setRange(-width+1, +100)\r
898         self.rightsize.setRange(-width+1, +100)\r
899         self.updatewidgets()\r
900 \r
901     def updatewidgets(self):\r
902         dh = 0\r
903         dw = 0\r
904 \r
905         if self.topbutton.isChecked():\r
906             dh = self.topsize.value()\r
907             self.topsize.setEnabled(True)\r
908             self.bottomsize.setDisabled(True)\r
909         elif self.bottombutton.isChecked():\r
910             dh = self.bottomsize.value()\r
911             self.topsize.setDisabled(True)\r
912             self.bottomsize.setEnabled(True)\r
913         if self.leftbutton.isChecked():\r
914             dw = self.leftsize.value()\r
915             self.leftsize.setEnabled(True)\r
916             self.rightsize.setDisabled(True)\r
917         elif self.rightbutton.isChecked():\r
918             dw = self.rightsize.value()\r
919             self.leftsize.setDisabled(True)\r
920             self.rightsize.setEnabled(True)\r
921 \r
922         self.sizelabel.setText(\r
923             u"変更前のサイズ: {w1} x {h1}\n変更後のサイズ: {w2} x {h2}".format(\r
924                 w1=self._width, h1=self._height,\r
925                 w2=self._width+dw, h2=self._height+dh))\r
926 \r
927     def getsize(self):\r
928         top = 0\r
929         bottom = 0\r
930         left = 0\r
931         right = 0\r
932         if self.topbutton.isChecked():\r
933             top = self.topsize.value()\r
934         elif self.bottombutton.isChecked():\r
935             bottom = self.bottomsize.value()\r
936         if self.leftbutton.isChecked():\r
937             left = self.leftsize.value()\r
938         elif self.rightbutton.isChecked():\r
939             right = self.rightsize.value()\r
940         return (top, bottom, left, right)\r
941 \r
942 \r
943 class MapImages(object):\r
944     def __init__(self):\r
945         vtext = [u"なし", u"壁", u"扉", u"扉(→)", u"扉(←)", u"一通(→)", u"一通(←)", u"隠", u"隠(→)", u"隠(←)",]\r
946         htext = [u"なし", u"壁", u"扉", u"扉(↓)", u"扉(↑)", u"一通(↓)", u"一通(↑)", u"隠", u"隠(↓)", u"隠(↑)",]\r
947         self.wall_images = list()\r
948         self.wall_icons = list()\r
949         self.wall_texts = list()\r
950         for index in range(10):\r
951             self.wall_images.append(dict())\r
952             self.wall_icons.append(dict())\r
953             self.wall_texts.append(dict())\r
954             for direction in ["v", "h"]:\r
955                 filename = os.path.join(\r
956                     basedir(),\r
957                     u"images",\r
958                     u"wall_{direction}_{index:02}.png".format(\r
959                         direction=direction, index=index))\r
960                 self.wall_images[index][direction] = QtGui.QImage()\r
961                 self.wall_images[index][direction].load(filename)\r
962                 self.wall_icons[index][direction] = QtGui.QIcon(filename)\r
963             self.wall_texts[index]["v"] = vtext[index]\r
964             self.wall_texts[index]["h"] = htext[index]\r
965 \r
966     @property\r
967     def width(self):\r
968         return self.wall_images[0]["h"].width()\r
969 \r
970     @property\r
971     def height(self):\r
972         return self.wall_images[0]["v"].height()\r
973 \r
974     @property\r
975     def widthoffset(self):\r
976         return self.wall_images[0]["v"].width()//2\r
977 \r
978     @property\r
979     def heightoffset(self):\r
980         return self.wall_images[0]["h"].height()//2\r
981 \r
982     def wall(self, index, direction):\r
983         return self.wall_images[index][direction]\r
984 \r
985 \r
986 class MapEngine(object):\r
987     hwall = " -#WMwmHVA"\r
988     vwall = " |#PCpc=DG"\r
989 \r
990     def __init__(self, width, height, signx, signy, offsetx, offsety):\r
991         self._width = width\r
992         self._height = height\r
993         self._signx = signx\r
994         self._signy = signy\r
995         self._offsetx = offsetx\r
996         self._offsety = offsety\r
997         self.filename = None\r
998 \r
999         self.initdata()\r
1000         self.inityaml()\r
1001         self._note = dict()\r
1002 \r
1003     def initdata(self):\r
1004         width = self.width + 1\r
1005         height = self.height + 1\r
1006         self._data = self.initialdata(width, height)\r
1007 \r
1008     def initialdata(self, width, height):\r
1009         dt = list()\r
1010         for x in range(width):\r
1011             dt.append(list())\r
1012             for y in range(height):\r
1013                 dt[x].append(dict())\r
1014                 for d in ["h", "v"]:\r
1015                     dt[x][y][d] = 0\r
1016         return dt\r
1017 \r
1018     def inityaml(self):\r
1019         #yaml !python/Unicode出力抑止おまじない\r
1020         def represent_unicode(dumper, data):\r
1021             return dumper.represent_scalar("tag:yaml.org,2002:str", data)\r
1022         def construct_unicode(loader, node):\r
1023             return unicode(loader.construct_scalar(node))\r
1024         yaml.add_representer(unicode, represent_unicode)\r
1025         yaml.add_constructor("tag:yaml.org,2002:str", construct_unicode)\r
1026 \r
1027     def getdata(self, x, y, direction):\r
1028         return self._data[x][y][direction]\r
1029 \r
1030     def setdata(self, x, y, direction, value):\r
1031         self._data[x][y][direction] = value\r
1032 \r
1033     @property\r
1034     def width(self):\r
1035         return self._width\r
1036 \r
1037     @property\r
1038     def height(self):\r
1039         return self._height\r
1040 \r
1041     @property\r
1042     def signx(self):\r
1043         return self._signx\r
1044 \r
1045     @property\r
1046     def signy(self):\r
1047         return self._signy\r
1048 \r
1049     @property\r
1050     def offsetx(self):\r
1051         return self._offsetx\r
1052 \r
1053     @property\r
1054     def offsety(self):\r
1055         return self._offsety\r
1056 \r
1057     def setoffset(self, x, y):\r
1058         self._offsetx = x\r
1059         self._offsety = y\r
1060 \r
1061     def getmark(self, x, y):\r
1062         return self.unescape(self.getnote(x, y)["mark"])\r
1063 \r
1064     def getdetail(self, x, y):\r
1065         return self.unescape(self.getnote(x, y)["detail"])\r
1066 \r
1067     def getforecolor(self, x, y):\r
1068         return self.getnote(x, y)["forecolor"]\r
1069 \r
1070     def getbackcolor(self, x, y):\r
1071         return self.getnote(x, y)["backcolor"]\r
1072 \r
1073     def getnote(self, x, y):\r
1074         return self._note.get(\r
1075             self.coodtokey(x, y), {"mark":u"", "detail":u"", "forecolor":u"#000000", "backcolor":u""})\r
1076 \r
1077     def coodtokey(self, x, y):\r
1078         return u"{x:+05d}_{y:+05d}".format(x=x, y=y)\r
1079 \r
1080     def keytocood(self, key):\r
1081         return map(int, key.split("_"))\r
1082 \r
1083     def setmark(self, x, y, mark):\r
1084         note = self.getnote(x, y)\r
1085         note["mark"] = self.escape(mark)\r
1086         self.setnote(x, y, note)\r
1087 \r
1088     def setdetail(self, x, y, detail):\r
1089         note = self.getnote(x, y)\r
1090         note["detail"] = self.escape(detail)\r
1091         self.setnote(x, y, note)\r
1092 \r
1093     def setforecolor(self, x, y, color):\r
1094         note = self.getnote(x, y)\r
1095         note["forecolor"] = color\r
1096         self.setnote(x, y, note)\r
1097 \r
1098     def setbackcolor(self, x, y, color):\r
1099         note = self.getnote(x, y)\r
1100         note["backcolor"] = color\r
1101         self.setnote(x, y, note)\r
1102 \r
1103     def setnote(self, x, y, note):\r
1104         self._note[self.coodtokey(x, y)] = note\r
1105 \r
1106     def escape(self, s):\r
1107         return s.replace("\\", "\\\\").replace("\n", r"\n")\r
1108 \r
1109     def unescape(self, s):\r
1110         return s.replace(r"\n", "\n").replace("\\\\", "\\")\r
1111 \r
1112     def viewx(self, x):\r
1113         return x * self.signx + self.offsetx\r
1114 \r
1115     def viewy(self, y):\r
1116         return y * self.signy + self.offsety\r
1117 \r
1118     def worldx(self, x):\r
1119         return (x - self.offsetx) / self.signx\r
1120 \r
1121     def worldy(self, y):\r
1122         return (y - self.offsety) / self.signy\r
1123 \r
1124     def changesize(self, top, bottom, left, right):\r
1125         oldoffsetx = max(-left, 0)\r
1126         newoffsetx = max(left, 0)\r
1127         newwidth = self.width + left + right\r
1128         oldoffsety = max(-top, 0)\r
1129         newoffsety = max(top, 0)\r
1130         newheight = self.height + top + bottom\r
1131 \r
1132         newdata = self.initialdata(newwidth + 1, newheight + 1)\r
1133         newnote = dict()\r
1134         for x in range(min(self._width, newwidth) + 1):\r
1135             for y in range(min(self._height, newheight) + 1):\r
1136                 for d in ["h", "v"]:\r
1137                     newdata[x+newoffsetx][y+newoffsety][d] = self._data[x+oldoffsetx][y+oldoffsety][d]\r
1138                 newnote[self.coodtokey(x+newoffsetx, y+newoffsety)] = self.getnote(x+oldoffsetx, y+oldoffsety)\r
1139         self._width = newwidth\r
1140         self._height = newheight\r
1141         self.setoffset(self.offsetx -self.signx * left, self.offsety -self.signy * top)\r
1142         self._data = newdata\r
1143         self._note = newnote\r
1144 \r
1145     def growwall(self, x1, y1, x2, y2, eraseonly, alwaysbox):\r
1146         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1147         offsetx, offsety = self.getoffset(x1, y1, x2, y2)\r
1148 \r
1149         #delete inner walls.\r
1150         for x in range(x1, x2+stepx, stepx):\r
1151             for y in range(y1+stepy+offsety, y2+stepy+offsety, stepy):\r
1152                 self._data[x][y]["h"] = 0\r
1153         for x in range(x1+stepx+offsetx, x2+stepx+offsetx, stepx):\r
1154             for y in range(y1, y2+stepy, stepy):\r
1155                 self._data[x][y]["v"] = 0\r
1156 \r
1157         if not eraseonly:\r
1158             #draw OUTER wall if it exists.\r
1159             if alwaysbox or (x1 == x2 and y1 == y2):\r
1160                 hline = False\r
1161                 vline = False\r
1162             elif x1 == x2:\r
1163                 hline = True\r
1164                 vline = False\r
1165             elif y1 == y2:\r
1166                 hline = False\r
1167                 vline = True\r
1168             else:\r
1169                 hline = False\r
1170                 vline = False\r
1171 \r
1172             for x in range(x1, x2+stepx, stepx):\r
1173                 if not (vline and x == x1):\r
1174                     if not hline:\r
1175                         if self._data[x][y1+offsety]["h"] == 0:\r
1176                             self._data[x][y1+offsety]["h"] = 1\r
1177                     if self._data[x][y2+stepy+offsety]["h"] == 0:\r
1178                         self._data[x][y2+stepy+offsety]["h"] = 1\r
1179             for y in range(y1, y2+stepy, stepy):\r
1180                 if not (hline and y == y1):\r
1181                     if not vline:\r
1182                         if self._data[x1+offsetx][y]["v"] == 0:\r
1183                             self._data[x1+offsetx][y]["v"] = 1\r
1184                     if self._data[x2+stepx+offsetx][y]["v"] == 0:\r
1185                         self._data[x2+stepx+offsetx][y]["v"] = 1\r
1186 \r
1187     def fillbackcolor(self, x1, y1, x2, y2, backcolor):\r
1188         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1189         for x in range(x1, x2+stepx, stepx):\r
1190             for y in range(y1, y2+stepy, stepy):\r
1191                 self.setbackcolor(x, y, backcolor)\r
1192 \r
1193     def getstep(self, x1, y1, x2, y2):\r
1194         if x1 <= x2:\r
1195             stepx = 1\r
1196         else:\r
1197             stepx = -1\r
1198         if y1 <= y2:\r
1199             stepy = 1\r
1200         else:\r
1201             stepy = -1\r
1202         return (stepx, stepy)\r
1203 \r
1204     def getoffset(self, x1, y1, x2, y2):\r
1205         if x1 <= x2:\r
1206             offsetx = 0\r
1207         else:\r
1208             offsetx = 1\r
1209         if y1 <= y2:\r
1210             offsety = 0\r
1211         else:\r
1212             offsety = 1\r
1213         return (offsetx, offsety)\r
1214 \r
1215     def save(self, filename):\r
1216         dt = self.savestring()\r
1217         with codecs.open(filename, "w") as f:\r
1218             f.write(dt)\r
1219             self.filename = filename\r
1220 \r
1221     def savestring(self):\r
1222         data = dict()\r
1223         data["size"] = {"x":self.width, "y":self.height}\r
1224         data["offset"] = {"x":self.offsetx, "y":self.offsety}\r
1225         data["sign"] = {"x":self.signx, "y":self.signy}\r
1226         data["map"] = self.getmapstring()\r
1227 \r
1228         #noteは表示用に座標変換する。\r
1229         n = dict()\r
1230         for nk, ni in self._note.items():\r
1231             if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"]:\r
1232                 x, y = self.keytocood(nk)\r
1233                 n[self.coodtokey(self.viewx(x), self.viewy(y))] = ni\r
1234         data["note"] = n\r
1235         return yaml.safe_dump(data, allow_unicode=True,\r
1236                 default_flow_style=False, encoding='utf-8')\r
1237 \r
1238     def getmapstring(self):\r
1239         #出力用マップ作成\r
1240         m = []\r
1241         for y in range(self.height):\r
1242             s = [" "]\r
1243             for x in range(self.width):\r
1244                 s.append("+")\r
1245                 s.append(self.hwall[self._data[x][y]["h"]])\r
1246             s.append("+")\r
1247             s.append(" ")\r
1248             m.append("".join(s))\r
1249             s = [" "]\r
1250             for x in range(self.width):\r
1251                 s.append(self.vwall[self._data[x][y]["v"]])\r
1252                 s.append(" ")\r
1253             s.append(self.vwall[self._data[self.width][y]["v"]])\r
1254             s.append(" ")\r
1255             m.append("".join(s))\r
1256         y = self.height\r
1257         s = [" "]\r
1258         for x in range(self.width):\r
1259             s.append("+")\r
1260             s.append(self.hwall[self._data[x][y]["h"]])\r
1261         s.append("+")\r
1262         s.append(" ")\r
1263         m.append("".join(s))\r
1264         return m\r
1265 \r
1266     def load(self, filename):\r
1267         with codecs.open(filename, "r", encoding="utf-8") as f:\r
1268             st = f.read()\r
1269         self.loadfromstring(st)\r
1270         self.filename = filename\r
1271 \r
1272     def loadfromstring(self, st):\r
1273         data = yaml.safe_load(st)\r
1274 \r
1275         #基本情報\r
1276         self._width = data["size"]["x"]\r
1277         self._height = data["size"]["y"]\r
1278         self._signx = data["sign"]["x"]\r
1279         self._signy = data["sign"]["y"]\r
1280         self._offsetx = data["offset"]["x"]\r
1281         self._offsety = data["offset"]["y"]\r
1282 \r
1283         #マップ\r
1284         self.initdata()\r
1285         for y in range(self.height):\r
1286             for x in range(self.width):\r
1287                 self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1288                 self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1289         x = self.width\r
1290         for y in range(self.height):\r
1291             self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1292         y = self.height\r
1293         for x in range(self.width):\r
1294             self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1295 \r
1296         #noteは内部用に座標変換する。\r
1297         n = dict()\r
1298         for nk, ni in data["note"].items():\r
1299             if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"] != "":\r
1300                 x, y = self.keytocood(nk)\r
1301                 n[self.coodtokey(self.worldx(x), self.worldy(y))] = ni\r
1302 \r
1303         self._note = n\r
1304 \r
1305 \r
1306 class UndoManager(QtCore.QObject):\r
1307     MAX_UNDO_COUNT = 128\r
1308     changed = QtCore.Signal(bool, bool)\r
1309 \r
1310     def __init__(self):\r
1311         super(UndoManager, self).__init__()\r
1312         self.clear()\r
1313 \r
1314     def clear(self):\r
1315         self._undo = [None for x in range(self.MAX_UNDO_COUNT)]\r
1316         self._index = 0\r
1317         self._undocount = 0\r
1318         self._commited = True\r
1319         self.changed.emit(self.canundo, self.canredo)\r
1320 \r
1321     def init(self, data):\r
1322         self.clear()\r
1323         self.save(data)\r
1324         self._commited = True\r
1325 \r
1326     def save(self, obj):\r
1327         if self._index >= self.MAX_UNDO_COUNT:\r
1328             self._undo = self._undo[1:]\r
1329             self._index -= 1\r
1330             self._undo.append(None)\r
1331         self._undo[self._index] = obj\r
1332         self._index += 1\r
1333         self._undocount = 0\r
1334         self._commited = False\r
1335         self.changed.emit(self.canundo, self.canredo)\r
1336 \r
1337     def undo(self):\r
1338         self._index -= 1\r
1339         self._undocount += 1\r
1340         self._commited = False\r
1341         self.changed.emit(self.canundo, self.canredo)\r
1342         return self._undo[self._index - 1]\r
1343 \r
1344     def redo(self):\r
1345         self._index += 1\r
1346         self._undocount -= 1\r
1347         self._commited = False\r
1348         self.changed.emit(self.canundo, self.canredo)\r
1349         return self._undo[self._index - 1]\r
1350 \r
1351     def commit(self):\r
1352         self._commited = True\r
1353 \r
1354     @property\r
1355     def canundo(self):\r
1356         return (self._index > 1)\r
1357 \r
1358     @property\r
1359     def canredo(self):\r
1360         return (self._undocount > 0)\r
1361 \r
1362     @property\r
1363     def commited(self):\r
1364         return self._commited\r
1365 \r
1366 \r
1367 class PydunColorDialog(QtGui.QColorDialog):\r
1368     def __init__(self, parent, config):\r
1369         super(PydunColorDialog, self).__init__(parent)\r
1370         for index in range(self.customCount()):\r
1371             self.setCustomColor(index,\r
1372                 getcolorfromstring(\r
1373                     config.get(index, "#FFFFFF")).rgb())\r
1374         self.updateconfig()\r
1375 \r
1376     def updateconfig(self):\r
1377         self._config = dict()\r
1378         for index in range(self.customCount()):\r
1379             self._config[index] = getcolorstring(\r
1380                 QtGui.QColor.fromRgb(self.customColor(index)))\r
1381 \r
1382     def exec_(self):\r
1383         super(PydunColorDialog, self).exec_()\r
1384         self.updateconfig()\r
1385 \r
1386     @property\r
1387     def config(self):\r
1388         return self._config\r
1389 \r
1390 \r
1391 class PydunAskSaveDialog(QtGui.QMessageBox):\r
1392     def __init__(self, parent, filename):\r
1393         super(PydunAskSaveDialog, self).__init__(parent)\r
1394         self.setText(u"{filename} への変更を保存しますか?".format(filename=filename))\r
1395         self.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel)\r
1396         self.setDefaultButton(QtGui.QMessageBox.Save)\r
1397         self.button(QtGui.QMessageBox.Save).setText(u"保存する(&S)")\r
1398         self.button(QtGui.QMessageBox.Discard).setText(u"保存しない(&N)")\r
1399         self.button(QtGui.QMessageBox.Cancel).setText(u"キャンセル")\r
1400 \r
1401 \r
1402 def getcolorstring(color):\r
1403     return "#{r:02x}{g:02x}{b:02x}".format(r=color.red(), g=color.green(), b=color.blue())\r
1404 \r
1405 def getcolorfromstring(colorstring):\r
1406     return QtGui.QColor.fromRgb(\r
1407         int(colorstring[1:3], 16),\r
1408         int(colorstring[3:5], 16),\r
1409         int(colorstring[5:7], 16))\r
1410 \r
1411 def basedir():\r
1412     return os.path.dirname(os.path.abspath(unicode(sys.argv[0], locale.getpreferredencoding())))\r
1413 \r
1414 def getlatestversion():\r
1415     try:\r
1416         rss = urllib.urlopen(projectrssurl)\r
1417         rssstring = rss.read()\r
1418         rsstree = xml.etree.ElementTree.fromstring(rssstring)\r
1419         item = rsstree.find("channel/item/title")\r
1420         ver = (item.text.split(" "))[2]\r
1421         rss.close()\r
1422     except:\r
1423         ver = projectversion\r
1424     return ver\r
1425 \r
1426 \r
1427 def main():\r
1428     loadconfig()\r
1429     app = QtGui.QApplication(sys.argv)\r
1430     mainWin = MainWindow()\r
1431     app.installEventFilter(mainWin.centralWidget().mapframe)\r
1432     mainWin.show()\r
1433     sys.exit(app.exec_())\r
1434 \r
1435 def loadconfig():\r
1436     global config\r
1437     global configfilename\r
1438     configfilename = os.path.join(\r
1439         basedir(),\r
1440         u"Pydun.config")\r
1441     try:\r
1442         with open(configfilename, "r") as f:\r
1443             config = yaml.safe_load(f)\r
1444     except:\r
1445         config = dict()\r
1446 \r
1447 if __name__ == '__main__':\r
1448     main()\r