OSDN Git Service

1.2.1
[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 PySide2 import QtCore, QtGui, QtWidgets\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://ja.osdn.net/projects/pydun/"\r
27 projectrssurl = "http://ja.osdn.net/projects/pydun/releases/rss"\r
28 projectversion = "1.2.1"\r
29 defaultfontname = "Yu Gothic UI"\r
30 \r
31 class MainWindow(QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.QAction("マップのサイズ(&S)", self)\r
103         setmapsizeact.triggered.connect(self.setmapsize_triggered)\r
104         editmenu.addAction(setmapsizeact)\r
105         setorigineact = QtWidgets.QAction("座標設定(&O)", self)\r
106         setorigineact.triggered.connect(self.setorigine_triggered)\r
107         editmenu.addAction(setorigineact)\r
108         wallmenustringact = QtWidgets.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 = QtWidgets.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 = QtWidgets.QAction("プロジェクトのWebサイト(&W)", self)\r
121         projectact.triggered.connect(self.project_triggered)\r
122         helpmenu.addAction(projectact)\r
123         aboutact = QtWidgets.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 == QtWidgets.QMessageBox.Cancel:\r
171                 return False\r
172             elif ret == QtWidgets.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 = QtWidgets.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 = QtWidgets.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             QtWidgets.QMessageBox.information(\r
271                 self, title, "座標設定を中止します。", QtWidgets.QMessageBox.Ok)\r
272             self.mainframe.mapframe.setoriginemode = False\r
273         else:\r
274             if QtWidgets.QMessageBox.Ok == QtWidgets.QMessageBox.information(\r
275                 self, title, "基準にする地点をクリックしてください。",\r
276                 (QtWidgets.QMessageBox.Ok| QtWidgets.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() == QtWidgets.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         QtWidgets.QMessageBox.information(\r
295                 self, "壁メニューに文字を表示する", "表示の切替は再起動後に有効になります。",\r
296                 (QtWidgets.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         QtWidgets.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, PySide2, PyYAML "\r
319         "これらの作成者に深く感謝いたします。</p>"\r
320         "<p>詳細はLICENCE.txtを参照してください。</p>")\r
321 \r
322 \r
323 class MainFrame(QtWidgets.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         f = QtGui.QFont(defaultfontname, 9)\r
330         self.setFont(f)\r
331 \r
332         self.mapframe = MapFrame(self)\r
333         scrollarea = QtWidgets.QScrollArea(self)\r
334         scrollarea.setWidget(self.mapframe)\r
335 \r
336         self.detail = QtWidgets.QLabel(self)\r
337         self.detail.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)\r
338         self.detail.setText("")\r
339         self.detail.setMaximumHeight(100)\r
340         self.detail.setMinimumHeight(100)\r
341 \r
342         self.boxdrawbutton = QtWidgets.QRadioButton(self)\r
343         self.boxdrawbutton.setText("ボックス形式で壁を描画(&B)")\r
344         self.boxdrawbutton.setChecked(True)\r
345         self.boxdrawbutton.setSizePolicy(\r
346             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
347 \r
348         self.growdrawbutton = QtWidgets.QRadioButton(self)\r
349         self.growdrawbutton.setText("足跡形式で壁を描画(&G)")\r
350         self.growdrawbutton.setChecked(False)\r
351         self.growdrawbutton.setSizePolicy(\r
352             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
353 \r
354         self.backcolorbutton = QtWidgets.QRadioButton(self)\r
355         self.backcolorbutton.setText("背景色(&C)")\r
356         self.backcolorbutton.setChecked(False)\r
357         self.backcolorbutton.setSizePolicy(\r
358             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
359 \r
360         self.setbackcolorbutton = QtWidgets.QPushButton(self)\r
361         self.setbackcolorbutton.setText("背景色を設定(&S)...")\r
362         self.setbackcolorbutton.setSizePolicy(\r
363             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
364 \r
365         self.backcolorbox = ColorBox(self)\r
366         self.backcolorbox.setMinimumSize(30, 30)\r
367         self.backcolorbox.setSizePolicy(\r
368             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
369 \r
370         latestversion = getlatestversion()\r
371         if latestversion != projectversion:\r
372             self.update = QtWidgets.QLabel(self)\r
373             self.update.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)\r
374             self.update.setText("<a href='{url}'>最新のPydun({ver})がダウンロードできます。</a>".format(url=projecturl, ver=latestversion))\r
375             self.update.setOpenExternalLinks(True)\r
376 \r
377         layout = QtWidgets.QGridLayout(self)\r
378         layout.addWidget(scrollarea, 0, 0, 1, 3)\r
379         layout.addWidget(self.detail, 1, 0, 4, 1)\r
380         layout.addWidget(self.boxdrawbutton, 1, 1, 1, 2)\r
381         layout.addWidget(self.growdrawbutton, 2, 1, 1, 2)\r
382         layout.addWidget(self.backcolorbutton, 3, 1, 1, 2)\r
383         layout.addWidget(self.setbackcolorbutton, 4, 1, 1, 1)\r
384         layout.addWidget(self.backcolorbox, 4, 2, 1, 1)\r
385         if latestversion != projectversion:\r
386             layout.addWidget(self.update, 5, 0, 1, 3)\r
387 \r
388         self.setLayout(layout)\r
389 \r
390         self.h_wall_menu = self.create_wall_menu("h")\r
391         self.v_wall_menu = self.create_wall_menu("v")\r
392 \r
393         self.mapframe.mouse_moved.connect(self.mouse_moved)\r
394         self.mapframe.mouse_released.connect(self.mouse_released)\r
395         self.mapframe.mouse_drag_released.connect(self.mouse_drag_released)\r
396         self.create_wall_menu_triggered_signal.connect(\r
397             self.create_wall_menu_triggered)\r
398         self.setbackcolorbutton.clicked.connect(\r
399             self.setbackcolorbutton_clicked)\r
400 \r
401     def create_wall_menu(self, direction):\r
402         menu = QtWidgets.QMenu(self)\r
403         for idx, img in enumerate(_mapimages.wall_icons):\r
404             act = QtWidgets.QAction(_mapimages.wall_texts[idx][direction], self)\r
405             act.setIcon(img[direction])\r
406 \r
407             def triggerd(idx):\r
408                 def emit():\r
409                     self.create_wall_menu_triggered_signal.emit(menu.x, menu.y, direction, idx)\r
410                 return emit\r
411 \r
412             act.triggered.connect(triggerd(idx))\r
413             menu.addAction(act)\r
414         return menu\r
415 \r
416     @QtCore.Slot(int, int, int)\r
417     def mouse_moved(self, x=0, y=0, b=QtCore.Qt.MouseButton.NoButton):\r
418         cood = "({x}, {y})\n".format(x=_mapengine.viewx(x), y=_mapengine.viewy(y))\r
419         self.detail.setText(cood + _mapengine.getdetail(x, y))\r
420         self.mapframe.repaint()\r
421 \r
422     @QtCore.Slot(int, int, int, int, int)\r
423     def mouse_drag_released(self, x1, y1, x2, y2, eraseonly):\r
424         if self.boxdrawbutton.isChecked():\r
425             _mapengine.growwall(x1, y1, x2, y2, eraseonly, True)\r
426         elif self.growdrawbutton.isChecked():\r
427             _mapengine.growwall(x1, y1, x2, y2, eraseonly, False)\r
428         elif self.backcolorbutton.isChecked():\r
429             if eraseonly:\r
430                 backcolor = ""\r
431             else:\r
432                 backcolor = getcolorstring(self.backcolorbox.color)\r
433             _mapengine.fillbackcolor(x1, y1, x2, y2, backcolor)\r
434         _undomanager.save(_mapengine.savestring())\r
435         self.mapframe.repaint()\r
436 \r
437     @QtCore.Slot(int, int, str)\r
438     def mouse_released(self, x1, y1, direction):\r
439         #座標設定モード\r
440         if self.mapframe.setoriginemode:\r
441             dlg = SetOrigineDialog(self)\r
442             dlg.setcurrent(_mapengine.viewx(x1), _mapengine.viewy(y1), _mapengine.signx, _mapengine.signy)\r
443             dlg.exec_() #showでは処理がとまらない。\r
444             if dlg.result() == QtWidgets.QDialog.Accepted:\r
445                 _mapengine.setsign(dlg.signx, dlg.signy)\r
446                 _mapengine.setoffset(\r
447                     dlg.originex - _mapengine.viewx(x1) + _mapengine.offsetx,\r
448                     dlg.originey - _mapengine.viewy(y1) + _mapengine.offsety\r
449                 )\r
450                 _undomanager.save(_mapengine.savestring())\r
451             self.mapframe.setoriginemode = False\r
452             return\r
453 \r
454         if direction == "c":\r
455             dlg = DetailDialog(self)\r
456             dlg.setvalue(_mapengine.viewx(x1), _mapengine.viewy(y1),\r
457                 _mapengine.getmark(x1, y1), _mapengine.getdetail(x1, y1),\r
458                 getcolorfromstring(_mapengine.getforecolor(x1, y1)))\r
459             dlg.exec_() #showでは処理がとまらない。\r
460             if dlg.result() == QtWidgets.QDialog.Accepted:\r
461                 forecolor = getcolorstring(dlg.forecolorbox.color)\r
462                 _mapengine.setmark(x1, y1, dlg.marktext.text())\r
463                 _mapengine.setdetail(x1, y1, dlg.detailtext.toPlainText())\r
464                 _mapengine.setforecolor(x1, y1, forecolor)\r
465                 _undomanager.save(_mapengine.savestring())\r
466                 self.mapframe.repaint()\r
467             else:\r
468                 pass\r
469         else:\r
470             if direction == "h":\r
471                 menu = self.h_wall_menu\r
472             elif direction == "v":\r
473                 menu = self.v_wall_menu\r
474             menu.x = x1\r
475             menu.y = y1\r
476             menu.popup(QtGui.QCursor.pos())\r
477 \r
478     @QtCore.Slot(int, int, str, int)\r
479     def create_wall_menu_triggered(self, x1, y1, direction, wall):\r
480         _mapengine.setdata(x1, y1, direction, wall)\r
481         _undomanager.save(_mapengine.savestring())\r
482         self.mapframe.repaint()\r
483 \r
484     @QtCore.Slot()\r
485     def setbackcolorbutton_clicked(self):\r
486         global config\r
487         dlg = PydunColorDialog(self, config.get("customColor", dict()))\r
488         dlg.setCurrentColor(self.backcolorbox.color)\r
489         dlg.exec_()\r
490         config["customColor"] = dlg.config\r
491         if dlg.result() == QtWidgets.QDialog.Accepted:\r
492             self.backcolorbox.color = dlg.currentColor()\r
493             self.backcolorbutton.setChecked(True)\r
494 \r
495 \r
496 class MapFrame(QtWidgets.QFrame):\r
497     mouse_moved = QtCore.Signal(int, int, int)\r
498     mouse_released = QtCore.Signal(int, int, str)\r
499     mouse_drag_released = QtCore.Signal(int, int, int, int, int)\r
500     global _mapengine\r
501     global _mapimages\r
502 \r
503     def __init__(self, parent=None):\r
504         super(MapFrame, self).__init__(parent)\r
505         self.setFont = QtGui.QFont(defaultfontname, 9)\r
506         self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
507         self._x1 = 0\r
508         self._y1 = 0\r
509         self._x2 = 0\r
510         self._y2 = 0\r
511         self._px1 = 0\r
512         self._py1 = 0\r
513         self._px2 = 0\r
514         self._py2 = 0\r
515         self._dragging = False\r
516         self.setoriginemode = False\r
517         self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
518         self.resize(\r
519             _mapimages.width * (_mapengine.width) + _mapimages.widthoffset * 2,\r
520             _mapimages.height * (_mapengine.height) + _mapimages.heightoffset * 2\r
521         )\r
522 \r
523     def paintEvent(self, event):\r
524         painter = QtGui.QPainter(self)\r
525         painter.fillRect(0, 0, self.width(), self.height(), QtGui.QColor(255, 255, 255))\r
526         w = _mapimages.width - 1\r
527         v = _mapimages.height - 1\r
528         ho = _mapimages.heightoffset\r
529         wo = _mapimages.widthoffset\r
530 \r
531         #エリアサイズを再計算\r
532         self.resize(\r
533             w * (_mapengine.width) + _mapimages.widthoffset * 2,\r
534             v * (_mapengine.height) + _mapimages.heightoffset * 2\r
535         )\r
536 \r
537         #backcolor\r
538         for x in range(_mapengine.width):\r
539             xx = x * w\r
540             for y in range(_mapengine.height):\r
541                 yy = y * v\r
542                 backcolor = _mapengine.getbackcolor(x, y)\r
543                 if backcolor:\r
544                     painter.fillRect(wo + xx, ho + yy, w, v,\r
545                         getcolorfromstring(backcolor))\r
546 \r
547         #grid\r
548         for x in range(_mapengine.width + 1):\r
549             xx = x * w\r
550             for y in range(_mapengine.height + 1):\r
551                 yy = y * v\r
552                 if x != _mapengine.width:\r
553                     painter.drawImage(wo + xx, yy,\r
554                         _mapimages.wall(0, "h"))\r
555                 if y != _mapengine.height:\r
556                     painter.drawImage(xx, ho + yy,\r
557                         _mapimages.wall(0, "v"))\r
558 \r
559         #wall(gridは描画しない)\r
560         for x in range(_mapengine.width + 1):\r
561             xx = x * w\r
562             for y in range(_mapengine.height + 1):\r
563                 yy = y * v\r
564                 if x != _mapengine.width and _mapengine.getdata(x, y, "h") != 0:\r
565                     painter.drawImage(wo + xx, yy,\r
566                         _mapimages.wall(_mapengine.getdata(x, y, "h"), "h"))\r
567                 if y != _mapengine.height and _mapengine.getdata(x, y, "v") != 0:\r
568                     painter.drawImage(xx, ho + yy,\r
569                         _mapimages.wall(_mapengine.getdata(x, y, "v"), "v"))\r
570                 mark = _mapengine.getmark(x, y)\r
571                 if mark != "":\r
572                     painter.setPen(getcolorfromstring(_mapengine.getforecolor(x, y)))\r
573                     painter.drawText(wo + xx + 2, ho + yy + 2, w - 2, v - 2,\r
574                         QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter,\r
575                         mark)\r
576 \r
577         #座標設定中はdrawing box を表示しない。\r
578         if self.setoriginemode:\r
579             return\r
580 \r
581         #drawing box\r
582         if self._pressedbutton != QtCore.Qt.MouseButton.NoButton:\r
583             if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:\r
584                 if self._x1 == self._x2 and self._y1 == self._y2:\r
585                     painter.setPen(QtGui.QColor(255, 0, 0))\r
586                 elif self._x1 == self._x2 or self._y1 == self._y2:\r
587                     painter.setPen(QtGui.QColor(0, 255, 0))\r
588                 else:\r
589                     painter.setPen(QtGui.QColor(255, 0, 0))\r
590             elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:\r
591                 painter.setPen(QtGui.QColor(0, 0, 255))\r
592             painter.drawRect(self._px1, self._py1,\r
593                 self._px2 - self._px1, self._py2 - self._py1)\r
594 \r
595     def eventFilter(self, obj, event):\r
596         def xpos():\r
597             return ((event.pos().x() - _mapimages.widthoffset) // (_mapimages.width - 1))\r
598 \r
599         def ypos():\r
600             return ((event.pos().y() - _mapimages.heightoffset) // (_mapimages.height - 1))\r
601 \r
602         if obj == self:\r
603             et = event.type()\r
604 \r
605             if et == QtCore.QEvent.MouseButtonPress:\r
606                 self._x1 = xpos()\r
607                 self._y1 = ypos()\r
608                 self._pos1 = event.pos()\r
609                 self._px1 = event.pos().x()\r
610                 self._py1 = event.pos().y()\r
611                 self._x2 = xpos()\r
612                 self._y2 = ypos()\r
613                 self._px2 = event.pos().x()\r
614                 self._py2 = event.pos().y()\r
615                 self._pressedbutton = event.buttons()\r
616                 self._dragging = False\r
617                 return True\r
618 \r
619             elif et == QtCore.QEvent.MouseMove:\r
620                 self._x2 = xpos()\r
621                 self._y2 = ypos()\r
622                 self._px2 = event.pos().x()\r
623                 self._py2 = event.pos().y()\r
624                 if (self._pressedbutton != QtCore.Qt.MouseButton.NoButton and\r
625                     (event.pos() - self._pos1).manhattanLength() >=\r
626                      QtWidgets.QApplication.startDragDistance()):\r
627                     self._dragging = True\r
628                 self.mouse_moved.emit(self._x2, self._y2, event.buttons())\r
629                 return True\r
630 \r
631             elif et == QtCore.QEvent.MouseButtonRelease:\r
632                 drag_emit = False\r
633                 release_emit = False\r
634                 if self._dragging:\r
635                     drag_emit = True\r
636                     if self._pressedbutton == QtCore.Qt.MouseButton.LeftButton:\r
637                         eraseonly = False\r
638                     elif self._pressedbutton == QtCore.Qt.MouseButton.RightButton:\r
639                         eraseonly = True\r
640                 else:\r
641                     release_emit = True\r
642                 if self.setoriginemode:\r
643                     release_emit = True\r
644 \r
645                 self._pressedbutton = QtCore.Qt.MouseButton.NoButton\r
646                 self._dragging = False\r
647                 if drag_emit:\r
648                     self.mouse_drag_released.emit(\r
649                         self._x1, self._y1, self._x2, self._y2, eraseonly)\r
650                 if release_emit:\r
651                     rpx = self._px2 - self._x2 * (_mapimages.width - 1) - _mapimages.widthoffset\r
652                     rpy = self._py2 - self._y2 * (_mapimages.height - 1) - _mapimages.heightoffset\r
653                     rdx = rpx - (_mapimages.width - 1) // 2\r
654                     rdy = rpy - (_mapimages.height - 1) // 2\r
655                     if rpx <= _mapimages.widthoffset and abs(rdx) > abs(rdy):\r
656                         rx = self._x2\r
657                         ry = self._y2\r
658                         d = "v"\r
659                     elif rpx >= _mapimages.width - _mapimages.widthoffset and abs(rdx) > abs(rdy):\r
660                         rx = self._x2 + 1\r
661                         ry = self._y2\r
662                         d = "v"\r
663                     elif rpy <= _mapimages.heightoffset and abs(rdx) <= abs(rdy):\r
664                         rx = self._x2\r
665                         ry = self._y2\r
666                         d = "h"\r
667                     elif rpy >= _mapimages.height - _mapimages.heightoffset and abs(rdx) <= abs(rdy):\r
668                         rx = self._x2\r
669                         ry = self._y2 + 1\r
670                         d = "h"\r
671                     else:\r
672                         rx = self._x2\r
673                         ry = self._y2\r
674                         d = "c"\r
675                     self.mouse_released.emit(rx, ry, d)\r
676                 return True\r
677 \r
678             else:\r
679                 return False\r
680         else:\r
681             # pass the event on to the parent class\r
682             return False\r
683 \r
684 \r
685 class ColorBox(QtWidgets.QFrame):\r
686     def __init__(self, parent=None):\r
687         super(ColorBox, self).__init__(parent)\r
688         self.color = QtGui.QColor(255, 255, 255)\r
689         self.bordercolor = QtGui.QColor(0, 0, 0)\r
690 \r
691     def paintEvent(self, event):\r
692         painter = QtGui.QPainter(self)\r
693         painter.fillRect(0, 0, self.width(), self.height(), self.color)\r
694         painter.setPen(self.bordercolor)\r
695         painter.drawRect(0, 0, self.width() - 1, self.height() - 1)\r
696 \r
697 \r
698 class DetailDialog(QtWidgets.QDialog):\r
699     def __init__(self, parent=None):\r
700         super(DetailDialog, self).__init__(parent)\r
701 \r
702         f = QtGui.QFont(defaultfontname, 9)\r
703         self.setFont(f)\r
704 \r
705         marklabel = QtWidgets.QLabel(self)\r
706         marklabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
707         marklabel.setText("マーク(&M)")\r
708         marklabel.setSizePolicy(\r
709             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
710 \r
711         self.marktext = QtWidgets.QLineEdit(self)\r
712         self.marktext.setFont(f)\r
713         self.marktext.setMaxLength(2)\r
714         self.marktext.setText("")\r
715         self.marktext.setMinimumWidth(20)\r
716         self.marktext.setSizePolicy(\r
717             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
718         marklabel.setBuddy(self.marktext)\r
719 \r
720         self.forecolorbutton = QtWidgets.QPushButton(self)\r
721         self.forecolorbutton.setText("文字色(&C)...")\r
722         self.forecolorbutton.clicked.connect(self.forecolorbutton_clicked)\r
723 \r
724         self.forecolorbox = ColorBox(self)\r
725         self.forecolorbox.setMinimumSize(30, 30)\r
726         self.forecolorbox.setSizePolicy(\r
727             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)\r
728 \r
729         detaillabel = QtWidgets.QLabel(self)\r
730         detaillabel.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight)\r
731         detaillabel.setText("詳細(&D)")\r
732 \r
733         self.detailtext = QtWidgets.QTextEdit(self)\r
734         self.detailtext.setText("")\r
735         detaillabel.setBuddy(self.detailtext)\r
736 \r
737         self.buttonbox = QtWidgets.QDialogButtonBox(\r
738             QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)\r
739         self.buttonbox.accepted.connect(self.accept)\r
740         self.buttonbox.rejected.connect(self.reject)\r
741         self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).setText("OK")\r
742         self.buttonbox.button(QtWidgets.QDialogButtonBox.Cancel).setText("キャンセル")\r
743 \r
744         layout = QtWidgets.QGridLayout()\r
745         layout.addWidget(marklabel, 0, 0, 1, 1)\r
746         layout.addWidget(self.marktext, 0, 1, 1, 1)\r
747         layout.addWidget(self.forecolorbutton, 0, 2, 1, 1)\r
748         layout.addWidget(self.forecolorbox, 0, 3, 1, 1)\r
749         layout.addWidget(detaillabel, 1, 0, 1, 1)\r
750         layout.addWidget(self.detailtext, 1, 1, 1, 3)\r
751         layout.addWidget(self.buttonbox, 2, 0, 1, 4)\r
752         self.setLayout(layout)\r
753         self.setModal(True)\r
754 \r
755     def setvalue(self, x, y, mark, detail, color):\r
756         self.setWindowTitle("({x}, {y})".format(x=x, y=y))\r
757         self.marktext.setText(mark)\r
758         self.detailtext.setText(detail)\r
759         self.forecolorbox.color = color\r
760 \r
761     def forecolorbutton_clicked(self):\r
762         global config\r
763         dlg = PydunColorDialog(self, config.get("customColor", dict()))\r
764         dlg.setCurrentColor(self.forecolorbox.color)\r
765         dlg.exec_()\r
766         config["customColor"] = dlg.config\r
767         if dlg.result() == QtWidgets.QDialog.Accepted:\r
768             self.forecolorbox.color = dlg.currentColor()\r
769 \r
770 \r
771 class SetOrigineDialog(QtWidgets.QDialog):\r
772     def __init__(self, parent=None):\r
773         super(SetOrigineDialog, self).__init__(parent)\r
774  \r
775         f = QtGui.QFont(defaultfontname, 9)\r
776         self.setFont(f)\r
777 \r
778         self.setWindowTitle("座標設定")\r
779 \r
780         promptlabel = QtWidgets.QLabel(self)\r
781         promptlabel.setAlignment(\r
782             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)\r
783         promptlabel.setText("この地点の座標と軸の方向を入力してください。")\r
784 \r
785         self.currentlabel = QtWidgets.QLabel(self)\r
786         self.currentlabel.setAlignment(\r
787             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)\r
788         self.currentlabel.setText("")\r
789 \r
790         xlabel = QtWidgets.QLabel(self)\r
791         xlabel.setAlignment(\r
792             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
793         xlabel.setText("&X")\r
794 \r
795         self.xbox = QtWidgets.QSpinBox(self)\r
796         self.xbox.setRange(-999, +999)\r
797         self.xbox.setSingleStep(1)\r
798         self.xbox.setValue(0)\r
799         xlabel.setBuddy(self.xbox)\r
800 \r
801         ylabel = QtWidgets.QLabel(self)\r
802         ylabel.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)\r
803         ylabel.setText("&Y")\r
804 \r
805         self.ybox = QtWidgets.QSpinBox(self)\r
806         self.ybox.setRange(-999, +999)\r
807         self.ybox.setSingleStep(1)\r
808         self.ybox.setValue(0)\r
809         ylabel.setBuddy(self.ybox)\r
810 \r
811         self.xplusbutton = QtWidgets.QRadioButton(self)\r
812         self.xplusbutton.setText("右が正(&R)")\r
813         self.xminusbutton = QtWidgets.QRadioButton(self)\r
814         self.xminusbutton.setText("左が正(&L)")\r
815         self.yminusbutton = QtWidgets.QRadioButton(self)\r
816         self.yminusbutton.setText("上が正(&T)")\r
817         self.yplusbutton = QtWidgets.QRadioButton(self)\r
818         self.yplusbutton.setText("下が正(&B)")\r
819 \r
820         xgroup = QtWidgets.QButtonGroup(self)\r
821         xgroup.addButton(self.xplusbutton)\r
822         xgroup.addButton(self.xminusbutton)\r
823 \r
824         ygroup = QtWidgets.QButtonGroup(self)\r
825         ygroup.addButton(self.yplusbutton)\r
826         ygroup.addButton(self.yminusbutton)\r
827 \r
828         self.buttonbox = QtWidgets.QDialogButtonBox(\r
829             QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)\r
830         self.buttonbox.accepted.connect(self.accept)\r
831         self.buttonbox.rejected.connect(self.reject)\r
832         self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).setText("OK")\r
833         self.buttonbox.button(QtWidgets.QDialogButtonBox.Cancel).setText("キャンセル")\r
834 \r
835         layout = QtWidgets.QGridLayout()\r
836         layout.addWidget(promptlabel, 0, 0, 1, 4)\r
837         layout.addWidget(self.currentlabel, 1, 0, 1, 4)\r
838         layout.addWidget(xlabel, 2, 0, 1, 1)\r
839         layout.addWidget(self.xbox, 2, 1, 1, 1)\r
840         layout.addWidget(ylabel, 2, 2, 1, 1)\r
841         layout.addWidget(self.ybox, 2, 3, 1, 1)\r
842         layout.addWidget(self.xplusbutton, 3, 1, 1, 1)\r
843         layout.addWidget(self.xminusbutton, 4, 1, 1, 1)\r
844         layout.addWidget(self.yminusbutton, 3, 3, 1, 1)\r
845         layout.addWidget(self.yplusbutton, 4, 3, 1, 1)\r
846         layout.addWidget(self.buttonbox, 5, 0, 1, 4)\r
847         self.setLayout(layout)\r
848         self.setModal(True)\r
849 \r
850     def setcurrent(self, x, y, signx, signy):\r
851         self.xbox.setValue(x)\r
852         self.ybox.setValue(y)\r
853         self.currentlabel.setText("現在の座標 ({x}, {y})".format(x=x, y=y))\r
854         if signx == 1:\r
855             self.xplusbutton.setChecked(True)\r
856         elif signx == -1:\r
857             self.xminusbutton.setChecked(True)\r
858         if signy == 1:\r
859             self.yplusbutton.setChecked(True)\r
860         elif signy == -1:\r
861             self.yminusbutton.setChecked(True)\r
862 \r
863     @property\r
864     def originex(self):\r
865         return self.xbox.value()\r
866 \r
867     @property\r
868     def originey(self):\r
869         return self.ybox.value()\r
870 \r
871     @property\r
872     def signx(self):\r
873         if self.xplusbutton.isChecked():\r
874             return 1\r
875         elif self.xminusbutton.isChecked():\r
876             return -1\r
877         else:\r
878             return 0\r
879 \r
880     @property\r
881     def signy(self):\r
882         if self.yplusbutton.isChecked():\r
883             return 1\r
884         elif self.yminusbutton.isChecked():\r
885             return -1\r
886         else:\r
887             return 0\r
888 \r
889 \r
890 class SetSizeDialog(QtWidgets.QDialog):\r
891     def __init__(self, parent=None):\r
892         super(SetSizeDialog, self).__init__(parent)\r
893 \r
894         f = QtGui.QFont(defaultfontname, 9)\r
895         self.setFont(f)\r
896 \r
897         self.setWindowTitle("マップのサイズ")\r
898 \r
899         self.topbutton = QtWidgets.QRadioButton(self)\r
900         self.topbutton.setText("上(&T)")\r
901         self.topbutton.clicked.connect(self.updatewidgets)\r
902 \r
903         self.topsize = QtWidgets.QSpinBox(self)\r
904         self.topsize.setSingleStep(1)\r
905         self.topsize.setValue(0)\r
906         self.topsize.valueChanged.connect(self.updatewidgets)\r
907 \r
908         self.bottombutton = QtWidgets.QRadioButton(self)\r
909         self.bottombutton.setText("下(&B)")\r
910         self.bottombutton.clicked.connect(self.updatewidgets)\r
911 \r
912         self.bottomsize = QtWidgets.QSpinBox(self)\r
913         self.bottomsize.setSingleStep(1)\r
914         self.bottomsize.setValue(0)\r
915         self.bottomsize.valueChanged.connect(self.updatewidgets)\r
916 \r
917         self.leftbutton = QtWidgets.QRadioButton(self)\r
918         self.leftbutton.setText("左(&L)")\r
919         self.leftbutton.clicked.connect(self.updatewidgets)\r
920 \r
921         self.leftsize = QtWidgets.QSpinBox(self)\r
922         self.leftsize.setSingleStep(1)\r
923         self.leftsize.setValue(0)\r
924         self.leftsize.valueChanged.connect(self.updatewidgets)\r
925 \r
926         self.rightbutton = QtWidgets.QRadioButton(self)\r
927         self.rightbutton.setText("右(&R)")\r
928         self.rightbutton.clicked.connect(self.updatewidgets)\r
929 \r
930         self.rightsize = QtWidgets.QSpinBox(self)\r
931         self.rightsize.setSingleStep(1)\r
932         self.rightsize.setValue(0)\r
933         self.rightsize.valueChanged.connect(self.updatewidgets)\r
934 \r
935         self.sizelabel = QtWidgets.QLabel(self)\r
936         self.sizelabel .setAlignment(\r
937             QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)\r
938         self.sizelabel.setText("この地点の座標を入力してください。")\r
939 \r
940         self.buttonbox = QtWidgets.QDialogButtonBox(\r
941         QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)\r
942         self.buttonbox.accepted.connect(self.accept)\r
943         self.buttonbox.rejected.connect(self.reject)\r
944         self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).setText("OK")\r
945         self.buttonbox.button(QtWidgets.QDialogButtonBox.Cancel).setText("キャンセル")\r
946 \r
947         verticalgroup = QtWidgets.QButtonGroup(self)\r
948         verticalgroup.addButton(self.topbutton)\r
949         verticalgroup.addButton(self.bottombutton)\r
950 \r
951         holizontalgroup = QtWidgets.QButtonGroup(self)\r
952         holizontalgroup.addButton(self.leftbutton)\r
953         holizontalgroup.addButton(self.rightbutton)\r
954 \r
955         self.topbutton.setChecked(True)\r
956         self.bottombutton.setChecked(False)\r
957         self.leftbutton.setChecked(True)\r
958         self.rightbutton.setChecked(False)\r
959 \r
960         layout = QtWidgets.QGridLayout(self)\r
961         layout.addWidget(self.topbutton, 0, 2, 1, 1)\r
962         layout.addWidget(self.topsize, 0, 3, 1, 1)\r
963         layout.addWidget(self.leftbutton, 1, 0, 1, 1)\r
964         layout.addWidget(self.leftsize, 1, 1, 1, 1)\r
965         layout.addWidget(self.sizelabel, 1, 2, 1, 2)\r
966         layout.addWidget(self.rightbutton, 1, 4, 1, 1)\r
967         layout.addWidget(self.rightsize, 1, 5, 1, 1)\r
968         layout.addWidget(self.bottombutton, 2, 2, 1, 1)\r
969         layout.addWidget(self.bottomsize, 2, 3, 1, 1)\r
970         layout.addWidget(self.buttonbox, 3, 0, 1, 6)\r
971         self.setLayout(layout)\r
972         self.setModal(True)\r
973 \r
974     def setoriginalsize(self, width, height):\r
975         self._width = width\r
976         self._height = height\r
977         self.topsize.setRange(-height+1, +100)\r
978         self.bottomsize.setRange(-height+1, +100)\r
979         self.leftsize.setRange(-width+1, +100)\r
980         self.rightsize.setRange(-width+1, +100)\r
981         self.updatewidgets()\r
982 \r
983     def updatewidgets(self):\r
984         dh = 0\r
985         dw = 0\r
986 \r
987         if self.topbutton.isChecked():\r
988             dh = self.topsize.value()\r
989             self.topsize.setEnabled(True)\r
990             self.bottomsize.setDisabled(True)\r
991         elif self.bottombutton.isChecked():\r
992             dh = self.bottomsize.value()\r
993             self.topsize.setDisabled(True)\r
994             self.bottomsize.setEnabled(True)\r
995         if self.leftbutton.isChecked():\r
996             dw = self.leftsize.value()\r
997             self.leftsize.setEnabled(True)\r
998             self.rightsize.setDisabled(True)\r
999         elif self.rightbutton.isChecked():\r
1000             dw = self.rightsize.value()\r
1001             self.leftsize.setDisabled(True)\r
1002             self.rightsize.setEnabled(True)\r
1003 \r
1004         self.sizelabel.setText(\r
1005             "変更前のサイズ: {w1} x {h1}\n変更後のサイズ: {w2} x {h2}".format(\r
1006                 w1=self._width, h1=self._height,\r
1007                 w2=self._width+dw, h2=self._height+dh))\r
1008 \r
1009     def getsize(self):\r
1010         top = 0\r
1011         bottom = 0\r
1012         left = 0\r
1013         right = 0\r
1014         if self.topbutton.isChecked():\r
1015             top = self.topsize.value()\r
1016         elif self.bottombutton.isChecked():\r
1017             bottom = self.bottomsize.value()\r
1018         if self.leftbutton.isChecked():\r
1019             left = self.leftsize.value()\r
1020         elif self.rightbutton.isChecked():\r
1021             right = self.rightsize.value()\r
1022         return (top, bottom, left, right)\r
1023 \r
1024 \r
1025 class MapImages(object):\r
1026     def __init__(self, show_wall_menu_string):\r
1027         if show_wall_menu_string:\r
1028             vtext = ["なし", "壁", "扉", "扉(→)", "扉(←)", "一通(→)", "一通(←)", "隠", "隠(→)", "隠(←)",]\r
1029             htext = ["なし", "壁", "扉", "扉(↓)", "扉(↑)", "一通(↓)", "一通(↑)", "隠", "隠(↓)", "隠(↑)",]\r
1030         else:\r
1031             vtext = ["", "", "", "", "", "", "", "", "", "",]\r
1032             htext = ["", "", "", "", "", "", "", "", "", "",]\r
1033         self.wall_images = list()\r
1034         self.wall_icons = list()\r
1035         self.wall_texts = list()\r
1036         for index in range(10):\r
1037             self.wall_images.append(dict())\r
1038             self.wall_icons.append(dict())\r
1039             self.wall_texts.append(dict())\r
1040             for direction in ["v", "h"]:\r
1041                 filename = os.path.join(\r
1042                     basedir(),\r
1043                     "images",\r
1044                     "wall_{direction}_{index:02}.png".format(\r
1045                         direction=direction, index=index))\r
1046                 self.wall_images[index][direction] = QtGui.QImage()\r
1047                 self.wall_images[index][direction].load(filename)\r
1048                 self.wall_icons[index][direction] = QtGui.QIcon(filename)\r
1049             self.wall_texts[index]["v"] = vtext[index]\r
1050             self.wall_texts[index]["h"] = htext[index]\r
1051 \r
1052     @property\r
1053     def width(self):\r
1054         return self.wall_images[0]["h"].width()\r
1055 \r
1056     @property\r
1057     def height(self):\r
1058         return self.wall_images[0]["v"].height()\r
1059 \r
1060     @property\r
1061     def widthoffset(self):\r
1062         return self.wall_images[0]["v"].width()//2\r
1063 \r
1064     @property\r
1065     def heightoffset(self):\r
1066         return self.wall_images[0]["h"].height()//2\r
1067 \r
1068     def wall(self, index, direction):\r
1069         return self.wall_images[index][direction]\r
1070 \r
1071 \r
1072 class MapEngine(object):\r
1073     hwall = " -#WMwmHVA"\r
1074     vwall = " |#PCpc=DG"\r
1075 \r
1076     def __init__(self, width, height, signx, signy, offsetx, offsety):\r
1077         self._width = width\r
1078         self._height = height\r
1079         self._signx = signx\r
1080         self._signy = signy\r
1081         self._offsetx = offsetx\r
1082         self._offsety = offsety\r
1083         self.filename = None\r
1084 \r
1085         self.initdata()\r
1086         self.inityaml()\r
1087         self._note = dict()\r
1088 \r
1089     def initdata(self):\r
1090         width = self.width + 1\r
1091         height = self.height + 1\r
1092         self._data = self.initialdata(width, height)\r
1093 \r
1094     def initialdata(self, width, height):\r
1095         dt = list()\r
1096         for x in range(width):\r
1097             dt.append(list())\r
1098             for y in range(height):\r
1099                 dt[x].append(dict())\r
1100                 for d in ["h", "v"]:\r
1101                     dt[x][y][d] = 0\r
1102         return dt\r
1103 \r
1104     def inityaml(self):\r
1105         #yaml !python/Unicode出力抑止おまじない\r
1106         def represent_unicode(dumper, data):\r
1107             return dumper.represent_scalar("tag:yaml.org,2002:str", data)\r
1108         def construct_unicode(loader, node):\r
1109             return str(loader.construct_scalar(node))\r
1110         yaml.add_representer(str, represent_unicode)\r
1111         yaml.add_constructor("tag:yaml.org,2002:str", construct_unicode)\r
1112 \r
1113     def getdata(self, x, y, direction):\r
1114         return self._data[x][y][direction]\r
1115 \r
1116     def setdata(self, x, y, direction, value):\r
1117         self._data[x][y][direction] = value\r
1118 \r
1119     @property\r
1120     def width(self):\r
1121         return self._width\r
1122 \r
1123     @property\r
1124     def height(self):\r
1125         return self._height\r
1126 \r
1127     @property\r
1128     def signx(self):\r
1129         return self._signx\r
1130 \r
1131     @property\r
1132     def signy(self):\r
1133         return self._signy\r
1134 \r
1135     @property\r
1136     def offsetx(self):\r
1137         return self._offsetx\r
1138 \r
1139     @property\r
1140     def offsety(self):\r
1141         return self._offsety\r
1142 \r
1143     def setoffset(self, x, y):\r
1144         self._offsetx = x\r
1145         self._offsety = y\r
1146 \r
1147     def setsign(self, signx, signy):\r
1148         self._signx = signx\r
1149         self._signy = signy\r
1150 \r
1151     def getmark(self, x, y):\r
1152         return self.unescape(self.getnote(x, y)["mark"])\r
1153 \r
1154     def getdetail(self, x, y):\r
1155         return self.unescape(self.getnote(x, y)["detail"])\r
1156 \r
1157     def getforecolor(self, x, y):\r
1158         return self.getnote(x, y)["forecolor"]\r
1159 \r
1160     def getbackcolor(self, x, y):\r
1161         return self.getnote(x, y)["backcolor"]\r
1162 \r
1163     def getnote(self, x, y):\r
1164         return self._note.get(\r
1165             self.coodtokey(x, y), {"mark":"", "detail":"", "forecolor":"#000000", "backcolor":""})\r
1166 \r
1167     def coodtokey(self, x, y):\r
1168         return "{x:+05d}_{y:+05d}".format(x=int(x), y=int(y))\r
1169 \r
1170     def keytocood(self, key):\r
1171         return list(map(int, key.split("_")))\r
1172 \r
1173     def setmark(self, x, y, mark):\r
1174         note = self.getnote(x, y)\r
1175         note["mark"] = self.escape(mark)\r
1176         self.setnote(x, y, note)\r
1177 \r
1178     def setdetail(self, x, y, detail):\r
1179         note = self.getnote(x, y)\r
1180         note["detail"] = self.escape(detail)\r
1181         self.setnote(x, y, note)\r
1182 \r
1183     def setforecolor(self, x, y, color):\r
1184         note = self.getnote(x, y)\r
1185         note["forecolor"] = color\r
1186         self.setnote(x, y, note)\r
1187 \r
1188     def setbackcolor(self, x, y, color):\r
1189         note = self.getnote(x, y)\r
1190         note["backcolor"] = color\r
1191         self.setnote(x, y, note)\r
1192 \r
1193     def setnote(self, x, y, note):\r
1194         self._note[self.coodtokey(x, y)] = note\r
1195 \r
1196     def escape(self, s):\r
1197         return s.replace("\\", "\\\\").replace("\n", r"\n")\r
1198 \r
1199     def unescape(self, s):\r
1200         return s.replace(r"\n", "\n").replace("\\\\", "\\")\r
1201 \r
1202     def viewx(self, x):\r
1203         return x * self.signx + self.offsetx\r
1204 \r
1205     def viewy(self, y):\r
1206         return y * self.signy + self.offsety\r
1207 \r
1208     def worldx(self, x):\r
1209         return (x - self.offsetx) / self.signx\r
1210 \r
1211     def worldy(self, y):\r
1212         return (y - self.offsety) / self.signy\r
1213 \r
1214     def changesize(self, top, bottom, left, right):\r
1215         oldoffsetx = max(-left, 0)\r
1216         newoffsetx = max(left, 0)\r
1217         newwidth = self.width + left + right\r
1218         oldoffsety = max(-top, 0)\r
1219         newoffsety = max(top, 0)\r
1220         newheight = self.height + top + bottom\r
1221 \r
1222         newdata = self.initialdata(newwidth + 1, newheight + 1)\r
1223         newnote = dict()\r
1224         for x in range(min(self._width, newwidth) + 1):\r
1225             for y in range(min(self._height, newheight) + 1):\r
1226                 for d in ["h", "v"]:\r
1227                     newdata[x+newoffsetx][y+newoffsety][d] = self._data[x+oldoffsetx][y+oldoffsety][d]\r
1228                 newnote[self.coodtokey(x+newoffsetx, y+newoffsety)] = self.getnote(x+oldoffsetx, y+oldoffsety)\r
1229         self._width = newwidth\r
1230         self._height = newheight\r
1231         self.setoffset(self.offsetx -self.signx * left, self.offsety -self.signy * top)\r
1232         self._data = newdata\r
1233         self._note = newnote\r
1234 \r
1235     def growwall(self, x1, y1, x2, y2, eraseonly, alwaysbox):\r
1236         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1237         offsetx, offsety = self.getoffset(x1, y1, x2, y2)\r
1238 \r
1239         #delete inner walls.\r
1240         for x in range(x1, x2+stepx, stepx):\r
1241             for y in range(y1+stepy+offsety, y2+stepy+offsety, stepy):\r
1242                 self._data[x][y]["h"] = 0\r
1243         for x in range(x1+stepx+offsetx, x2+stepx+offsetx, stepx):\r
1244             for y in range(y1, y2+stepy, stepy):\r
1245                 self._data[x][y]["v"] = 0\r
1246 \r
1247         if not eraseonly:\r
1248             #draw OUTER wall if it exists.\r
1249             if alwaysbox or (x1 == x2 and y1 == y2):\r
1250                 hline = False\r
1251                 vline = False\r
1252             elif x1 == x2:\r
1253                 hline = True\r
1254                 vline = False\r
1255             elif y1 == y2:\r
1256                 hline = False\r
1257                 vline = True\r
1258             else:\r
1259                 hline = False\r
1260                 vline = False\r
1261 \r
1262             for x in range(x1, x2+stepx, stepx):\r
1263                 if not (vline and x == x1):\r
1264                     if not hline:\r
1265                         if self._data[x][y1+offsety]["h"] == 0:\r
1266                             self._data[x][y1+offsety]["h"] = 1\r
1267                     if self._data[x][y2+stepy+offsety]["h"] == 0:\r
1268                         self._data[x][y2+stepy+offsety]["h"] = 1\r
1269             for y in range(y1, y2+stepy, stepy):\r
1270                 if not (hline and y == y1):\r
1271                     if not vline:\r
1272                         if self._data[x1+offsetx][y]["v"] == 0:\r
1273                             self._data[x1+offsetx][y]["v"] = 1\r
1274                     if self._data[x2+stepx+offsetx][y]["v"] == 0:\r
1275                         self._data[x2+stepx+offsetx][y]["v"] = 1\r
1276 \r
1277     def fillbackcolor(self, x1, y1, x2, y2, backcolor):\r
1278         stepx, stepy = self.getstep(x1, y1, x2, y2)\r
1279         for x in range(x1, x2+stepx, stepx):\r
1280             for y in range(y1, y2+stepy, stepy):\r
1281                 self.setbackcolor(x, y, backcolor)\r
1282 \r
1283     def getstep(self, x1, y1, x2, y2):\r
1284         if x1 <= x2:\r
1285             stepx = 1\r
1286         else:\r
1287             stepx = -1\r
1288         if y1 <= y2:\r
1289             stepy = 1\r
1290         else:\r
1291             stepy = -1\r
1292         return (stepx, stepy)\r
1293 \r
1294     def getoffset(self, x1, y1, x2, y2):\r
1295         if x1 <= x2:\r
1296             offsetx = 0\r
1297         else:\r
1298             offsetx = 1\r
1299         if y1 <= y2:\r
1300             offsety = 0\r
1301         else:\r
1302             offsety = 1\r
1303         return (offsetx, offsety)\r
1304 \r
1305     def save(self, filename):\r
1306         dt = self.savestring()\r
1307         with open(filename, "w", encoding='utf-8') as f:\r
1308             f.write(dt)\r
1309             self.filename = filename\r
1310 \r
1311     def savestring(self):\r
1312         data = dict()\r
1313         data["size"] = {"x":self.width, "y":self.height}\r
1314         data["offset"] = {"x":self.offsetx, "y":self.offsety}\r
1315         data["sign"] = {"x":self.signx, "y":self.signy}\r
1316         data["map"] = self.getmapstring()\r
1317 \r
1318         #noteは表示用に座標変換する。\r
1319         n = dict()\r
1320         for nk, ni in list(self._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.viewx(x), self.viewy(y))] = ni\r
1324         data["note"] = n\r
1325         return yaml.safe_dump(data, allow_unicode=True,\r
1326                 default_flow_style=False, encoding='utf-8').decode('utf-8')\r
1327 \r
1328     def getmapstring(self):\r
1329         #出力用マップ作成\r
1330         m = []\r
1331         for y in range(self.height):\r
1332             s = [" "]\r
1333             for x in range(self.width):\r
1334                 s.append("+")\r
1335                 s.append(self.hwall[self._data[x][y]["h"]])\r
1336             s.append("+")\r
1337             s.append(" ")\r
1338             m.append("".join(s))\r
1339             s = [" "]\r
1340             for x in range(self.width):\r
1341                 s.append(self.vwall[self._data[x][y]["v"]])\r
1342                 s.append(" ")\r
1343             s.append(self.vwall[self._data[self.width][y]["v"]])\r
1344             s.append(" ")\r
1345             m.append("".join(s))\r
1346         y = self.height\r
1347         s = [" "]\r
1348         for x in range(self.width):\r
1349             s.append("+")\r
1350             s.append(self.hwall[self._data[x][y]["h"]])\r
1351         s.append("+")\r
1352         s.append(" ")\r
1353         m.append("".join(s))\r
1354         return m\r
1355 \r
1356     def load(self, filename):\r
1357         with open(filename, "r", encoding="utf-8") as f:\r
1358             st = f.read()\r
1359         self.loadfromstring(st)\r
1360         self.filename = filename\r
1361 \r
1362     def loadfromstring(self, st):\r
1363         data = yaml.safe_load(st)\r
1364 \r
1365         #基本情報\r
1366         self._width = data["size"]["x"]\r
1367         self._height = data["size"]["y"]\r
1368         self._signx = data["sign"]["x"]\r
1369         self._signy = data["sign"]["y"]\r
1370         self._offsetx = data["offset"]["x"]\r
1371         self._offsety = data["offset"]["y"]\r
1372 \r
1373         #マップ\r
1374         self.initdata()\r
1375         for y in range(self.height):\r
1376             for x in range(self.width):\r
1377                 self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1378                 self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1379         x = self.width\r
1380         for y in range(self.height):\r
1381             self._data[x][y]["v"] = self.vwall.find(data["map"][y*2+1][1+x*2])\r
1382         y = self.height\r
1383         for x in range(self.width):\r
1384             self._data[x][y]["h"] = self.hwall.find(data["map"][y*2][1+x*2+1])\r
1385 \r
1386         #noteは内部用に座標変換する。\r
1387         n = dict()\r
1388         for nk, ni in list(data["note"].items()):\r
1389             if ni["mark"] != "" or ni["detail"] != "" or ni["backcolor"] != "":\r
1390                 x, y = self.keytocood(nk)\r
1391                 n[self.coodtokey(self.worldx(x), self.worldy(y))] = ni\r
1392 \r
1393         self._note = n\r
1394 \r
1395 \r
1396 class UndoManager(QtCore.QObject):\r
1397     MAX_UNDO_COUNT = 128\r
1398     changed = QtCore.Signal(bool, bool)\r
1399 \r
1400     def __init__(self):\r
1401         super(UndoManager, self).__init__()\r
1402         self.clear()\r
1403 \r
1404     def clear(self):\r
1405         self._undo = [None for x in range(self.MAX_UNDO_COUNT)]\r
1406         self._index = 0\r
1407         self._undocount = 0\r
1408         self._commited = True\r
1409         self.changed.emit(self.canundo, self.canredo)\r
1410 \r
1411     def init(self, data):\r
1412         self.clear()\r
1413         self.save(data)\r
1414         self._commited = True\r
1415 \r
1416     def save(self, obj):\r
1417         if self._index >= self.MAX_UNDO_COUNT:\r
1418             self._undo = self._undo[1:]\r
1419             self._index -= 1\r
1420             self._undo.append(None)\r
1421         self._undo[self._index] = obj\r
1422         self._index += 1\r
1423         self._undocount = 0\r
1424         self._commited = False\r
1425         self.changed.emit(self.canundo, self.canredo)\r
1426 \r
1427     def undo(self):\r
1428         self._index -= 1\r
1429         self._undocount += 1\r
1430         self._commited = False\r
1431         self.changed.emit(self.canundo, self.canredo)\r
1432         return self._undo[self._index - 1]\r
1433 \r
1434     def redo(self):\r
1435         self._index += 1\r
1436         self._undocount -= 1\r
1437         self._commited = False\r
1438         self.changed.emit(self.canundo, self.canredo)\r
1439         return self._undo[self._index - 1]\r
1440 \r
1441     def commit(self):\r
1442         self._commited = True\r
1443 \r
1444     @property\r
1445     def canundo(self):\r
1446         return (self._index > 1)\r
1447 \r
1448     @property\r
1449     def canredo(self):\r
1450         return (self._undocount > 0)\r
1451 \r
1452     @property\r
1453     def commited(self):\r
1454         return self._commited\r
1455 \r
1456 \r
1457 class PydunColorDialog(QtWidgets.QColorDialog):\r
1458     def __init__(self, parent, config):\r
1459         super(PydunColorDialog, self).__init__(parent)\r
1460         f = QtGui.QFont(defaultfontname, 9)\r
1461         self.setFont(f)\r
1462         for index in range(self.customCount()):\r
1463             self.setCustomColor(index,\r
1464                 getcolorfromstring(\r
1465                     config.get(index, "#FFFFFF")))\r
1466         self.updateconfig()\r
1467 \r
1468     def updateconfig(self):\r
1469         self._config = dict()\r
1470         for index in range(self.customCount()):\r
1471             self._config[index] = getcolorstring(self.customColor(index))\r
1472 \r
1473     def exec_(self):\r
1474         super(PydunColorDialog, self).exec_()\r
1475         self.updateconfig()\r
1476 \r
1477     @property\r
1478     def config(self):\r
1479         return self._config\r
1480 \r
1481 \r
1482 class PydunAskSaveDialog(QtWidgets.QMessageBox):\r
1483     def __init__(self, parent, filename):\r
1484         super(PydunAskSaveDialog, self).__init__(parent)\r
1485         f = QtGui.QFont(defaultfontname, 9)\r
1486         self.setFont(f)\r
1487         self.setText("{filename} への変更を保存しますか?".format(filename=filename))\r
1488         self.setStandardButtons(QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard | QtWidgets.QMessageBox.Cancel)\r
1489         self.setDefaultButton(QtWidgets.QMessageBox.Save)\r
1490         self.button(QtWidgets.QMessageBox.Save).setText("保存する(&S)")\r
1491         self.button(QtWidgets.QMessageBox.Discard).setText("保存しない(&N)")\r
1492         self.button(QtWidgets.QMessageBox.Cancel).setText("キャンセル")\r
1493 \r
1494 \r
1495 def getcolorstring(color):\r
1496     return "#{r:02x}{g:02x}{b:02x}".format(r=color.red(), g=color.green(), b=color.blue())\r
1497 \r
1498 def getcolorfromstring(colorstring):\r
1499     return QtGui.QColor.fromRgb(\r
1500         int(colorstring[1:3], 16),\r
1501         int(colorstring[3:5], 16),\r
1502         int(colorstring[5:7], 16))\r
1503 \r
1504 def basedir():\r
1505     return os.path.dirname(os.path.abspath(sys.argv[0]))\r
1506 \r
1507 def getlatestversion():\r
1508     try:\r
1509         rss = urllib.request.urlopen(projectrssurl)\r
1510         rssstring = rss.read()\r
1511         rsstree = xml.etree.ElementTree.fromstring(rssstring)\r
1512         item = rsstree.find("channel/item/title")\r
1513         ver = (item.text.split(" "))[2]\r
1514         rss.close()\r
1515     except:\r
1516         ver = projectversion\r
1517     return ver\r
1518 \r
1519 \r
1520 def main():\r
1521     loadconfig()\r
1522     app = QtWidgets.QApplication(sys.argv)\r
1523     mainWin = MainWindow()\r
1524     app.installEventFilter(mainWin.centralWidget().mapframe)\r
1525     mainWin.show()\r
1526     sys.exit(app.exec_())\r
1527 \r
1528 def loadconfig():\r
1529     global config\r
1530     global configfilename\r
1531     configfilename = os.path.join(\r
1532         basedir(),\r
1533         "Pydun.config")\r
1534     try:\r
1535         with open(configfilename, "r") as f:\r
1536             config = yaml.safe_load(f)\r
1537     except:\r
1538         config = dict()\r
1539 \r
1540 if __name__ == '__main__':\r
1541     main()\r