OSDN Git Service

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