wxPythonで作った簡易XMLエディタ
Revisão | 154f0bd01547d6579da0bfcc6a7fd9cd100ab2fb (tree) |
---|---|
Hora | 2014-04-10 01:40:46 |
Autor | tamanegi <tamanegi@user...> |
Commiter | tamanegi |
implement multiple cut copy paste
@@ -2,10 +2,12 @@ wmxmled.py: simple xml editor by wxPython | ||
2 | 2 | |
3 | 3 | TODO |
4 | 4 | * test on other platforms (now tested only on Linux-GTK env.) |
5 | -* add wm3D specific functions | |
6 | -* implement multiple delete, copy, cut | |
5 | +* add wm3d specific functions | |
6 | +* about information | |
7 | +* code refactoring | |
7 | 8 | |
8 | 9 | CHANGELOG |
10 | +2014/04/09: implement and improve multiple copy, cut, delete functions | |
9 | 11 | 2014/04/07: fix and improve undo functions (paste, del, add) |
10 | 12 | 2014/03/14: read file from command line argument |
11 | 13 | fix bug on removing attributes |
@@ -132,8 +132,7 @@ class WMXmlEditorParams: | ||
132 | 132 | TC_showattr_value = False # active only when showattr_name is True |
133 | 133 | |
134 | 134 | # undo/redo settings |
135 | - UNDO_truncate_queue_wo_query = True | |
136 | - UNDO_queue_size = 5 | |
135 | + UNDO_queue_size = 15 | |
137 | 136 | |
138 | 137 | # find settings |
139 | 138 | FIND_clear_wo_query = True |
@@ -314,6 +313,39 @@ class ActionAdd( Action ): | ||
314 | 313 | def reverse( self ): |
315 | 314 | return ActionDel( self.uniqids, self.pos, self.itemid, self.myxml ) |
316 | 315 | |
316 | +class ActionAdds( Action ): | |
317 | + def __init__( self, uniqids = None, pos = None, itemids = None, myxmls = None ): | |
318 | + Action.__init__( self ) | |
319 | + if len(myxmls) > 0: | |
320 | + self.initialize( uniqids, pos, itemids, myxmls ) | |
321 | + | |
322 | + def initialize( self, uniqids, pos, itemids, myxmls ): | |
323 | + mylen = len( uniqids ) | |
324 | + self.myadds = [] | |
325 | + # length shall be same | |
326 | + #if len(pos) != mylen or len(itemids) != mylen or len(myxmls) != mylen: | |
327 | + if len(itemids) != mylen or len(myxmls) != mylen: | |
328 | + return | |
329 | + for i in range( 0, mylen ): | |
330 | + self.myadds.append( ActionAdd( uniqids[i], 0, itemids[i], myxmls[i] ) ) | |
331 | + | |
332 | + def undo( self, treectrl, wmxmled ): | |
333 | + self.myadds.reverse() | |
334 | + for myadd in self.myadds: | |
335 | + myadd.undo( treectrl, wmxmled ) | |
336 | + return True | |
337 | + | |
338 | + def reverse( self ): | |
339 | + uniqids = [] | |
340 | + pos = [] | |
341 | + itemids = [] | |
342 | + myxmls = [] | |
343 | + for myadd in self.myadds: | |
344 | + uniqids.append( myadd.uniqids ) | |
345 | + pos.append( myadd.pos ) | |
346 | + myxmls.append( myadd.myxml ) | |
347 | + return ActionDels( uniqids, pos, itemids, myxmls ) | |
348 | + | |
317 | 349 | # delete action; reversed action is add |
318 | 350 | class ActionDel( Action ): |
319 | 351 | def __init__( self, uniqids = None, pos = None, itemid = None, myxml = None ): |
@@ -357,3 +389,39 @@ class ActionDel( Action ): | ||
357 | 389 | |
358 | 390 | def reverse( self ): |
359 | 391 | return ActionAdd( self.uniqids, self.pos, self.itemid, self.myxml ) |
392 | + | |
393 | +# multiple deletes | |
394 | +# registered actions should not be nested | |
395 | +class ActionDels( Action ): | |
396 | + def __init__( self, uniqids = None, pos = None, itemids = None, myxmls = None ): | |
397 | + Action.__init__( self ) | |
398 | + if len(myxmls) > 0: | |
399 | + self.initialize( uniqids, pos, itemids, myxmls ) | |
400 | + | |
401 | + def initialize( self, uniqids, pos, itemids, myxmls ): | |
402 | + mylen = len( uniqids ) | |
403 | + self.mydels = [] | |
404 | + # lengths shall be same | |
405 | + #if len(pos) != mylen or len(itemids) != mylen or len(myxmls) != mylen: | |
406 | + if len(pos) != mylen or len(myxmls) != mylen: | |
407 | + return | |
408 | + for i in range( 0, mylen ): | |
409 | + self.mydels.append( ActionDel( uniqids[i], pos[i], None, myxmls[i] ) ) | |
410 | + | |
411 | + def undo( self, treectrl, wmxmled ): | |
412 | + self.mydels.reverse() | |
413 | + for mydel in self.mydels: | |
414 | + mydel.undo( treectrl, wmxmled ) | |
415 | + return True | |
416 | + | |
417 | + def reverse( self ): | |
418 | + uniqids = [] | |
419 | + pos = [] | |
420 | + itemids = [] | |
421 | + myxmls = [] | |
422 | + for mydel in self.mydels: | |
423 | + uniqids.append( mydel.uniqids ) | |
424 | + pos.append( mydel.pos ) | |
425 | + itemids.append( mydel.itemid ) | |
426 | + myxmls.append( mydel.myxml ) | |
427 | + return ActionAdds( uniqids, pos, itemids, myxmls ) |
@@ -1,7 +1,7 @@ | ||
1 | 1 | <wmconf> |
2 | 2 | <!-- sample configuration file --> |
3 | 3 | <!-- the following values are equivalent to values in wmutils.py --> |
4 | - <undo no_query_trunc="1" qsize="15" /> | |
4 | + <undo qsize="15" /> | |
5 | 5 | <find clear_wo_query="1" /> |
6 | 6 | <tc showattr_name="1" showattr_value="1" /> |
7 | 7 | <lc emphasis="blue" indent="2" /> |
@@ -600,21 +600,6 @@ | ||
600 | 600 | <event name="OnMenuSelection"></event> |
601 | 601 | <event name="OnUpdateUI"></event> |
602 | 602 | </object> |
603 | - <object class="wxMenuItem" expanded="1"> | |
604 | - <property name="bitmap"></property> | |
605 | - <property name="checked">0</property> | |
606 | - <property name="enabled">1</property> | |
607 | - <property name="help"></property> | |
608 | - <property name="id">wxID_ANY</property> | |
609 | - <property name="kind">wxITEM_NORMAL</property> | |
610 | - <property name="label">Delete Selected</property> | |
611 | - <property name="name">rightClickMenuDeleteSelected</property> | |
612 | - <property name="permission">none</property> | |
613 | - <property name="shortcut"></property> | |
614 | - <property name="unchecked_bitmap"></property> | |
615 | - <event name="OnMenuSelection"></event> | |
616 | - <event name="OnUpdateUI"></event> | |
617 | - </object> | |
618 | 603 | <object class="separator" expanded="0"> |
619 | 604 | <property name="name">m_separator6</property> |
620 | 605 | <property name="permission">none</property> |
@@ -108,7 +108,6 @@ class WMXmlEditor( wx.App ): | ||
108 | 108 | self.Bind( wx.EVT_MENU, self.evPopupMenuPasteA, id=xrc.XRCID("rightClickMenuPasteAppend") ) |
109 | 109 | self.Bind( wx.EVT_MENU, self.evPopupMenuPasteAC, id=xrc.XRCID("rightClickMenuPasteAppendChild") ) |
110 | 110 | self.Bind( wx.EVT_MENU, self.evPopupMenuDelete, id=xrc.XRCID("rightClickMenuDelete") ) |
111 | - self.Bind( wx.EVT_MENU, self.evPopupMenuDeleteSelected, id=xrc.XRCID("rightClickMenuDeleteSelected") ) | |
112 | 111 | self.Bind( wx.EVT_MENU, self.evPopupMenuInsert, id=xrc.XRCID("rightClickMenuInsert") ) |
113 | 112 | self.Bind( wx.EVT_MENU, self.evPopupMenuInsertChild, id=xrc.XRCID("rightClickMenuInsertChild") ) |
114 | 113 | self.Bind( wx.EVT_MENU, self.evPopupMenuAppend, id=xrc.XRCID("rightClickMenuAppend") ) |
@@ -608,6 +607,8 @@ class WMXmlEditor( wx.App ): | ||
608 | 607 | if not self.lc.IsSelected( index ): |
609 | 608 | return |
610 | 609 | self.lc_editing_index = index |
610 | + self.lc_editing_indexes = self.listCtrlGetSelectedIndexes() | |
611 | + numselected = len( self.lc_editing_indexes ) | |
611 | 612 | ctrl = wx.GetKeyState( wx.WXK_CONTROL ) |
612 | 613 | if keycode == 3: # Ctrl-C; copy |
613 | 614 | self.evPopupMenuCopy( None ) |
@@ -1067,7 +1068,6 @@ class WMXmlEditor( wx.App ): | ||
1067 | 1068 | if self.copybuffer is None: |
1068 | 1069 | self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuPaste")).Enable( False ) |
1069 | 1070 | self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuDelete")).Enable( activate ) |
1070 | - self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuDeleteSelected")).Enable( activate ) | |
1071 | 1071 | self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuInsert")).Enable( True ) |
1072 | 1072 | self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuInsertChild")).Enable( True ) |
1073 | 1073 | self.rightClickMenu.FindItemById(xrc.XRCID("rightClickMenuAppend")).Enable( True ) |
@@ -1087,11 +1087,21 @@ class WMXmlEditor( wx.App ): | ||
1087 | 1087 | def evPopupMenuCopy( self, event ): |
1088 | 1088 | if not self.popupIsItemAvailable( self.lc_editing_index ): |
1089 | 1089 | return |
1090 | + self.copybuffer = [] | |
1091 | + self.removeNestedIndexes() | |
1092 | + if len( self.lc_editing_indexes ) > 1: # multiple | |
1093 | + for myindex in self.lc_editing_indexes: | |
1094 | + self.lc_editing_index = myindex | |
1095 | + self.copybuffer.append( self.workerCopy() ) | |
1096 | + else: # single copy | |
1097 | + self.copybuffer = [ self.workerCopy() ] | |
1098 | + | |
1099 | + def workerCopy( self ): | |
1090 | 1100 | myindex = min( self.lc_editing_index, self.l2l_correspondence[self.lc_editing_index] ) |
1091 | - treeitemid = self.l2t_correspondence[myindex] | |
1101 | + itemid = self.l2t_correspondence[myindex] | |
1092 | 1102 | # uniqids will be discarded |
1093 | - self.copybuffer, uniqids = self.createElementTreeFromTreeCtrl( treeitemid ) | |
1094 | - self.copybuffer = self.copybuffer.getroot() | |
1103 | + buf, uniqids = self.createElementTreeFromTreeCtrl( itemid ) | |
1104 | + return buf.getroot() | |
1095 | 1105 | |
1096 | 1106 | def evPopupMenuCut( self, event ): |
1097 | 1107 | if not self.popupIsItemAvailable( self.lc_editing_index ): |
@@ -1113,69 +1123,131 @@ class WMXmlEditor( wx.App ): | ||
1113 | 1123 | self.evPopupMenuPaste( event, False, True ) |
1114 | 1124 | |
1115 | 1125 | def evPopupMenuPaste( self, event, insert = True, child = True ): |
1126 | + # brief check | |
1116 | 1127 | if self.copybuffer is None: |
1117 | 1128 | self.miscShowMessageBox( "no data in buffer" ) |
1118 | 1129 | return |
1119 | - newxml = copy.deepcopy( self.copybuffer ) | |
1120 | - showname = self.createTreeCtrlLabel( newxml ) | |
1130 | + if len( self.copybuffer ) == 0: | |
1131 | + self.miscShowMessageBox( "no data in buffer" ) | |
1132 | + return | |
1133 | + # prepare TreeCtrl labels | |
1134 | + newxmls = copy.deepcopy( self.copybuffer ) | |
1135 | + names = [] | |
1136 | + for newxml in newxmls: | |
1137 | + names.append( self.createTreeCtrlLabel( newxml ) ) | |
1138 | + numadd = len( names ) | |
1139 | + # get ListCtrl index | |
1121 | 1140 | if len( self.l2l_correspondence ) > 0: |
1122 | 1141 | index = min( self.lc_editing_index, self.l2l_correspondence[self.lc_editing_index] ) |
1123 | 1142 | else: |
1124 | 1143 | index = -1 |
1144 | + | |
1145 | + if insert: | |
1146 | + names.reverse() | |
1147 | + newxmls.reverse() | |
1148 | + | |
1149 | + # add xml in copybuffer | |
1150 | + my_itemids = [] | |
1125 | 1151 | if index >= 0: |
1126 | - treeitemid = self.l2t_correspondence[index] | |
1152 | + itemid = self.l2t_correspondence[index] | |
1127 | 1153 | if index < 0 and child: # i do not know what to do |
1128 | 1154 | self.miscShowMessageBox( "cannot create child element." ) |
1129 | 1155 | return |
1130 | 1156 | elif index < 0 and self.tc.IsEmpty(): # add root element |
1131 | - my_itemid = self.tc.AddRoot( showname, self.imageFolder, self.imageFolderOpen ) | |
1157 | + if numadd > 1: | |
1158 | + self.miscShowMessageBox( "cannot add multiple root element." ) | |
1159 | + return | |
1160 | + my_itemids.append( self.tc.AddRoot( names[0], self.imageFolder, self.imageFolderOpen ) ) | |
1132 | 1161 | else: |
1133 | 1162 | if insert and child: # insert child |
1134 | - my_itemid = self.insertChildElement( showname, treeitemid ) | |
1163 | + for name in names: | |
1164 | + my_itemids.append( self.insertChildElement( name, itemid ) ) | |
1135 | 1165 | elif insert and not child: # insert |
1136 | 1166 | if index == 0: |
1137 | 1167 | self.miscShowMessageBox("Cannot insert another root element.") |
1138 | 1168 | return |
1139 | 1169 | else: |
1140 | - my_itemid = self.insertElement( showname, treeitemid ) | |
1170 | + my_itemids.append( self.insertElement( names[0], itemid ) ) | |
1171 | + for i in range( 1, numadd ): | |
1172 | + my_itemids.append( self.insertElement( names[i], my_itemids[-1] ) ) | |
1141 | 1173 | elif not insert and child: # append child |
1142 | - my_itemid = self.appendChildElement( showname, treeitemid ) | |
1174 | + for name in names: | |
1175 | + my_itemids.append( self.appendChildElement( names[i], itemid ) ) | |
1143 | 1176 | elif not insert and not child: # append |
1144 | 1177 | if index == 0: |
1145 | 1178 | self.miscShowMessageBox("Cannot insert another root element.") |
1146 | 1179 | return |
1147 | 1180 | else: |
1148 | - my_itemid = self.appendElement( showname, treeitemid ) | |
1149 | - newxml = WMXmlElement( self.copybuffer ) | |
1150 | - # use new uniq id | |
1151 | - self.tc.SetItemPyData( my_itemid, [ self.tc.GetCount() - 1, newxml, self.uniqid ] ) | |
1152 | - self.uniqid += 1 | |
1153 | - self.createXmlTreeRecursive( self.copybuffer, my_itemid ) | |
1181 | + my_itemids.append( self.appendElement( names[0], itemid ) ) | |
1182 | + for i in range( 1, numadd ): | |
1183 | + my_itemids.append( self.appendElement( names[i], my_itemids[-1] ) ) | |
1184 | + # convert | |
1185 | + myxmls = [] | |
1186 | + for i in range( 0, numadd ): | |
1187 | + myxmls.append( WMXmlElement( newxmls[i] ) ) | |
1188 | + # use new uniq id | |
1189 | + self.tc.SetItemPyData( my_itemids[i], [ self.tc.GetCount() - 1, myxmls[-1], self.uniqid ] ) | |
1190 | + self.uniqid += 1 | |
1191 | + self.createXmlTreeRecursive( self.copybuffer[i], my_itemids[i] ) | |
1154 | 1192 | self.createListCtrlFromTreeData() |
1155 | - # add to the undo queue | |
1156 | - self.addActionToUndoQueue( ActionAdd( self.gatherUniqIds( my_itemid ), 0, my_itemid, newxml ) ) | |
1193 | + | |
1194 | + if numadd == 1: | |
1195 | + # add to the undo queue | |
1196 | + self.addActionToUndoQueue( ActionAdd( self.gatherUniqIds( my_itemids[0] ), 0, my_itemids[0], myxmls[0] ) ) | |
1197 | + else: | |
1198 | + uniqids = [] | |
1199 | + for i in range( 0, numadd ): | |
1200 | + uniqids.append( self.gatherUniqIds( my_itemids[i] ) ) | |
1201 | + self.addActionToUndoQueue( ActionAdds( uniqids, 0, my_itemids, myxmls ) ) | |
1157 | 1202 | self.IsSaved = False |
1203 | + # focus on newly created item | |
1204 | + self.focusTreeCtrlItem( my_itemids[-1] ) | |
1158 | 1205 | |
1159 | - def evPopupMenuDelete( self, event ): | |
1206 | + def focusTreeCtrlItem( self, itemid ): | |
1207 | + # TreeCtrl | |
1208 | + self.tc.UnselectAll() | |
1209 | + self.tc.SelectItem( itemid ) | |
1210 | + # ListCtrl | |
1211 | + index = self.tc.GetItemPyData( itemid )[0] | |
1212 | + self.deselectAllItemsListCtrl() | |
1213 | + self.selectListCtrlItems( index ) | |
1214 | + self.lc.Focus( index ) | |
1215 | + | |
1216 | + # preprocess for deleting item | |
1217 | + def preRemove( self ): | |
1160 | 1218 | if not self.popupIsItemAvailable( self.lc_editing_index ): |
1161 | 1219 | return |
1162 | 1220 | myindex = self.lc_editing_index |
1163 | - treeitemid = self.l2t_correspondence[myindex] | |
1164 | - # save action | |
1165 | - pos = self.calculateItemPosition( treeitemid ) | |
1166 | - if not self.tc.GetNextSibling( treeitemid ): | |
1221 | + itemid = self.l2t_correspondence[myindex] | |
1222 | + # remember item position for undoing | |
1223 | + pos = self.calculateItemPosition( itemid ) | |
1224 | + if not self.tc.GetNextSibling( itemid ): | |
1167 | 1225 | pos = -1 |
1168 | 1226 | ## we need Element, not WMXmlElement |
1169 | - myxml, uniqids = self.createElementTreeFromTreeCtrl( treeitemid ) | |
1227 | + myxml, uniqids = self.createElementTreeFromTreeCtrl( itemid ) | |
1170 | 1228 | myxml = myxml.getroot() |
1171 | - parent = self.tc.GetItemParent(treeitemid) | |
1229 | + parent = self.tc.GetItemParent( itemid ) | |
1172 | 1230 | if parent.IsOk(): |
1173 | 1231 | uniqids.insert( 0, self.tc.GetItemPyData(parent)[2] ) |
1174 | 1232 | else: |
1175 | 1233 | uniqids.insert( 0, None ) |
1176 | - #myxml = self.tc.GetItemPyData( treeitemid )[1] | |
1234 | + return uniqids, pos, itemid, myxml | |
1235 | + | |
1236 | + def evPopupMenuDelete( self, event ): | |
1237 | + if not self.popupIsItemAvailable( self.lc_editing_index ): | |
1238 | + return | |
1239 | + if len( self.lc_editing_indexes ) > 1: | |
1240 | + self.deleteItemMultiple() | |
1241 | + else: | |
1242 | + self.deleteItem() | |
1243 | + | |
1244 | + def deleteItem( self ): | |
1245 | + uniqids, pos, itemid, myxml = self.preRemove() | |
1246 | + # find next item | |
1247 | + nextitem = self.tc.GetNextSibling(itemid) | |
1248 | + parent = self.tc.GetItemParent(itemid) | |
1177 | 1249 | # remove item |
1178 | - if self.deleteTreeCtrlItem( treeitemid ): | |
1250 | + if self.deleteTreeCtrlItem( itemid ): | |
1179 | 1251 | # add to undo queue |
1180 | 1252 | self.addActionToUndoQueue( ActionDel( uniqids, pos, None, myxml ) ) |
1181 | 1253 | # store removed data for undo |
@@ -1183,17 +1255,59 @@ class WMXmlEditor( wx.App ): | ||
1183 | 1255 | self.IsSaved = False |
1184 | 1256 | # force update data |
1185 | 1257 | self.createListCtrlFromTreeData() |
1186 | - | |
1187 | - def evPopupMenuDeleteSelected( self, event ): | |
1188 | - if not self.popupIsItemAvailable( self.lc_editing_index ): | |
1189 | - return | |
1258 | + if nextitem.IsOk(): | |
1259 | + self.focusTreeCtrlItem( nextitem ) | |
1260 | + elif parent.IsOk(): | |
1261 | + self.focusTreeCtrlItem( parent ) | |
1262 | + | |
1263 | + def deleteItemMultiple( self ): | |
1264 | + # remove nested indexes | |
1265 | + self.removeNestedIndexes() | |
1190 | 1266 | # sort indexes |
1191 | 1267 | self.lc_editing_indexes.sort() |
1192 | 1268 | self.lc_editing_indexes.reverse() |
1193 | 1269 | # use self.lc_editing_indexes to delete selected items |
1270 | + uniqidss = [] | |
1271 | + poss = [] | |
1272 | + myxmls = [] | |
1194 | 1273 | for index in self.lc_editing_indexes: |
1195 | 1274 | self.lc_editing_index = index |
1196 | - self.evPopupMenuDelete( event ) | |
1275 | + uniqids, pos, itemid, myxml = self.preRemove() | |
1276 | + if self.deleteTreeCtrlItem( itemid ): | |
1277 | + uniqidss.append( uniqids ) | |
1278 | + poss.append( pos ) | |
1279 | + myxmls.append( myxml ) | |
1280 | + self.addActionToUndoQueue( ActionDels( uniqidss, poss, None, myxmls ) ) | |
1281 | + self.IsSaved = False | |
1282 | + self.createListCtrlFromTreeData() | |
1283 | + # do not focus next item here | |
1284 | + | |
1285 | + # remove nested elements from lc_editing_indexes | |
1286 | + def removeNestedIndexes( self ): | |
1287 | + num = len(self.lc_editing_indexes) | |
1288 | + itemids = [] | |
1289 | + for i in range( 0, num ): | |
1290 | + ind = self.lc_editing_indexes[i] | |
1291 | + ind = min( ind, self.l2l_correspondence[ind] ) | |
1292 | + itemids.append( self.l2t_correspondence[ind] ) | |
1293 | + self.lc_editing_indexes[i] = ind | |
1294 | + | |
1295 | + newone = [] | |
1296 | + for i in range( 0, num ): | |
1297 | + if not self.isNestedItem( itemids[i] ): | |
1298 | + newone.append( self.lc_editing_indexes[i] ) | |
1299 | + | |
1300 | + # replace to new data | |
1301 | + self.lc_editing_indexes = list(set(newone)) | |
1302 | + | |
1303 | + def isNestedItem( self, itemid ): | |
1304 | + parent = self.tc.GetItemParent( itemid ) | |
1305 | + while parent.IsOk(): | |
1306 | + ind = self.tc.GetItemPyData( parent )[0] | |
1307 | + if ind in self.lc_editing_indexes: | |
1308 | + return True | |
1309 | + parent = self.tc.GetItemParent( parent ) | |
1310 | + return False | |
1197 | 1311 | |
1198 | 1312 | def evPopupMenuInsert( self, event ): |
1199 | 1313 | if self.lc_editing_index == 0: |
@@ -678,10 +678,6 @@ | ||
678 | 678 | <label>Delete</label> |
679 | 679 | <help></help> |
680 | 680 | </object> |
681 | - <object class="wxMenuItem" name="rightClickMenuDeleteSelected"> | |
682 | - <label>Delete Selected</label> | |
683 | - <help></help> | |
684 | - </object> | |
685 | 681 | <object class="separator" /> |
686 | 682 | <object class="wxMenuItem" name="rightClickMenuInsert"> |
687 | 683 | <label>Insert</label> |