OSDN Git Service

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