mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			442 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			442 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)
 | 
						|
 | 
						|
 |