OSDN Git Service

0ba403eb10db83bee36368d8ba3f8f0ed5dce280
[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(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         self.wall_images = list()\r
946         self.wall_icons = list()\r
947         for index in range(10):\r
948             self.wall_images.append(dict())\r
949             self.wall_icons.append(dict())\r
950             for direction in ["v", "h"]:\r
951                 filename = os.path.join(\r
952                     basedir(),\r
953                     u"images",\r
954                     u"wall_{direction}_{index:02}.png".format(\r
955                         direction=direction, index=index))\r
956                 self.wall_images[index][direction] = QtGui.QImage()\r
957                 self.wall_images[index][direction].load(filename)\r
958                 self.wall_icons[index][direction] = QtGui.QIcon(filename)\r
959 \r
960     @property\r
961     def width(self):\r
962         return self.wall_images[0]["h"].width()\r
963 \r
964     @property\r
965     def height(self):\r
966         return self.wall_images[0]["v"].height()\r
967 \r
968     @property\r
969     def widthoffset(self):\r
970         return self.wall_images[0]["v"].width()//2\r
971 \r
972     @property\r
973     def heightoffset(self):\r
974         return self.wall_images[0]["h"].height()//2\r
975 \r
976     def wall(self, index, direction):\r
977         return self.wall_images[index][direction]\r
978 \r
979 \r
980 class MapEngine(object):\r
981     hwall = " -#WMwmHVA"\r
982     vwall = " |#PCpc=DG"\r
983 \r
984     def __init__(self, width, height, signx, signy, offsetx, offsety):\r
985         self._width = width\r
986         self._height = height\r
987         self._signx = signx\r
988         self._signy = signy\r
989         self._offsetx = offsetx\r
990         self._offsety = offsety\r
991         self.filename = None\r
992 \r
993         self.initdata()\r
994         self.inityaml()\r
995         self._note = dict()\r
996 \r
997     def initdata(self):\r
998         width = self.width + 1\r
999         height = self.height + 1\r
1000         self._data = self.initialdata(width, height)\r
1001 \r
1002     def initialdata(self, width, height):\r
1003         dt = list()\r
1004         for x in range(width):\r
1005             dt.append(list())\r
1006             for y in range(height):\r
1007                 dt[x].append(dict())\r
1008                 for d in ["h", "v"]:\r
1009                     dt[x][y][d] = 0\r
1010         return dt\r
1011 \r
1012     def inityaml(self):\r
1013         #yaml !python/Unicode出力抑止おまじない\r
1014         def represent_unicode(dumper, data):\r
1015             return dumper.represent_scalar("tag:yaml.org,2002:str", data)\r
1016         def construct_unicode(loader, node):\r
1017             return unicode(loader.construct_scalar(node))\r
1018         yaml.add_representer(unicode, represent_unicode)\r
1019         yaml.add_constructor("tag:yaml.org,2002:str", construct_unicode)\r
1020 \r
1021     def getdata(self, x, y, direction):\r
1022         return self._data[x][y][direction]\r
1023 \r
1024     def setdata(self, x, y, direction, value):\r
1025         self._data[x][y][direction] = value\r
1026 \r
1027     @property\r
1028     def width(self):\r
1029         return self._width\r
1030 \r
1031     @property\r
1032     def height(self):\r
1033         return self._height\r
1034 \r
1035     @property\r
1036     def signx(self):\r
1037         return self._signx\r
1038 \r
1039     @property\r
1040     def signy(self):\r
1041         return self._signy\r
1042 \r
1043     @property\r
1044     def offsetx(self):\r
1045         return self._offsetx\r
1046 \r
1047     @property\r
1048     def offsety(self):\r
1049         return self._offsety\r
1050 \r
1051     def setoffset(self, x, y):\r
1052         self._offsetx = x\r
1053         self._offsety = y\r
1054 \r
1055     def getmark(self, x, y):\r
1056         return self.unescape(self.getnote(x, y)["mark"])\r
1057 \r
1058     def getdetail(self, x, y):\r
1059         return self.unescape(self.getnote(x, y)["detail"])\r
1060 \r
1061     def getforecolor(self, x, y):\r
1062         return self.getnote(x, y)["forecolor"]\r
1063 \r
1064     def getbackcolor(self, x, y):\r
1065         return self.getnote(x, y)["backcolor"]\r
1066 \r
1067     def getnote(self, x, y):\r
1068         return self._note.get(\r
1069             self.coodtokey(x, y), {"mark":u"", "detail":u"", "forecolor":u"#000000", "backcolor":u""})\r
1070 \r
1071     def coodtokey(self, x, y):\r
1072         return u"{x:+05d}_{y:+05d}".format(x=x, y=y)\r
1073 \r
1074     def keytocood(self, key):\r
1075         return map(int, key.split("_"))\r
1076 \r
1077     def setmark(self, x, y, mark):\r
1078         note = self.getnote(x, y)\r
1079         note["mark"] = self.escape(mark)\r
1080         self.setnote(x, y, note)\r
1081 \r
1082     def setdetail(self, x, y, detail):\r
1083         note = self.getnote(x, y)\r
1084         note["detail"] = self.escape(detail)\r
1085         self.setnote(x, y, note)\r
1086 \r
1087     def setforecolor(self, x, y, color):\r
1088         note = self.getnote(x, y)\r
1089         note["forecolor"] = color\r
1090         self.setnote(x, y, note)\r
1091 \r
1092     def setbackcolor(self, x, y, color):\r
1093         note = self.getnote(x, y)\r
1094         note["backcolor"] = color\r
1095         self.setnote(x, y, note)\r
1096 \r
1097     def setnote(self, x, y, note):\r
1098         self._note[self.coodtokey(x, y)] = note\r
1099 \r
1100     def escape(self, s):\r
1101         return s.replace("\\", "\\\\").replace("\n", r"\n")\r
1102 \r
1103     def unescape(self, s):\r
1104         return s.replace(r"\n", "\n").replace("\\\\", "\\")\r
1105 \r
1106     def viewx(self, x):\r
1107         return x * self.signx + self.offsetx\r
1108 \r
1109     def viewy(self, y):\r
1110         return y * self.signy + self.offsety\r
1111 \r
1112     def worldx(self, x):\r
1113         return (x - self.offsetx) / self.signx\r
1114 \r
1115     def worldy(self, y):\r
1116         return (y - self.offsety) / self.signy\r
1117 \r
1118     def changesize(self, top, bottom, left, right):\r
1119         oldoffsetx = max(-left, 0)\r
1120         newoffsetx = max(left, 0)\r
1121         newwidth = self.width + left + right\r
1122         oldoffsety = max(-top, 0)\r
1123         newoffsety = max(top, 0)\r
1124         newheight = self.height + top + bottom\r
1125 \r
1126         newdata = self.initialdata(newwidth + 1, newheight + 1)\r
1127         newnote = dict()\r
1128         for x in range(min(self._width, newwidth) + 1):\r
1129             for y in range(min(self._height, newheight) + 1):\r
1130                 for d in ["h", "v"]:\r
1131                     newdata[x+newoffsetx][y+newoffsety][d] = self._data[x+oldoffsetx][y+oldoffsety][d]\r
1132                 newnote[self.coodtokey(x+newoffsetx, y+newoffsety)] = self.getnote(x+oldoffsetx, y+oldoffsety)\r
1133         self._width = newwidth\r
1134         self._height = newheight\r
1135         self.setoffset(self.offsetx -self.signx * left, self.offsety -self.signy * top)\r
1136         self._data = newdata\r
1137         self._note = newnote\r
1138 \r
1139     def growwall(self, x1, y1, x2, y2, eraseonly, alwaysbox):\r
1140         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1141         offsetx, offsety = self.getoffset(x1, y1, x2, y2)\r
1142 \r
1143         #delete inner walls.\r
1144         for x in range(x1, x2+stepx, stepx):\r
1145             for y in range(y1+stepy+offsety, y2+stepy+offsety, stepy):\r
1146                 self._data[x][y]["h"] = 0\r
1147         for x in range(x1+stepx+offsetx, x2+stepx+offsetx, stepx):\r
1148             for y in range(y1, y2+stepy, stepy):\r
1149                 self._data[x][y]["v"] = 0\r
1150 \r
1151         if not eraseonly:\r
1152             #draw OUTER wall if it exists.\r
1153             if alwaysbox or (x1 == x2 and y1 == y2):\r
1154                 hline = False\r
1155                 vline = False\r
1156             elif x1 == x2:\r
1157                 hline = True\r
1158                 vline = False\r
1159             elif y1 == y2:\r
1160                 hline = False\r
1161                 vline = True\r
1162             else:\r
1163                 hline = False\r
1164                 vline = False\r
1165 \r
1166             for x in range(x1, x2+stepx, stepx):\r
1167                 if not (vline and x == x1):\r
1168                     if not hline:\r
1169                         if self._data[x][y1+offsety]["h"] == 0:\r
1170                             self._data[x][y1+offsety]["h"] = 1\r
1171                     if self._data[x][y2+stepy+offsety]["h"] == 0:\r
1172                         self._data[x][y2+stepy+offsety]["h"] = 1\r
1173             for y in range(y1, y2+stepy, stepy):\r
1174                 if not (hline and y == y1):\r
1175                     if not vline:\r
1176                         if self._data[x1+offsetx][y]["v"] == 0:\r
1177                             self._data[x1+offsetx][y]["v"] = 1\r
1178                     if self._data[x2+stepx+offsetx][y]["v"] == 0:\r
1179                         self._data[x2+stepx+offsetx][y]["v"] = 1\r
1180 \r
1181     def fillbackcolor(self, x1, y1, x2, y2, backcolor):\r
1182         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1183         for x in range(x1, x2+stepx, stepx):\r
1184             for y in range(y1, y2+stepy, stepy):\r
1185                 self.setbackcolor(x, y, backcolor)\r
1186 \r
1187     def getstep(self, x1, y1, x2, y2):\r
1188         if x1 <= x2:\r
1189             stepx = 1\r
1190         else:\r
1191             stepx = -1\r
1192         if y1 <= y2:\r
1193             stepy = 1\r
1194         else:\r
1195             stepy = -1\r
1196         return (stepx, stepy)\r
1197 \r
1198     def getoffset(self, x1, y1, x2, y2):\r
1199         if x1 <= x2:\r
1200             offsetx = 0\r
1201         else:\r
1202             offsetx = 1\r
1203         if y1 <= y2:\r
1204             offsety = 0\r
1205         else:\r
1206             offsety = 1\r
1207         return (offsetx, offsety)\r
1208 \r
1209     def save(self, filename):\r
1210         dt = self.savestring()\r
1211         with codecs.open(filename, "w") as f:\r
1212             f.write(dt)\r
1213             self.filename = filename\r
1214 \r
1215     def savestring(self):\r
1216         data = dict()\r
1217         data["size"] = {"x":self.width, "y":self.height}\r
1218         data["offset"] = {"x":self.offsetx, "y":self.offsety}\r
1219         data["sign"] = {"x":self.signx, "y":self.signy}\r
1220         data["map"] = self.getmapstring()\r
1221 \r
1222         #noteは表示用に座標変換する。\r
1223         n = dict()\r
1224         for nk, ni in self._note.items():\r
1225             if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"]:\r
1226                 x, y = self.keytocood(nk)\r
1227                 n[self.coodtokey(self.viewx(x), self.viewy(y))] = ni\r
1228         data["note"] = n\r
1229         return yaml.safe_dump(data, allow_unicode=True,\r
1230                 default_flow_style=False, encoding='utf-8')\r
1231 \r
1232     def getmapstring(self):\r
1233         #出力用マップ作成\r
1234         m = []\r
1235         for y in range(self.height):\r
1236             s = [" "]\r
1237             for x in range(self.width):\r
1238                 s.append("+")\r
1239                 s.append(self.hwall[self._data[x][y]["h"]])\r
1240             s.append("+")\r
1241             s.append(" ")\r
1242             m.append("".join(s))\r
1243             s = [" "]\r
1244             for x in range(self.width):\r
1245                 s.append(self.vwall[self._data[x][y]["v"]])\r
1246                 s.append(" ")\r
1247             s.append(self.vwall[self._data[self.width][y]["v"]])\r
1248             s.append(" ")\r
1249             m.append("".join(s))\r
1250         y = self.height\r
1251         s = [" "]\r
1252         for x in range(self.width):\r
1253             s.append("+")\r
1254             s.append(self.hwall[self._data[x][y]["h"]])\r
1255         s.append("+")\r
1256         s.append(" ")\r
1257         m.append("".join(s))\r
1258         return m\r
1259 \r
1260     def load(self, filename):\r
1261         with codecs.open(filename, "r", encoding="utf-8") as f:\r
1262             st = f.read()\r
1263         self.loadfromstring(st)\r
1264         self.filename = filename\r
1265 \r
1266     def loadfromstring(self, st):\r
1267         data = yaml.safe_load(st)\r
1268 \r
1269         #基本情報\r
1270         self._width = data["size"]["x"]\r
1271         self._height = data["size"]["y"]\r
1272         self._signx = data["sign"]["x"]\r
1273         self._signy = data["sign"]["y"]\r
1274         self._offsetx = data["offset"]["x"]\r
1275         self._offsety = data["offset"]["y"]\r
1276 \r
1277         #マップ\r
1278         self.initdata()\r
1279         for y in range(self.height):\r
1280             for x in range(self.width):\r
1281                 self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1282                 self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1283         x = self.width\r
1284         for y in range(self.height):\r
1285             self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1286         y = self.height\r
1287         for x in range(self.width):\r
1288             self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1289 \r
1290         #noteは内部用に座標変換する。\r
1291         n = dict()\r
1292         for nk, ni in data["note"].items():\r
1293             if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"] != "":\r
1294                 x, y = self.keytocood(nk)\r
1295                 n[self.coodtokey(self.worldx(x), self.worldy(y))] = ni\r
1296 \r
1297         self._note = n\r
1298 \r
1299 \r
1300 class UndoManager(QtCore.QObject):\r
1301     MAX_UNDO_COUNT = 128\r
1302     changed = QtCore.Signal(bool, bool)\r
1303 \r
1304     def __init__(self):\r
1305         super(UndoManager, self).__init__()\r
1306         self.clear()\r
1307 \r
1308     def clear(self):\r
1309         self._undo = [None for x in range(self.MAX_UNDO_COUNT)]\r
1310         self._index = 0\r
1311         self._undocount = 0\r
1312         self._commited = True\r
1313         self.changed.emit(self.canundo, self.canredo)\r
1314 \r
1315     def init(self, data):\r
1316         self.clear()\r
1317         self.save(data)\r
1318         self._commited = True\r
1319 \r
1320     def save(self, obj):\r
1321         if self._index >= self.MAX_UNDO_COUNT:\r
1322             self._undo = self._undo[1:]\r
1323             self._index -= 1\r
1324             self._undo.append(None)\r
1325         self._undo[self._index] = obj\r
1326         self._index += 1\r
1327         self._undocount = 0\r
1328         self._commited = False\r
1329         self.changed.emit(self.canundo, self.canredo)\r
1330 \r
1331     def undo(self):\r
1332         self._index -= 1\r
1333         self._undocount += 1\r
1334         self._commited = False\r
1335         self.changed.emit(self.canundo, self.canredo)\r
1336         return self._undo[self._index - 1]\r
1337 \r
1338     def redo(self):\r
1339         self._index += 1\r
1340         self._undocount -= 1\r
1341         self._commited = False\r
1342         self.changed.emit(self.canundo, self.canredo)\r
1343         return self._undo[self._index - 1]\r
1344 \r
1345     def commit(self):\r
1346         self._commited = True\r
1347 \r
1348     @property\r
1349     def canundo(self):\r
1350         return (self._index > 1)\r
1351 \r
1352     @property\r
1353     def canredo(self):\r
1354         return (self._undocount > 0)\r
1355 \r
1356     @property\r
1357     def commited(self):\r
1358         return self._commited\r
1359 \r
1360 \r
1361 class PydunColorDialog(QtGui.QColorDialog):\r
1362     def __init__(self, parent, config):\r
1363         super(PydunColorDialog, self).__init__(parent)\r
1364         for index in range(self.customCount()):\r
1365             self.setCustomColor(index,\r
1366                 getcolorfromstring(\r
1367                     config.get(index, "#FFFFFF")).rgb())\r
1368         self.updateconfig()\r
1369 \r
1370     def updateconfig(self):\r
1371         self._config = dict()\r
1372         for index in range(self.customCount()):\r
1373             self._config[index] = getcolorstring(\r
1374                 QtGui.QColor.fromRgb(self.customColor(index)))\r
1375 \r
1376     def exec_(self):\r
1377         super(PydunColorDialog, self).exec_()\r
1378         self.updateconfig()\r
1379 \r
1380     @property\r
1381     def config(self):\r
1382         return self._config\r
1383 \r
1384 \r
1385 class PydunAskSaveDialog(QtGui.QMessageBox):\r
1386     def __init__(self, parent, filename):\r
1387         super(PydunAskSaveDialog, self).__init__(parent)\r
1388         self.setText(u"{filename} への変更を保存しますか?".format(filename=filename))\r
1389         self.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel)\r
1390         self.setDefaultButton(QtGui.QMessageBox.Save)\r
1391         self.button(QtGui.QMessageBox.Save).setText(u"保存する(&S)")\r
1392         self.button(QtGui.QMessageBox.Discard).setText(u"保存しない(&N)")\r
1393         self.button(QtGui.QMessageBox.Cancel).setText(u"キャンセル")\r
1394 \r
1395 \r
1396 def getcolorstring(color):\r
1397     return "#{r:02x}{g:02x}{b:02x}".format(r=color.red(), g=color.green(), b=color.blue())\r
1398 \r
1399 def getcolorfromstring(colorstring):\r
1400     return QtGui.QColor.fromRgb(\r
1401         int(colorstring[1:3], 16),\r
1402         int(colorstring[3:5], 16),\r
1403         int(colorstring[5:7], 16))\r
1404 \r
1405 def basedir():\r
1406     return os.path.dirname(os.path.abspath(unicode(sys.argv[0], locale.getpreferredencoding())))\r
1407 \r
1408 def getlatestversion():\r
1409     try:\r
1410         rss = urllib.urlopen(projectrssurl)\r
1411         rssstring = rss.read()\r
1412         rsstree = xml.etree.ElementTree.fromstring(rssstring)\r
1413         item = rsstree.find("channel/item/title")\r
1414         ver = (item.text.split(" "))[2]\r
1415         rss.close()\r
1416     except:\r
1417         ver = projectversion\r
1418     return ver\r
1419 \r
1420 \r
1421 def main():\r
1422     loadconfig()\r
1423     app = QtGui.QApplication(sys.argv)\r
1424     mainWin = MainWindow()\r
1425     app.installEventFilter(mainWin.centralWidget().mapframe)\r
1426     mainWin.show()\r
1427     sys.exit(app.exec_())\r
1428 \r
1429 def loadconfig():\r
1430     global config\r
1431     global configfilename\r
1432     configfilename = os.path.join(\r
1433         basedir(),\r
1434         u"Pydun.config")\r
1435     try:\r
1436         with open(configfilename, "r") as f:\r
1437             config = yaml.safe_load(f)\r
1438     except:\r
1439         config = dict()\r
1440 \r
1441 if __name__ == '__main__':\r
1442     main()\r