mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
440 lines
16 KiB
Python
Executable File
440 lines
16 KiB
Python
Executable File
"""@package OsmUndoRedoDW
|
|
This module holds evidence of user edit actions.
|
|
|
|
Such evidence exists for each loaded OSM data.
|
|
|
|
Module provides easy way how to call undo/redo actions.
|
|
"""
|
|
|
|
from PyQt4.QtCore import *
|
|
from PyQt4.QtGui import *
|
|
from qgis.core import *
|
|
from qgis.gui import *
|
|
|
|
from ui_OsmUndoRedoDW import Ui_OsmUndoRedoDW
|
|
from OsmDatabaseManager import OsmDatabaseManager
|
|
|
|
import sqlite3
|
|
from math import *
|
|
from time import *
|
|
|
|
|
|
|
|
class OsmUndoRedoDW(QDockWidget, Ui_OsmUndoRedoDW, object):
|
|
"""This class extends functionality of Ui_OsmUndoRedoDW dialog which displays history of user edit actions.
|
|
Such history exists for each loaded OSM data.
|
|
|
|
This class provides easy way how to call undo/redo actions.
|
|
"""
|
|
|
|
def __init__(self, plugin):
|
|
"""The constructor.
|
|
|
|
Does basic initialization, connecting dialog signals to appropriate slots
|
|
and setting icons to its buttons.
|
|
|
|
@param plugin pointer to OSM Plugin instance
|
|
"""
|
|
|
|
QDockWidget.__init__(self, None)
|
|
|
|
self.setupUi(self)
|
|
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
|
|
|
self.undoButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_undo.png"))
|
|
self.redoButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_redo.png"))
|
|
self.clearButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_clearUndoRedo.png"))
|
|
|
|
self.canvas=plugin.canvas
|
|
self.iface=plugin.iface
|
|
self.dbm=plugin.dbm
|
|
self.plugin=plugin
|
|
|
|
self.actionList.setDragDropMode(QAbstractItemView.NoDragDrop)
|
|
|
|
QObject.connect(self.undoButton,SIGNAL("clicked()"),self.undo)
|
|
QObject.connect(self.redoButton,SIGNAL("clicked()"),self.redo)
|
|
QObject.connect(self.clearButton,SIGNAL("clicked()"),self.clear)
|
|
QObject.connect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
|
|
# structures for evidence of user edit changes (required by undo and redo operations)
|
|
self.mapActions={}
|
|
self.mapIxAction={}
|
|
self.currentMapKey=None
|
|
|
|
self.actStartId=self.actStopId=self.actNote=None
|
|
self.actionInProgress=False
|
|
|
|
self.redoCounter=0
|
|
self.undoCounter=0
|
|
self.affected=set()
|
|
self.urIsBusy=False
|
|
|
|
self.actionList.addItem(QString("<empty>"))
|
|
self.actionList.setCurrentRow(0)
|
|
|
|
|
|
def currRowChanged(self,row):
|
|
"""This function is called after currentRowChanged(...) signal is emmited on dialog's list of edit actions.
|
|
Functions calls as many undo actions (as many redo actions) as needed to jump in editing history to selected row.
|
|
|
|
@param row row number from the list of edit actions
|
|
"""
|
|
|
|
if row<0:
|
|
return
|
|
self.goToAction(row)
|
|
|
|
|
|
def setContentEnabled(self,flag):
|
|
|
|
if flag:
|
|
if self.undoCounter>0:
|
|
self.undoButton.setEnabled(True)
|
|
if self.redoCounter>0:
|
|
self.redoButton.setEnabled(True)
|
|
else:
|
|
self.undoButton.setEnabled(False)
|
|
self.redoButton.setEnabled(False)
|
|
|
|
self.clearButton.setEnabled(flag)
|
|
self.actionList.setEnabled(flag)
|
|
|
|
|
|
def clear(self):
|
|
"""Function clears (re-initializes) the whole undo/redo dialog.
|
|
"""
|
|
|
|
if self.dbm and self.dbm.currentKey:
|
|
self.dbm.removeAllChangeSteps()
|
|
|
|
self.actionList.clear()
|
|
self.actStartId=self.actStopId=self.actNote=None
|
|
self.actionInProgress=False
|
|
self.urIsBusy=False
|
|
|
|
self.actionList.addItem(QString("<empty>"))
|
|
self.actionList.setCurrentRow(0)
|
|
|
|
self.redoCounter=0
|
|
self.redoButton.setEnabled(False)
|
|
self.undoCounter=0
|
|
self.undoButton.setEnabled(False)
|
|
|
|
if self.plugin:
|
|
if self.plugin.dockWidget:
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.undoButton.setEnabled(False)
|
|
|
|
if self.currentMapKey:
|
|
self.mapActions[self.currentMapKey]=[]
|
|
self.mapIxAction[self.currentMapKey]=-1
|
|
|
|
|
|
def databaseChanged(self,dbKey):
|
|
"""Functions is called when current database of OSM Plugin changed.
|
|
|
|
OSM Undo/Redo module clears its list of actions and loads the one for new current database.
|
|
If dbKey parameter is None, there is no current database. In this case function just clears the list
|
|
and reinitialize its inner structures.
|
|
|
|
@param dbKey key/name of new current database file
|
|
"""
|
|
|
|
# clear the list widget
|
|
self.actionList.clear()
|
|
|
|
# clear inner structures
|
|
self.actStartId=self.actStopId=self.actNote=None
|
|
self.actionInProgress=False
|
|
self.currentMapKey=dbKey
|
|
self.urIsBusy=False
|
|
|
|
if not dbKey:
|
|
self.setContentEnabled(False)
|
|
return
|
|
|
|
self.setContentEnabled(True)
|
|
if dbKey in self.mapActions.keys():
|
|
|
|
# load the list widget
|
|
self.redoCounter=0
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
self.undoCounter=0
|
|
self.undoButton.setEnabled(False)
|
|
self.plugin.dockWidget.undoButton.setEnabled(False)
|
|
|
|
self.actionList.addItem(QString("<empty>"))
|
|
|
|
for action in self.mapActions[self.currentMapKey]:
|
|
self.actionList.addItem(action[2]) # 2 ~ actionNote!
|
|
|
|
ixAction=self.mapIxAction[self.currentMapKey]
|
|
self.undoCounter=ixAction+1
|
|
self.redoCounter=len(self.mapActions[self.currentMapKey])-self.undoCounter
|
|
self.actionList.setCurrentRow(ixAction+1)
|
|
|
|
if self.undoCounter>0:
|
|
self.undoButton.setEnabled(True)
|
|
self.plugin.dockWidget.undoButton.setEnabled(True)
|
|
if self.redoCounter>0:
|
|
self.redoButton.setEnabled(True)
|
|
self.plugin.dockWidget.redoButton.setEnabled(True)
|
|
return
|
|
|
|
# new dbKey has no undo/redo history yet
|
|
self.actionList.addItem(QString("<empty>"))
|
|
self.actionList.setCurrentRow(0)
|
|
|
|
self.redoCounter=0
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
self.undoCounter=0
|
|
self.undoButton.setEnabled(False)
|
|
self.plugin.dockWidget.undoButton.setEnabled(False)
|
|
|
|
self.mapActions[dbKey]=[]
|
|
self.mapIxAction[dbKey]=-1
|
|
|
|
|
|
def goToAction(self,row):
|
|
"""Functions goes to the selected row in history of edit actions.
|
|
It calls as many undo/redo operations, as needed.
|
|
|
|
@param row row index to list of edit actions history
|
|
"""
|
|
curr=self.actionList.currentRow()
|
|
self.actionList.setEnabled(False)
|
|
|
|
if not self.currentMapKey in self.mapIxAction:
|
|
return
|
|
|
|
ixGoto=row # ix of row which was clicked
|
|
ixCurrent=self.mapIxAction[self.currentMapKey]+1 # current action index
|
|
|
|
# how many undo/redo actions are necessary?
|
|
howFar=0
|
|
self.affected=set()
|
|
|
|
if ixCurrent<ixGoto:
|
|
# redo actions; we move "whitespace item" behind the row which was clicked
|
|
howFar=ixGoto-ixCurrent
|
|
for ix in range(0,howFar):
|
|
self.redo(False)
|
|
elif ixCurrent>ixGoto:
|
|
# undo actions; we move "whitespace item" before the row which was clicked
|
|
howFar=ixCurrent-ixGoto
|
|
for ix in range(0,howFar):
|
|
self.undo(False)
|
|
else:
|
|
self.actionList.setEnabled(True)
|
|
QObject.disconnect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
self.actionList.setCurrentRow(curr)
|
|
QObject.connect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
return
|
|
|
|
self.dbm.commit()
|
|
self.dbm.recacheAffectedNow(self.affected)
|
|
self.affected=set()
|
|
self.canvas.refresh()
|
|
|
|
if self.plugin.dockWidget:
|
|
lFeat=self.plugin.dockWidget.feature
|
|
lFeatType=self.plugin.dockWidget.featureType
|
|
self.plugin.dockWidget.loadFeature(lFeat,lFeatType)
|
|
|
|
self.actionList.setEnabled(True)
|
|
QObject.disconnect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
self.actionList.setCurrentRow(curr)
|
|
QObject.connect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
|
|
|
|
def startAction(self,actNote):
|
|
"""Function remembers current state of system.
|
|
|
|
This function is called before performing an action that should be put into editing history.
|
|
It's expected that you call stopAction() function after edit actions finishes.
|
|
Then new record in history will be created.
|
|
|
|
@param actNote action description (in brief)
|
|
"""
|
|
|
|
if not self.dbm.currentKey:
|
|
# there is no current database
|
|
return
|
|
|
|
if self.actionInProgress:
|
|
print "failed! change in progress!"
|
|
return
|
|
|
|
self.actionInProgress=True
|
|
self.actStartId=self.dbm.getCurrentActionNumber()
|
|
self.actStopId=None
|
|
self.actNote=actNote
|
|
|
|
|
|
def stopAction(self,affected=set()):
|
|
"""Function is called after an edit action. It stores current state of system
|
|
and the state from last calling of startAction(). This two states of system
|
|
are considered new edit history record together and are stored in database.
|
|
|
|
@param affected list of all OSM features that was affected with just finished action"""
|
|
|
|
if not self.dbm.currentKey:
|
|
# there is no current database
|
|
return
|
|
|
|
if affected==None:
|
|
affected=set()
|
|
|
|
self.actStopId=self.dbm.getCurrentActionNumber()
|
|
if self.actStopId<=self.actStartId:
|
|
self.actionInProgress=False
|
|
return
|
|
|
|
for ix in range(len(self.mapActions[self.currentMapKey]),self.actionList.currentRow(),-1):
|
|
self.actionList.takeItem(ix)
|
|
self.redoCounter=self.redoCounter-1
|
|
|
|
ixAction=self.mapIxAction[self.currentMapKey]
|
|
cntActions=len(self.mapActions[self.currentMapKey])
|
|
if ixAction>-1:
|
|
fromId=self.mapActions[self.currentMapKey][ixAction][1]+1
|
|
self.dbm.removeChangeStepsBetween(fromId,self.actStartId)
|
|
|
|
del self.mapActions[self.currentMapKey][ixAction+1:cntActions]
|
|
|
|
if self.redoCounter==0:
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
|
|
self.mapIxAction[self.currentMapKey]=ixAction+1
|
|
ixAction=ixAction+1
|
|
|
|
# increase undo counter
|
|
self.undoCounter=self.undoCounter+1
|
|
self.undoButton.setEnabled(True)
|
|
self.plugin.dockWidget.undoButton.setEnabled(True)
|
|
|
|
self.mapActions[self.currentMapKey].append((self.actStartId,self.actStopId,self.actNote,affected))
|
|
self.actionList.addItem(self.actNote)
|
|
self.actionList.setCurrentRow(ixAction+1)
|
|
|
|
self.actStartId=self.actStopId=self.actNote=None
|
|
self.actionInProgress=False
|
|
|
|
|
|
def undo(self,standAlone=True):
|
|
"""Functions performs exactly one undo operation in system.
|
|
|
|
Last edit action is reverted, list of editing history on undo/redo dialog
|
|
shifts its current row up.
|
|
|
|
@param refresh if False, no canvas refresh will be performed after reverting an action; default is True
|
|
"""
|
|
|
|
self.undoButton.setEnabled(False)
|
|
self.plugin.dockWidget.undoButton.setEnabled(False)
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
|
|
(startId,stopId,note,affected)=self.mapActions[self.currentMapKey][self.mapIxAction[self.currentMapKey]]
|
|
|
|
# shift up in the list widget
|
|
ixCurrent=self.actionList.currentRow()
|
|
QObject.disconnect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
self.actionList.setCurrentRow(ixCurrent-1)
|
|
QObject.connect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
|
|
self.mapIxAction[self.currentMapKey]=self.mapIxAction[self.currentMapKey]-1
|
|
|
|
changeSteps=self.dbm.getChangeSteps(startId,stopId)
|
|
|
|
for (change_type,tab_name,row_id,col_name,old_value,new_value) in changeSteps:
|
|
|
|
if change_type=='I':
|
|
self.dbm.setRowDeleted(tab_name,row_id)
|
|
elif change_type=='D':
|
|
self.dbm.setRowNotDeleted(tab_name,row_id)
|
|
elif change_type=='U':
|
|
self.dbm.setRowColumnValue(tab_name,col_name,old_value,row_id)
|
|
|
|
# increase redo counter
|
|
self.redoCounter=self.redoCounter+1
|
|
# decrease undo counter
|
|
self.undoCounter=self.undoCounter-1
|
|
|
|
# refresh
|
|
if standAlone:
|
|
self.dbm.commit()
|
|
self.dbm.recacheAffectedNow(affected)
|
|
self.canvas.refresh()
|
|
self.plugin.dockWidget.loadFeature(self.plugin.dockWidget.feature,self.plugin.dockWidget.featureType)
|
|
else:
|
|
self.affected.update(affected)
|
|
|
|
self.redoButton.setEnabled(True)
|
|
self.plugin.dockWidget.redoButton.setEnabled(True)
|
|
if self.undoCounter>0:
|
|
self.undoButton.setEnabled(True)
|
|
self.plugin.dockWidget.undoButton.setEnabled(True)
|
|
|
|
|
|
def redo(self,standAlone=True):
|
|
"""Functions performs exactly one redo operation in system.
|
|
|
|
Last reverted edit action is redone again. List of editing history on undo/redo dialog
|
|
shifts its current row down.
|
|
|
|
@param refresh if False, no canvas refresh will be performed after redo action; default is True
|
|
"""
|
|
|
|
self.undoButton.setEnabled(False)
|
|
self.plugin.dockWidget.undoButton.setEnabled(False)
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
|
|
(startId,stopId,note,affected)=self.mapActions[self.currentMapKey][self.mapIxAction[self.currentMapKey]+1]
|
|
|
|
# shift down in the list widget
|
|
ixCurrent=self.actionList.currentRow()
|
|
QObject.disconnect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
self.actionList.setCurrentRow(ixCurrent+1)
|
|
QObject.connect(self.actionList,SIGNAL("currentRowChanged(int)"),self.currRowChanged)
|
|
|
|
self.mapIxAction[self.currentMapKey]=self.mapIxAction[self.currentMapKey]+1
|
|
if self.mapIxAction[self.currentMapKey]==len(self.mapActions[self.currentMapKey]):
|
|
self.redoButton.setEnabled(False)
|
|
self.plugin.dockWidget.redoButton.setEnabled(False)
|
|
|
|
changeSteps=self.dbm.getChangeSteps(startId,stopId)
|
|
for (change_type,tab_name,row_id,col_name,old_value,new_value) in changeSteps:
|
|
|
|
if change_type=='I':
|
|
self.dbm.setRowNotDeleted(tab_name,row_id)
|
|
elif change_type=='D':
|
|
self.dbm.setRowDeleted(tab_name,row_id)
|
|
elif change_type=='U':
|
|
self.dbm.setRowColumnValue(tab_name,col_name,new_value,row_id)
|
|
|
|
# decrease redo counter
|
|
self.redoCounter=self.redoCounter-1
|
|
# increase undo counter
|
|
self.undoCounter=self.undoCounter+1
|
|
|
|
# refresh
|
|
if standAlone:
|
|
self.dbm.commit()
|
|
self.dbm.recacheAffectedNow(affected)
|
|
self.canvas.refresh()
|
|
self.plugin.dockWidget.loadFeature(self.plugin.dockWidget.feature,self.plugin.dockWidget.featureType)
|
|
else:
|
|
self.affected.update(affected)
|
|
|
|
self.undoButton.setEnabled(True)
|
|
self.plugin.dockWidget.undoButton.setEnabled(True)
|
|
if self.redoCounter>0:
|
|
self.redoButton.setEnabled(True)
|
|
self.plugin.dockWidget.redoButton.setEnabled(True)
|