def __init__(self):
        super(MainEditor, self).__init__()
        self.setWindowTitle('Lex Talionis Palette Editor v5.9.0')
        self.setMinimumSize(640, 480)

        self.grid = QGridLayout()
        self.setLayout(self.grid)

        self.main_view = MainView(self)
        self.menu_bar = QMenuBar(self)
        self.palette_list = PaletteList.PaletteList(self)
        self.image_map_list = ImageMap.ImageMapList()
        self.scripts = []

        self.undo_stack = QUndoStack(self)

        self.create_menu_bar()

        self.grid.setMenuBar(self.menu_bar)
        self.grid.addWidget(self.main_view, 0, 0)
        self.grid.addWidget(self.palette_list, 0, 1, 2, 1)
        self.info_form = QFormLayout()
        self.grid.addLayout(self.info_form, 1, 0)

        self.create_info_bars()
        self.clear_info()
    def __init__(self):
        super().__init__()

        self.resize(600, 400)

        self.mUndoStack = QUndoStack(self)
        self.mUndoStack.setUndoLimit(100)

        self.mUndoStack.canUndoChanged.connect(self.can_undo_changed)
        self.mUndoStack.canRedoChanged.connect(self.can_redo_changed)

        self.actionUndo = self.menuBar().addAction("Undo")
        self.actionUndo.triggered.connect(self.mUndoStack.undo)

        self.actionRedo = self.menuBar().addAction("Redo")
        self.actionRedo.triggered.connect(self.mUndoStack.redo)

        self.can_undo_changed(self.mUndoStack.canUndo())
        self.can_redo_changed(self.mUndoStack.canRedo())

        self.image = QImage(self.size(), QImage.Format_ARGB32)
        self.image.fill(Qt.transparent)

        self.start_pos = None
        self.end_pos = None
        self.is_pressed = False
    def __init__(self):
        QWidget.__init__(self)
        self.filename = ""
        self.undoStack = QUndoStack()
        self.undo = FlatButton(":/images/undo_normal.png",
                               ":/images/undo_hover.png", "",
                               ":/images/undo_disabled.png")
        self.redo = FlatButton(":/images/redo_normal.png",
                               ":/images/redo_hover.png", "",
                               ":/images/redo_disabled.png")
        self.undo.setToolTip("Undo")
        self.redo.setToolTip("Redo")
        self.undo.setEnabled(False)
        self.redo.setEnabled(False)
        hbox = QHBoxLayout()
        hbox.addStretch(0)
        hbox.addWidget(self.undo)
        hbox.addWidget(self.redo)

        self.titleLabel = QLabel()
        fnt = self.titleLabel.font()
        fnt.setPointSize(20)
        fnt.setBold(True)
        self.titleLabel.setFont(fnt)
        self.layout = QGridLayout()
        self.layout.addWidget(self.titleLabel, 0, 0)
        self.layout.addLayout(hbox, 0, 2)
        self.setLayout(self.layout)
Esempio n. 4
0
    def __init__(self, document, mu_page, index):
        assert isinstance(document, GMDoc)
        assert isinstance(mu_page, fitz.Page)
        assert type(index) is int

        self._doc = document
        self._index = index

        self._mu_page = mu_page

        # zoom in to get higher resolution pixmap
        # todo: add this param to preferences dialog
        zoom = 2
        matrix = fitz.Matrix(zoom, zoom)

        page_data = self._mu_page.getPixmap(matrix=matrix, alpha=False).getImageData()

        self._undo_stack = QUndoStack()

        self._page_image = QPixmap()
        self._page_image.loadFromData(page_data)
        self._drawing_image = generateDrawingPixmap(self._page_image)
        self._last_drawing_hash = None

        self.captureState()
Esempio n. 5
0
    def __init__(self, parent: QWidget=None):
        super().__init__( parent )

        self.parentWidget = parent
        self.domainModel = Manager()

        self.undoStack = QUndoStack(self)
Esempio n. 6
0
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self.parentWidget = parent

        self.userContainer = UserContainer()  ## user data

        self.undoStack = QUndoStack(self)
Esempio n. 7
0
    def __init__(self, application):
        super().__init__()

        self._loading = True
        self.application = application
        self.settings = Settings(self)
        self.workflows = Workflows(self)
        self.undo_stack = QUndoStack(self)
        self.refresh_timer = QTimer()

        self._init_widgets()

        self.main_window_actions = MainWindowActions(self)

        self._init_window()
        self._init_toolbars()

        self.settings.restore()
        if self.settings.signature_key is None:
            self.settings.signature_key = genkey()

        self.show()
        self._update_action_toggles()

        # Update the GUI so that everything matches the model
        cell_attributes = self.grid.model.code_array.cell_attributes
        attributes = cell_attributes[self.grid.current]
        self.on_gui_update(attributes)

        self._loading = False
        self._previous_window_state = self.windowState()
Esempio n. 8
0
    def __init__(self, mainwindow, parent=None):
        """
        Initialize the diagram scene.
        :type mainwindow: MainWindow
        :type parent: QWidget
        """
        super().__init__(parent)
        self.document = File(parent=self)
        self.guid = GUID(self)
        self.factory = ItemFactory(self)
        self.index = ItemIndex(self)
        self.meta = PredicateMetaIndex(self)
        self.undostack = QUndoStack(self)
        self.undostack.setUndoLimit(50)
        self.validator = OWL2Validator(self)
        self.mainwindow = mainwindow
        self.pasteOffsetX = Clipboard.PasteOffsetX
        self.pasteOffsetY = Clipboard.PasteOffsetY
        self.mode = DiagramMode.Idle
        self.modeParam = Item.Undefined
        self.mouseOverNode = None
        self.mousePressEdge = None
        self.mousePressPos = None
        self.mousePressNode = None
        self.mousePressNodePos = None
        self.mousePressData = {}

        connect(self.sgnItemAdded, self.index.add)
        connect(self.sgnItemRemoved, self.index.remove)
Esempio n. 9
0
    def __init__(self, participants, parent=None):
        super().__init__(parent)
        self.controller = None  # :type: CompareFrameController|GeneratorTabController
        self.protocol = None  # type: ProtocolAnalyzer

        self.col_count = 0
        self.row_count = 0
        self.display_data = None  # type: list[str]

        self.search_results = []
        self.search_value = ""
        self._proto_view = 0
        self._refindex = -1

        self.first_messages = []
        self.hidden_rows = set()

        self.is_writeable = False
        self.locked = False
        self.decode = True  # False for Generator

        self.background_colors = defaultdict(lambda: None)
        self.bold_fonts = defaultdict(lambda: False)
        self.italic_fonts = defaultdict(lambda: False)
        self.text_colors = defaultdict(lambda: None)
        self.vertical_header_text = defaultdict(lambda: None)
        self.vertical_header_colors = defaultdict(lambda: None)

        self._diffs = defaultdict(set)  # type: dict[int, set[int]]

        self.undo_stack = QUndoStack()

        self.__participants = participants
Esempio n. 10
0
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.undo_stack = QUndoStack(self)
        self.game_name = data["gameName"]

        self.sprite_pixel_palettes = PixelPalettes(
            data["sprites"], Source.SPRITE
        )
        self.tile_pixel_palettes = PixelPalettes(data["tiles"], Source.TILE)
        self.sprite_color_palettes = ColorPalettes(
            data["spriteColorPalettes"], Source.SPRITE
        )
        self.tile_color_palettes = ColorPalettes(
            data["tileColorPalettes"], Source.TILE
        )
        self.sprite_pixel_palettes.batch_updated.connect(
            self.pix_batch_updated
        )
        self.tile_pixel_palettes.batch_updated.connect(self.pix_batch_updated)
        self.sprite_color_palettes.color_changed.connect(self.col_pal_updated)
        self.tile_color_palettes.color_changed.connect(self.col_pal_updated)
        self.sprite_pixel_palettes.row_count_updated.connect(
            self.row_count_updated
        )
        self.tile_pixel_palettes.row_count_updated.connect(
            self.row_count_updated
        )
        self.sprite_color_palettes.name_changed.connect(self.col_pal_renamed)
        self.tile_color_palettes.name_changed.connect(self.col_pal_renamed)
        self.sprite_color_palettes.palette_added.connect(self.col_pal_added)
        self.tile_color_palettes.palette_added.connect(self.col_pal_added)
        self.sprite_color_palettes.palette_removed.connect(
            self.col_pal_removed
        )
        self.tile_color_palettes.palette_removed.connect(self.col_pal_removed)
Esempio n. 11
0
    def __init__(self, parent):
        super(TableView, self).__init__(parent=parent)

        self.setShowGrid(False)
        self.setAlternatingRowColors(True)

        # self.setDragEnabled(True)
        # self.setDropIndicatorShown(True)
        # self.setDragDropOverwriteMode(False)
        # self.setAcceptDrops(True)
        # self.viewport().setAcceptDrops(True)

        # self.setDragDropMode(QAbstractItemView.InternalMove)

        # self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        # self.verticalHeader().setMinimumWidth(40)
        # self.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
        # QHeaderView.setsize
        self.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.matches = []
        self.undoStack = QUndoStack()
        self.dialog = SearchAndReplace(self)
        self.dialog.nextBotton.clicked.connect(self.nextMatch)
        self.dialog.caseSensitive.stateChanged.connect(self.resetMatches)
        self.dialog.regxp.stateChanged.connect(self.resetMatches)
        self.dialog.textToSearch.textChanged.connect(self.resetMatches)
        self.dialog.repalceBotton.clicked.connect(self.replace)
        self.dialog.repalceAllButton.clicked.connect(self.replaceAll)
        self.current_match = None
Esempio n. 12
0
    def __init__(self, model, *args):
        super(GridTableView, self).__init__(*args)

        #self.path = path

        self.model = model
        self.setModel(model)

        self.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.verticalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.horizontalHeader().customContextMenuRequested['QPoint'].connect(
            self.OnCustomContextMenuRequestedH)
        self.verticalHeader().customContextMenuRequested['QPoint'].connect(
            self.OnCustomContextMenuRequestedV)
        self.setItemDelegate(TableViewDelegate(self))
        self.verticalHeader().setSectionsMovable(True)

        self.setRowHeight(1, 150)

        self.undoStack = QUndoStack()
        self.undoStackIndex = 0

        self.undoStack.indexChanged.connect(self.OnUndoStackIndexChanged)

        self.setCornerButtonEnabled(False)
Esempio n. 13
0
 def _refund(self, child: PhysicalPrinting, undo_stack: QUndoStack):
     undo_stack.push(child.scene().get_cube_modification(
         add=child.values['tickets_payed'],
         remove=(child, ),
         position=child.pos() + QPoint(1, 1),
         closed_operation=True,
     ))
Esempio n. 14
0
    def __init__(self, parent):
        super(mylabel, self).__init__(parent)
        self.image = QImage()
        self.drawing = True
        self.lastPoint = QPoint()
        self.modified = False
        self.scribbling = False
        self.eraserSize = 5  #橡皮擦初始值
        self.fontSize = 12   #字形初始值
        self.setAcceptDrops(True)
        self.savetextedit = []
        self.text = [] #紀錄文字
        self.eraserPos = []
        self.temp_img = 1   #紀錄圖片編號
        self.numstack = []

        self.i = 0  # 紀錄textedit

        self.eraserClicked = False
        self.textClicked = False
        self.clearClicked = False

        self.undoStack = QUndoStack()
        self.m_undoaction = self.undoStack.createUndoAction(self, self.tr("&Undo"))
        # self.m_undoaction.setShortcut('Ctrl+Z')
        self.addAction(self.m_undoaction)
        self.m_redoaction = self.undoStack.createRedoAction(self, self.tr("&Redo"))
        # self.m_redoaction.setShortcut('Ctrl+Y')
        self.addAction(self.m_redoaction)
Esempio n. 15
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # undo
        # <http://doc.qt.io/qt-5/qtwidgets-tools-undoframework-example.html>
        self.undoStack = QUndoStack()
        self.ui.menuStyles.addSeparator()
        self.undoAction = self.undoStack.createUndoAction(self)
        self.undoAction.setShortcuts(QKeySequence.Undo)
        self.ui.menuStyles.addAction(self.undoAction)
        self.redoAction = self.undoStack.createRedoAction(self)
        self.redoAction.setShortcuts(QKeySequence.Redo)
        self.ui.menuStyles.addAction(self.redoAction)

        # constants
        self.chartPatternFilter = self.tr("ChartPattern (*.chartpattern)")

        try:
            self.XL = win32com.client.Dispatch("Excel.Application")
        except pythoncom.com_error:
            QMessageBox.warning(self, self.tr("Error"),
                                self.tr("Excel isn't installed."))
            sys.exit(0)

        # model
        self.pickedModel = models.ChartStyleModel(self)
        self.pickedDelegate = models.ChartPatternDelegate(self)
        self.ui.treeView.setModel(self.pickedModel)
        self.ui.treeView.setItemDelegate(self.pickedDelegate)
        if QT_VERSION_STR.startswith("5."):
            self.ui.treeView.header().setSectionsMovable(False)
        else:
            self.ui.treeView.header().setMovable(False)
        self.targetModel = QStringListModel([
            self.tr("ActiveBook"),
            self.tr("ActiveSheet"),
        ], self)
        self.typeModel = QStringListModel([
            self.tr("All Chart"),
            self.tr("Type of ActiveChart"),
        ], self)
        self.ui.comboBoxTarget.setModel(self.targetModel)
        self.ui.comboBoxType.setModel(self.typeModel)
        self.pickedModel.setUndoStack(self.undoStack)

        # general_progress
        self.general_progress = QProgressBar()
        self.ui.statusbar.addWidget(self.general_progress)
        self.general_progress.setVisible(False)
        # progress & progObj
        self.progress = QProgressBar()
        self.ui.statusbar.addWidget(self.progress)
        self.progress.setVisible(False)
        self.progObj = QtProgressObject()
        self.progObj.initialized.connect(self.on_progress_initialized)
        self.progObj.updated.connect(self.on_progress_updated)
        self.progObj.finished.connect(self.on_progress_finished)
Esempio n. 16
0
    def initUndo(self):
        self.undoStack = QUndoStack(self)
        
        shortcut = QShortcut(QKeySequence.Undo, self)
        shortcut.activated.connect(self.undoStack.undo)

        shortcut = QShortcut(QKeySequence.Redo, self)
        shortcut.activated.connect(self.undoStack.redo)
Esempio n. 17
0
    def __init__(self, filename=None):
        super(App, self).__init__()
        self.is_write = False
        self.backup_file = os.path.join(os.getcwd(), '.tmp.json')
        self.setWindowTitle(GUI_NAME)
        self.filedialog = FileDialog(self)
        # self.table = Table(self)
        self.address_space = {}
        self.info = InfoDialog(title=GUI_NAME, parent=self)
        self.threadpool = QThreadPool()
        self.data = {'project': '', 'top_module': '', 'blocks': []}
        self.undoStack = QUndoStack()
        self.treeUndoStack = QUndoStack()
        self.treeUndoStack.setUndoLimit(50)

        self.tree = BlockView(parent=self,
                              cols=register_columns,
                              blocks=self.data['blocks'],
                              undoStack=self.treeUndoStack)
        self.table = FieldView(parent=self,
                               cols=field_columns,
                               items=[],
                               undoStack=self.undoStack)
        self.tree.selectionChanged.connect(self.createTable)

        self.hbox = QHBoxLayout()
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(self.tree)
        splitter.addWidget(self.table)
        splitter.setStretchFactor(0, 2)
        splitter.setStretchFactor(1, 5)
        self.hbox.addWidget(splitter)
        self.setCentralWidget(QWidget(self))
        self.tabs = TabLayout(self)
        self.tabs.setContentsMargins(0, 0, 0, 0)
        tab1 = QWidget(self)
        tab1.setLayout(self.hbox)
        self.analyzer = AnalyzerWapper(parent=self,
                                       cols=field_columns,
                                       address_space=self.address_space,
                                       reload=self.reload_address)
        self.tabs.setTab(tab1, title='RegisterProfile')
        self.tabs.setTab(self.analyzer, title='RegisterAnalyzer')

        self.setCentralWidget(self.tabs)
        self.menubar = self.menuBar()
        self.create_ui()
        if self.check_backup():
            self.loadFiles([self.backup_file])
        else:
            if filename:
                self.loadFiles([filename])

        # self.table.tableSaved.connect(self.backUpFile)
        self.tree.addAnalyzerTrigger.connect(self.analyzer.add_analyzer)
        self.undoStack.indexChanged.connect(self.backUpFile)
Esempio n. 18
0
    def __init__(self, map, fileName=QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map  ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(
            self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(
            self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(
            self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())
Esempio n. 19
0
 def resize(self, undo_stack: QUndoStack) -> None:
     dialog = GridResizeDialog(self)
     dialog.exec_()
     if dialog.accepted:
         undo_stack.push(
             GridResize(
                 self,
                 *reversed(dialog.get_values()),
             )
         )
Esempio n. 20
0
 def __initialize(self):
     """
     Private method to initialize some data.
     """
     self.__loaded = False
     self.__bookmarkRootNode = None
     self.__toolbar = None
     self.__menu = None
     self.__bookmarksModel = None
     self.__commands = QUndoStack()
Esempio n. 21
0
 def __init__(self):
     super().__init__()
     self.undoStack = QUndoStack(self)
     self.setDragMode(QGraphicsView.RubberBandDrag)
     self.scene = QGraphicsScene()
     self.scene.setSceneRect(0, 0, 500, 500)
     self.setScene(self.scene)
     self.tool = 'Valinta'
     self.show()
     self.draw = DrawItem(self)
Esempio n. 22
0
 def __init__(self, r, c, set_title):
     super().__init__(r, c)
     self.set_title = set_title
     self.check_change = True
     self.header_bold = False
     self.undo_stack = QUndoStack(self)
     self.init_cells()
     self.init_ui()
     self.installEventFilter(self)
     self.init_undo_cell_edits()
     return None
Esempio n. 23
0
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self.parentWidget = parent

        self.dataContainer = DataContainer()

        self.undoStack = QUndoStack(self)

        self.markersChanged.connect(self.updateMarkersFavGroup)

        self.favsGrpChanged.connect(self.updateAllFavsGroup)
        self.favsChanged.connect(self.updateAllFavsGroup)
Esempio n. 24
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.change_stacks = {}
        self.stack = QUndoStack()
        self.fpath = None
        self.px = QPixmap()
        self.active_label = PangoGraphic()
        self.active_com = CreateShape(PangoGraphic, QPointF(), PangoGraphic())

        self.tool = None
        self.tool_size = 10

        self.full_clear()
Esempio n. 25
0
 def __init__(self, window, new=True):
     super().__init__()
     if new:
         self._editable = True
         self._wheelZoom = True
         self._zoomFactor = (1.1, 1.1)
         self._zoomLimit = 20
         self._scale = (1.0, 1.0)
     self._window = window
     self.setDragMode(QGraphicsView.RubberBandDrag)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setMouseTracking(True)
     self._undoStack = QUndoStack()
     self._undoView = None
Esempio n. 26
0
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(IconEditorGrid, self).__init__(parent)

        self.setAttribute(Qt.WA_StaticContents)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.__curColor = Qt.black
        self.__zoom = 12
        self.__curTool = self.Pencil
        self.__startPos = QPoint()
        self.__endPos = QPoint()
        self.__dirty = False
        self.__selecting = False
        self.__selRect = QRect()
        self.__isPasting = False
        self.__clipboardSize = QSize()
        self.__pasteRect = QRect()

        self.__undoStack = QUndoStack(self)
        self.__currentUndoCmd = None

        self.__image = QImage(32, 32, QImage.Format_ARGB32)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.__markImage = QImage(self.__image)
        self.__markImage.fill(self.NoMarkColor.rgba())

        self.__compositingMode = QPainter.CompositionMode_SourceOver
        self.__lastPos = (-1, -1)

        self.__gridEnabled = True
        self.__selectionAvailable = False

        self.__initCursors()
        self.__initUndoTexts()

        self.setMouseTracking(True)

        self.__undoStack.canRedoChanged.connect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.connect(self.canUndoChanged)
        self.__undoStack.cleanChanged.connect(self.__cleanChanged)

        self.imageChanged.connect(self.__updatePreviewPixmap)
        QApplication.clipboard().dataChanged.connect(self.__checkClipboard)

        self.__checkClipboard()
Esempio n. 27
0
 def update_column_count(self, parent: QtWidgets.QWidget,
                         undo_stack: QUndoStack) -> None:
     amount, ok = QInputDialog.getInt(
         parent,
         'Choose new column count',
         '',
         self._columns,
         1,
         64,
     )
     if ok:
         undo_stack.push(SetColumnCount(
             self,
             amount,
         ))
Esempio n. 28
0
    def __init__(self, filepath: str = None, reset_settings: bool = False):
        """
        :param filepath: File path for inital file to be opened
        :param reset_settings: Ignore stored `QSettings` and use defaults

        """

        super().__init__()

        self._loading = True

        self.settings = Settings(self, reset_settings=reset_settings)
        self.workflows = Workflows(self)
        self.undo_stack = QUndoStack(self)
        self.refresh_timer = QTimer()

        self._init_widgets()

        self.main_window_actions = MainWindowActions(self)

        self._init_window()
        self._init_toolbars()

        self.settings.restore()
        if self.settings.signature_key is None:
            self.settings.signature_key = genkey()

        # Update recent files in the file menu
        self.menuBar().file_menu.history_submenu.update()

        # Update toolbar toggle checkboxes
        self.update_action_toggles()

        # Update the GUI so that everything matches the model
        cell_attributes = self.grid.model.code_array.cell_attributes
        attributes = cell_attributes[self.grid.current]
        self.on_gui_update(attributes)

        self._loading = False
        self._previous_window_state = self.windowState()

        # Open initial file if provided by the command line
        if filepath is not None:
            if self.workflows.filepath_open(filepath):
                self.workflows.update_main_window_title()
            else:
                msg = "File '{}' could not be opened.".format(filepath)
                self.statusBar().showMessage(msg)
Esempio n. 29
0
    def __init__(self, participants, parent=None):
        super().__init__(parent)
        self.controller = None  # :type: CompareFrameController|GeneratorTabController
        self.protocol = None  # type: ProtocolAnalyzer

        self.col_count = 0
        self.row_count = 0
        self.display_data = None  # type: list[str]

        self.search_results = []
        self.search_value = ""
        self._proto_view = 0
        self._refindex = -1

        self.first_messages = []
        self.hidden_rows = set()

        self.is_writeable = False
        self.locked = False
        self.decode = True  # False for Generator

        self.background_colors = defaultdict(lambda: None)
        self.bold_fonts = defaultdict(lambda: False)
        self.italic_fonts = defaultdict(lambda: False)
        self.text_colors = defaultdict(lambda: None)
        self.vertical_header_text = defaultdict(lambda: None)
        self.vertical_header_colors = defaultdict(lambda: None)

        self._diffs = defaultdict(set)  # type: dict[int, set[int]]

        self.undo_stack = QUndoStack()

        self.__participants = participants
Esempio n. 30
0
    def __init__(self, **kwargs):
        QGraphicsScene.__init__(self, **kwargs)

        # view linked to this scene, currently under mouse
        self.active_view = None
        # incremented for each move
        self.move_id = 0
        self._last_checked_move_id = 0
        self.signal_remove_selected_items.connect(self.remove_selected_items)

        self.move_restrictions_on = False
        """Whether objects moves should be restricted.
		
		.. glossary::
			move restrictions
				Some displacements can be restricted to certain directions.
				If ``move_restrictions_on`` is True, then the displacements are
				constrained to be along the `x` or `y` direction.
				The actual direction (`x` or `y`) should be the closest
				to the mouse displacement.
				For instance, if the mouse movement is mainly horizontal,
				then a :class:`~.qt.handles.PointHandle` would move along `x` only.
				
				This requires to store the initial position, at the beginning
				of the displacement. This is done by :meth:`reset_move` slots.
		"""

        # http://www.informit.com/articles/article.aspx?p=1187104&seqNum=3
        # recommends to set the parent
        # "so that PyQt is able to clean it up at the right time
        #  when the dialog box is destroyed"
        # but then there is a cycle => possible crashes on exit ?
        self.undo_stack = QUndoStack(parent=self)
Esempio n. 31
0
	def __init__(self, renderScene):
		super(MainManager, self).__init__();
		self.renderScene = renderScene;
		self.noPen = QPen(Qt.NoPen);
		self.bodies = {};
		self.bodyInstances = [];
		self.nameIndex = {};

		#Draw the axes
		self.axes = QGraphicsItemGroup();
		renderScene.addItem(self.axes);
		xAxis = QGraphicsItemGroup(self.axes);
		s1, s2 = 10, 4
		x1, y1, x2, y2 = -self.AXIS_LEN, 0, self.AXIS_LEN, 0
		QGraphicsLineItem(x1, y1, x2, y2, xAxis);
		QGraphicsLineItem(x2-s1, y2-s2, x2, y2, xAxis);
		QGraphicsLineItem(x2-s1, y2+s2, x2, y2, xAxis);
		x1, y1, x2, y2 = 0, -self.AXIS_LEN, 0, self.AXIS_LEN
		yAxis = QGraphicsItemGroup(self.axes);
		QGraphicsLineItem(x1, y1, x2, y2, yAxis);
		QGraphicsLineItem(x2-s2, -(y2-s1), x2, -y2, yAxis);
		QGraphicsLineItem(x2+s2, -(y2-s1), x2, -y2, yAxis);
		grid = GridItem(0, 0, self.UNITS_PER_METER, 100);
		renderScene.addItem(grid);
		for o in [self.axes, grid]:
			o.setZValue(-1000);		
		self.undoStack = QUndoStack(self);
 def initUndoRedo(self):
     self.undoStack = QUndoStack()
     temp = QDir(os.path.join(QDir.tempPath(), "FlatSiteBuilder"))
     if temp.exists():
         temp.removeRecursively()
     temp.setPath(QDir.tempPath())
     temp.mkdir("FlatSiteBuilder")
Esempio n. 33
0
 def __initialize(self):
     """
     Private method to initialize some data.
     """
     self.__loaded = False
     self.__bookmarkRootNode = None
     self.__toolbar = None
     self.__menu = None
     self.__bookmarksModel = None
     self.__commands = QUndoStack()
Esempio n. 34
0
    def __init__(self, map, fileName = QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())
Esempio n. 35
0
 def __init__(self, parent=None):
     """
     Constructor
     
     @param parent reference to the parent widget (QWidget)
     """
     super(IconEditorGrid, self).__init__(parent)
     
     self.setAttribute(Qt.WA_StaticContents)
     self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
     
     self.__curColor = Qt.black
     self.__zoom = 12
     self.__curTool = self.Pencil
     self.__startPos = QPoint()
     self.__endPos = QPoint()
     self.__dirty = False
     self.__selecting = False
     self.__selRect = QRect()
     self.__isPasting = False
     self.__clipboardSize = QSize()
     self.__pasteRect = QRect()
     
     self.__undoStack = QUndoStack(self)
     self.__currentUndoCmd = None
     
     self.__image = QImage(32, 32, QImage.Format_ARGB32)
     self.__image.fill(qRgba(0, 0, 0, 0))
     self.__markImage = QImage(self.__image)
     self.__markImage.fill(self.NoMarkColor.rgba())
     
     self.__compositingMode = QPainter.CompositionMode_SourceOver
     self.__lastPos = (-1, -1)
     
     self.__gridEnabled = True
     self.__selectionAvailable = False
     
     self.__initCursors()
     self.__initUndoTexts()
     
     self.setMouseTracking(True)
     
     self.__undoStack.canRedoChanged.connect(self.canRedoChanged)
     self.__undoStack.canUndoChanged.connect(self.canUndoChanged)
     self.__undoStack.cleanChanged.connect(self.__cleanChanged)
     
     self.imageChanged.connect(self.__updatePreviewPixmap)
     QApplication.clipboard().dataChanged.connect(self.__checkClipboard)
     
     self.__checkClipboard()
Esempio n. 36
0
    def __init__(self, parent, **kwargs):
        """

        :param ModManager manager:
        :param kwargs: anything to pass on to base class
        :return:
        """

        global Manager
        Manager = modmanager.Manager()

        # noinspection PyArgumentList
        super().__init__(parent=parent,**kwargs)
        self._parent = parent
        self.rootpath = None #type: str
        self.modname = None #type: str
        self.rootitem = None #type: QFSItem

        # the mod table has this stored on the custom view,
        # but we have no custom view for the file tree, so...here it is
        self.undostack = QUndoStack()
Esempio n. 37
0
class ModFileTreeModel(QAbstractItemModel):
    """
    A custom model that presents a view into the actual files saved
    within a mod's folder. It is vastly simplified compared to the
    QFileSystemModel, and only supports editing the state of the
    checkbox on each file or folder (though there is some neat trickery
    that propagates a check-action on a directory to all of its
    descendants)
    """
    #TODO: calculate and inform the user of any file-conflicts that will occur in their mod-setup to help them decide what needs to be hidden.

    rootPathChanged = pyqtSignal(str)
    hasUnsavedChanges = pyqtSignal(bool)

    def __init__(self, parent, **kwargs):
        """

        :param ModManager manager:
        :param kwargs: anything to pass on to base class
        :return:
        """

        global Manager
        Manager = modmanager.Manager()

        # noinspection PyArgumentList
        super().__init__(parent=parent,**kwargs)
        self._parent = parent
        self.rootpath = None #type: str
        self.modname = None #type: str
        self.rootitem = None #type: QFSItem

        # the mod table has this stored on the custom view,
        # but we have no custom view for the file tree, so...here it is
        self.undostack = QUndoStack()

    @property
    def root_path(self):
        return self.rootpath

    @property
    def root_item(self):
        return self.rootitem

    @property
    def current_mod(self):
        return self.modname

    @property
    def has_unsaved_changes(self):
        return Manager.DB.in_transaction

    def setRootPath(self, path=None):
        """
        Using this instead of a setter just for API-similarity with
        QFileSystemModel. That's the same reason rootPathChanged is
        emitted at the end of the method, as well.

        :param str path: the absolute filesystem path to the active
            mod's data folder. If passed as ``None``, the model is
            reset to empty
        """

        if path == self.rootpath: return

        # commit any changes we've made so far
        self.save()

        # drop the undo stack
        self.undostack.clear()

        if path is None: # reset Model to show nothing
            self.beginResetModel()
            self.rootpath=None
            self.rootitem=None
            self.modname=None
            self.rootPathChanged.emit(path)
            self.endResetModel()

        elif check_path(path):

            # tells the view to get ready to redisplay its contents
            self.beginResetModel()

            self.rootpath = path
            self.modname = os.path.basename(path)

            self._setup_or_reload_tree()

            # tells the view it should get new
            # data from model & reset itself
            self.endResetModel()

            # emit notifier signal
            self.rootPathChanged.emit(path)

    def _setup_or_reload_tree(self):
        """
        Loads thde data from the db and disk
        """
        self._load_tree()

        # now mark hidden files
        self._mark_hidden_files()

        # this used to call resetModel() stuff, too, but I decided
        # this wasn't the place for that. It's a little barren now...

    def _load_tree(self):
        """
        Build the tree from the rootitem
        :return:
        """
        # name for this item is never actually seen
        self.rootitem = QFSItem(path="", name="data", parent=None)
        self.rootitem.load_children(self.rootpath, namefilter=lambda
            n: n.lower() == 'meta.ini')

    def _mark_hidden_files(self):


        hfiles = list(r['filepath'] for r in Manager.DB.select(
            "hiddenfiles",
            "filepath",
            where="directory = ?",
            params=(self.modname,)
        ))

        # only files (with their full paths relative to the root of
        # the mod directory) are in the hidden files list; thus we
        # need only compare files and not dirs to the list. As usual,
        # a directory's checkstate will be derived from its children
        for c in self.rootitem.iterchildren(True):
            if c.lpath in hfiles:
                c.checkState = Qt_Unchecked

    def getitem(self, index) -> QFSItem:
        """Extracts actual item from given index

        :param QModelIndex index:
        """
        if index.isValid():
            item = index.internalPointer()
            if item: return item
        return self.rootitem

    def item_from_path(self, path_parts):
        """

        :param path_parts: a tuple where each element is an element in
            the filesystem path leading from the root item to the item
        :return: the item
        """
        item = self.rootitem
        for p in path_parts:
            item = item[p]

        return item

    def columnCount(self, *args, **kwargs) -> int:
        """Dir/File Name(+checkbox), path to file, file conflicts """
        # return 2
        return len(COLUMNS)

    def rowCount(self, index=QModelIndex(), *args, **kwargs) -> int:
        """Number of children contained by the item referenced by `index`

        :param QModelIndex index:
        """
        # return 0 until we actually have something to show
        return self.getitem(index).child_count if self.rootitem else 0

    def headerData(self, section, orient, role=None):
        """Just one column, 'Name'. super() call should take care of the
        size hints &c.

        :param int section:
        :param orient:
        :param role:
        """
        if orient == Qt.Horizontal and role==Qt.DisplayRole:
            return ColHeaders[section]
            # return "Name"
        return super().headerData(section, orient, role)

    def index(self, row, col, parent=QModelIndex(), *args, **kwargs):
        """

        :param int row:
        :param int col:
        :param QModelIndex parent:
        :return: the QModelIndex that represents the item at (row, col)
            with respect to the given  parent index. (or the root index
            if parent is invalid)
        """

        parent_item = self.rootitem
        if parent.isValid():
            parent_item = parent.internalPointer()

        child = parent_item[row]
        if child:
            return self.createIndex(row, col, child)

        return QModelIndex()

    def getIndexFromItem(self, item) -> QModelIndex:
        return self.createIndex(item.row, 0, item)

    # noinspection PyArgumentList
    @pyqtSlot('QModelIndex',name="parent", result = 'QModelIndex')
    def parent(self, child_index=QModelIndex()):
        if not child_index.isValid(): return QModelIndex()

        # get the parent FSItem from the reference stored in each FSItem
        parent = child_index.internalPointer().parent

        if not parent or parent is self.rootitem:
            return QModelIndex()

        # Every FSItem has a row attribute
        # which we use to create the index
        return self.createIndex(parent.row, 0, parent)

    # noinspection PyArgumentList
    @pyqtSlot(name='parent', result='QObject')
    def parent_of_self(self):
        return self._parent

    def flags(self, index):
        """
        Flags are held at the item level; lookup and return them from
        the item referred to by the index

        :param QModelIndex index:
        """
        # item = self.getitem(index)
        return self.getitem(index).itemflags

    def data(self, index, role=Qt.DisplayRole):
        """
        We handle DisplayRole to return the filename, CheckStateRole to
        indicate whether the file has been hidden, and Decoration Role
        to return different icons for folders and files.

        :param QModelIndex index:
        :param role:
        """

        item = self.getitem(index)
        col = index.column()

        if col == COL_PATH:
            if role == Qt.DisplayRole: #second column is path
                return item.parent.path + "/"

        elif col == COL_CONFLICTS: # third column is conflicts
            if role == Qt.DisplayRole and \
                self.modname in Manager.mods_with_conflicting_files \
                    and item.lpath in Manager.file_conflicts:
                return "Yes"


        else: # column must be Name
            if role == Qt.DisplayRole:
                return item.name
            elif role == Qt_CheckStateRole:
                # hides the complexity of the tristate workings
                return item.checkState
            elif role == Qt.DecorationRole:
                return item.icon

    # noinspection PyTypeChecker
    def setData(self, index, value, role=Qt_CheckStateRole):
        """Only the checkStateRole can be edited in this model.
        Most of the machinery for that is in the QFSItem class

        :param QModelIndex index:
        :param value:
        :param role:
        """
        if not index.isValid(): return False

        item = self.getitem(index)
        if role==Qt_CheckStateRole:

            item.checkState = value #triggers cascade if this a dir

            # if this item is the last checked/unchecked item in a dir,
            # make sure the change is propagated up through the parent
            # hierarchy, to make sure that no folders remain checked
            # when none of their descendants are.
            ancestor = self._get_highest_affected_ancestor(item, value)

            if ancestor is not item:
                index1 = self.getIndexFromItem(ancestor)
            else:
                index1 = index

            # using the "last_child_seen" value--which SHOULD be the most
            # "bottom-right" child that was just changed--to feed to
            # datachanged saves a lot of individual calls. Hopefully there
            # won't be any concurrency issues to worry about later on.

            # update the db with which files are now hidden
            self.update_db(index1,
                           self.getIndexFromItem(QFSItem.last_child_seen))

            return True
        return super().setData(index, value, role)

    def _get_highest_affected_ancestor(self, item, value):
        """worst name for a function ever but i can't think of better"""
        if item.parent and item.parent.children_checkState() == value:
            return self._get_highest_affected_ancestor(item.parent, value)
        else:
            return item

    # noinspection PyUnresolvedReferences
    def _send_data_through_proxy(self, index1, index2, *args):
        proxy = self._parent.model() #QSortFilterProxyModel

        proxy.dataChanged.emit(proxy.mapFromSource(index1),
                               proxy.mapFromSource(index2), *args)

    def save(self):
        """
        Commit any unsaved changes (currenlty just to hidden files) to
        the db and save the updated db state to disk
        """
        if Manager.DB.in_transaction:
            Manager.DB.commit()
            Manager.save_hidden_files()
            self.hasUnsavedChanges.emit(False)

    def revert(self):
        """
        Undo all changes made to the tree since the last save.
        """
        self.beginResetModel()

        #SOOOO...
        # will a rollback/drop-the-undostack work here?
        # or is individually undoing everything (a bunch of savepoint-
        # rollbacks) better? I guess it depends on whether we want to
        # be able to define a "clean" point in the middle of a
        # transaction...

        Manager.DB.rollback()
        self.undostack.clear()

        # while self.undostack.canUndo() and not self.undostack.isClean():
        #     self.undostack.undo()


        self._setup_or_reload_tree()

        self.endResetModel()
        self.hasUnsavedChanges.emit(False)

    def update_db(self, start_index, final_index):
        """Make  changes to database.
        NOTE: this does not commit them! That must be done separately

        :param start_index: index of the "top-left" affected item
        :param final_index: index of the "bottom-right" affected item


        """
        cb = partial(self._send_data_through_proxy,
                     start_index, final_index)

        self.undostack.push(
            ChangeHiddenFilesCommand(self.rootitem,
                                     os.path.basename(self.rootpath),
                                     post_redo_callback=cb,
                                     post_undo_callback=cb
                                     ))
        self.hasUnsavedChanges.emit(True)
Esempio n. 38
0
class AbstractPage(QObject):
    sigChanged = pyqtSignal()

    def __init__(self, parent: MasterDocument):
        super().__init__()
        self._objs = set()
        self._parent = parent
        self.undoStack = QUndoStack()
        self._name = "untitled"

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, n):
        self._name = n
        self.sigChanged.emit()

    @property
    def parentDoc(self):
        return self._parent

    def isModified(self):
        return not self.undoStack.isClean()

    def doCommand(self, cmd):
        cmd.doc = self
        self.undoStack.push(cmd)
        self.sigChanged.emit()

    def objects(self, objType=None, exclude=None):
        if not objType and not exclude:
            return set(self._objs)
        if exclude is None:
            exclude = ()
        if objType:
            return {obj for obj in self._objs if type(obj) is objType and not (type(obj) in exclude)}
        else:
            return {obj for obj in self._objs if not (type(obj) in exclude)}

    def hasObject(self, obj):
        return obj in self._objs

    def findObjsInRect(self, rect: QRect, objType=None):
        return {obj for obj in self.objects(objType) if rect.intersects(obj.bbox())}

    def findObjsNear(self, pt: QPoint, dist=1, objType=None):
        hitRect = QRect(pt.x()-dist/2, pt.y()-dist/2, dist, dist)
        return {obj for obj in self.findObjsInRect(hitRect, objType) if obj.testHit(pt, dist)}

    def addObj(self, obj):
        self._objs.add(obj)
        self.sigChanged.emit()

    def removeObj(self, obj):
        self._objs.remove(obj)
        self.sigChanged.emit()

    @pyqtSlot()
    def undo(self):
        self.undoStack.undo()
        self.sigChanged.emit()

    @pyqtSlot()
    def redo(self):
        self.undoStack.redo()
        self.sigChanged.emit()

    def fromXml(self, pageNode):
        raise NotImplementedError()

    def toXml(self, parentNode):
        raise NotImplementedError()
Esempio n. 39
0
 def __init__(self, parent=None):
     super(GRODelegate, self).__init__(parent)
     self.undoStack = QUndoStack(self)  # This is for undo/redo
Esempio n. 40
0
class GRODelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super(GRODelegate, self).__init__(parent)
        self.undoStack = QUndoStack(self)  # This is for undo/redo

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)

    def sizeHint(self, option, index):
        fm = option.fontMetrics
        if index.column() == residNum:
            return QSize(fm.width("9,999,999"), fm.height())
        if index.column() == residName:
            text = index.model().data(index)
            document = QTextDocument()
            document.setDefaultFont(option.font)
            document.setHtml(text)
            return QSize(document.idealWidth() + 5, fm.height())
        return QStyledItemDelegate.sizeHint(self, option, index)

    def createEditor(self, parent, option, index):  # residNum,residNum_color, residName, atomName, atomNum, X,Y,Z
        if index.column() == residNum:
            spinbox = QSpinBox(parent)
            spinbox.setRange(1, 200000)
            spinbox.setSingleStep(1)
            spinbox.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            return spinbox
        elif index.column() == residName:
            combobox = QComboBox(parent)
            combobox.addItems(comboBoxList)
            combobox.insertSeparator(23)
            combobox.setEditable(True)
            return combobox
        elif index.column() == atomName:
            editor = QLineEdit(parent)
            editor.returnPressed.connect(self.commitAndCloseEditor)
            return editor
        elif index.column() == atomNum:
            spinbox = QSpinBox(parent)
            spinbox.setRange(1, 200000)
            spinbox.setSingleStep(1)
            spinbox.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            return spinbox
        elif index.column() in (X, Y, Z):  ###this works
            dspinbox = QDoubleSpinBox(parent)
            dspinbox.setRange(-200000, 200000)
            dspinbox.setSingleStep(0.1)
            dspinbox.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            return dspinbox
        else:
            return QStyledItemDelegate.createEditor(self, parent, option,
                                                    index)

    def commitAndCloseEditor(self):
        editor = self.sender()
        if isinstance(editor, (QTextEdit, QLineEdit)):
            comitDataSignal.emit(editor)
            closeEditorSignal.emit(editor)

    def setEditorData(self, editor, index):
        text = index.model().data(index, Qt.DisplayRole)
        if index.column() == residNum:
            if text is None:
                value = 0
            elif isinstance(text, int):
                value = text
            else:
                value = int(re.sub(r"[., ]", "", text))
            editor.setValue(value)
            # elif index.column() == residName:
            # editor.setText(text)
        elif index.column() == atomName:
            editor.setText(text)
        elif index.column() == atomNum:
            if text is None:
                value = 0
            elif isinstance(text, int):
                value = text
            else:
                value = int(re.sub(r"[., ]", "", text))
            editor.setValue(value)
        elif index.column() in (X, Y, Z):
            if text is None:
                value = 0
            elif isinstance(text, int):
                value = text
            else:
                value = float(text)
            editor.setValue(value)
        else:
            QStyledItemDelegate.setEditorData(self, editor, index)

    def setModelData(self, editor, model, index):
        command = CommandElementChange(self, editor, model, index,
                                       "Change item value")
        self.undoStack.push(command)
Esempio n. 41
0
class MainManager(QObject):
	DEFAULT_BODY_SIZE = 50;
	TRANSCOORD_X = 30;
	TRANSCOORD_Y = -30;
	AXIS_LEN = 250;
	UNITS_PER_METER = 30;

	bodiesLoaded = pyqtSignal(dict);
	# itemPositionChanged = pyqtSignal(QPointF);
	# itemScaleChanged = pyqtSignal(QPointF);

	def __init__(self, renderScene):
		super(MainManager, self).__init__();
		self.renderScene = renderScene;
		self.noPen = QPen(Qt.NoPen);
		self.bodies = {};
		self.bodyInstances = [];
		self.nameIndex = {};

		#Draw the axes
		self.axes = QGraphicsItemGroup();
		renderScene.addItem(self.axes);
		xAxis = QGraphicsItemGroup(self.axes);
		s1, s2 = 10, 4
		x1, y1, x2, y2 = -self.AXIS_LEN, 0, self.AXIS_LEN, 0
		QGraphicsLineItem(x1, y1, x2, y2, xAxis);
		QGraphicsLineItem(x2-s1, y2-s2, x2, y2, xAxis);
		QGraphicsLineItem(x2-s1, y2+s2, x2, y2, xAxis);
		x1, y1, x2, y2 = 0, -self.AXIS_LEN, 0, self.AXIS_LEN
		yAxis = QGraphicsItemGroup(self.axes);
		QGraphicsLineItem(x1, y1, x2, y2, yAxis);
		QGraphicsLineItem(x2-s2, -(y2-s1), x2, -y2, yAxis);
		QGraphicsLineItem(x2+s2, -(y2-s1), x2, -y2, yAxis);
		grid = GridItem(0, 0, self.UNITS_PER_METER, 100);
		renderScene.addItem(grid);
		for o in [self.axes, grid]:
			o.setZValue(-1000);		
		self.undoStack = QUndoStack(self);

	def raiseItems(self):
		for item in self.renderScene.selectedItems():
			item.setZValue(item.zValue() + 0.001);

	def lowerItems(self):
		for item in self.renderScene.selectedItems():
			item.setZValue(item.zValue() - 0.001);

	def deleteSelected(self):
		delCmd = DeleteCommand(self.renderScene.selectedItems())
		self.undoStack.push(delCmd);

	def handleMoveCommand(self, pos1, pos2):
		moveCmd = MoveCommand(self.renderScene.selectedItems(), pos1, pos2)
		self.undoStack.push(moveCmd)
		moveCmd.undo() #The default event implementation moved the object already, another move is wrong
						#I do this 'cause Qt just doesn't allow me to disable the automatic redo() when pushing

	def handleScaleCommand(self, scaleDelta):
		scaleCmd = ScaleCommand(self.renderScene.selectedItems(), scaleDelta);
		self.undoStack.push(scaleCmd);
		scaleCmd.undo();

	def loadFromPBE(self, PBEFile):
		with open(PBEFile, "r") as f:
			baseDir = os.path.dirname(PBEFile);
			data = json.load(f);
			for body in data["rigidBodies"]:
				self.loadBody(body, baseDir);
			self.bodiesLoaded.emit(self.bodies);


	def trans(self, vertexDef):
		return QPointF(vertexDef["x"]*self.TRANSCOORD_X, vertexDef["y"]*self.TRANSCOORD_Y)

	def loadBody(self, bodyDef, baseDir):
		bodyConf = {};
		if (bodyDef["imagePath"]):
			bodyConf["image"] = os.path.join(baseDir, bodyDef["imagePath"])
		else:
			bodyConf["image"] = None;
		bodyConf["shapes"] = [];

		for shape in bodyDef["shapes"]:
			bodyConf["shapes"].append(	{
									"type": shape["type"],
									"vertices": [self.trans(vertex) for vertex in shape["vertices"]]
								}
							);
		self.bodies[bodyDef["name"]] = bodyConf;

	def duplicateItems(self):
		for item in self.renderScene.selectedItems():
			newItem = self.cloneBody(item.bodyspecName, item.pos(),
				width=item.getMeterWidth());
			item.setSelected(False);
			newItem.setSelected(True);

	def cloneBody(self, bodyspecName, dropPos, itemId=None, width=0):
		bodyDef = self.bodies[bodyspecName];
		if not itemId:
			if bodyspecName not in self.nameIndex:
				self.nameIndex[bodyspecName] = 0;
			self.nameIndex[bodyspecName] += 1;
			itemId = "{}{}".format(bodyspecName, self.nameIndex[bodyspecName]);
		body = BodyItem(itemId, bodyspecName, 2);
		self.bodyInstances.append(body);
		body.setPos(dropPos);
		group = QGraphicsItemGroup(body);
		self.renderScene.addItem(body);
		width = width*self.UNITS_PER_METER or self.DEFAULT_BODY_SIZE;

		for shape in bodyDef["shapes"]:
			vertices = shape["vertices"];
			if shape["type"] == "POLYGON":
				newItem = QGraphicsPolygonItem(QPolygonF(vertices));
			if shape["type"] == "CIRCLE":
				p1, p2 = vertices
				radius = math.hypot(p2.x()-p1.x(), p2.y()-p1.y());
				newItem = QGraphicsEllipseItem(p1.x()-radius, p1.y()-radius, radius*2, radius*2);
			pen = QPen();
			pen.setWidth(0);			
			newItem.setPen(pen);
			newItem.setParentItem(group);
		bounding = group.childrenBoundingRect();
		imagePath = None;
		height = 0;
		if (bodyDef["image"]):
			imagePath = bodyDef["image"];
			pixmap = QPixmap(imagePath);
			body.setPixmap(pixmap);
			pm = QGraphicsPixmapItem(pixmap.scaledToWidth(width), body);
			body.setImg(pm);
			pm.setFlags(QGraphicsItem.ItemStacksBehindParent);
			pm.setOffset(0, -pm.boundingRect().height());
			group.setScale(width/self.TRANSCOORD_X);
			height = pm.boundingRect().height();
		else:
			group.setScale(width/bounding.width());
			height = bounding.height();
		for item in body.childItems():
			item.setPos(item.pos().x(), item.pos().y() + height)
		body.updateBorder();

		return body;

	def save(self, file):
		with open(file, "w") as f:
			instancesDef = [];
			for inst in self.bodyInstances:
				if inst.deleted: continue;
				pos = inst.scenePos();
				instancesDef.append({"id": inst.itemId, "bodyspec": inst.bodyspecName,
					"pos": {"x": pos.x()/self.UNITS_PER_METER, "y": pos.y()/self.UNITS_PER_METER},
					"width": inst.getMeterWidth(), "height": inst.getMeterHeight()
					});
			output = {"spec": self.bodies, "instances": instancesDef, "nameIndex": self.nameIndex};
			json.dump(output, f, cls=MyJsonEncoder);

	def loadFile(self, file):
		with open(file, "r") as f:
			self.renderScene.clearInstancesOf(BodyItem);
			self.bodyInstances = [];
			data = json.load(f);			
			self.bodies = data["spec"];
			for name, body in self.bodies.items():
				shapes = [];
				for shape in body["shapes"]:
					shape["vertices"]=[QPointF(vertex["x"], vertex["y"]) for vertex in shape["vertices"]];
					shapes.append(shape);
				body["shapes"]=shapes;
				self.bodies[name] = body;

			for inst in data["instances"]:
				pos = inst["pos"];
				self.cloneBody(inst["bodyspec"], QPointF(pos["x"]*self.UNITS_PER_METER, pos["y"]*self.UNITS_PER_METER),
					itemId=inst["id"], width=inst["width"])
			self.bodiesLoaded.emit(self.bodies);
			self.nameIndex = data["nameIndex"];
Esempio n. 42
0
 def __init__(self, parent: MasterDocument):
     super().__init__()
     self._objs = set()
     self._parent = parent
     self.undoStack = QUndoStack()
     self._name = "untitled"
Esempio n. 43
0
class TableModel(QAbstractTableModel):
    ALIGNMENT_CHAR = " "

    data_edited = pyqtSignal(int, int)
    vertical_header_color_status_changed = pyqtSignal(bool)

    def __init__(self, participants, parent=None):
        super().__init__(parent)
        self.controller = None  # :type: CompareFrameController|GeneratorTabController
        self.protocol = None  # type: ProtocolAnalyzer

        self.col_count = 0
        self.row_count = 0
        self.display_data = None  # type: list[str]

        self.search_results = []
        self.search_value = ""
        self._proto_view = 0
        self._refindex = -1

        self.first_messages = []
        self.hidden_rows = set()

        self.is_writeable = False
        self.locked = False
        self.decode = True  # False for Generator

        self.background_colors = defaultdict(lambda: None)
        self.bold_fonts = defaultdict(lambda: False)
        self.italic_fonts = defaultdict(lambda: False)
        self.text_colors = defaultdict(lambda: None)
        self.vertical_header_text = defaultdict(lambda: None)
        self.vertical_header_colors = defaultdict(lambda: None)

        self._diffs = defaultdict(set)  # type: dict[int, set[int]]

        self.undo_stack = QUndoStack()

        self.__participants = participants

    @property
    def participants(self):
        return self.__participants

    @participants.setter
    def participants(self, value):
        self.__participants = value
        for msg in self.protocol.messages:
            if msg.participant not in self.__participants:
                msg.participant = None

    @property
    def proto_view(self):
        return self._proto_view

    @proto_view.setter
    def proto_view(self, value):
        self._proto_view = value
        if self._refindex >= 0:
            self._diffs = self.find_differences(self._refindex)
        self.update()

    def get_alignment_offset_at(self, index: int):
        f = 1 if self.proto_view == 0 else 4 if self.proto_view == 1 else 8
        alignment_offset = int(math.ceil(self.protocol.messages[index].alignment_offset / f))
        return alignment_offset

    def __pad_until_index(self, row: int, bit_pos: int):
        """
        Pad message in given row with zeros until given column so user can enter values behind end of message
        :return:
        """
        try:
            new_bits = array.array("B", [0] * max(0, bit_pos - len(self.protocol.messages[row])))
            if len(new_bits) == 0:
                return True

            self.protocol.messages[row].plain_bits = self.protocol.messages[row].plain_bits + new_bits
            msg = self.protocol.messages[row]
            self.display_data[
                row] = msg.plain_bits if self.proto_view == 0 else msg.plain_hex_array if self.proto_view == 1 else msg.plain_ascii_array
        except IndexError:
            return False

        return True

    def headerData(self, section: int, orientation, role=Qt.DisplayRole):
        if orientation == Qt.Vertical:
            if role == Qt.DisplayRole:
                return self.vertical_header_text[section]
            elif role == Qt.BackgroundColorRole:
                return self.vertical_header_colors[section]
            elif role == Qt.TextColorRole:
                color = self.vertical_header_colors[section]
                if color:
                    red, green, blue = color.red(), color.green(), color.blue()
                    return QColor("black") if (red * 0.299 + green * 0.587 + blue * 0.114) > 186 else QColor("white")
                else:
                    return None

        return super().headerData(section, orientation, role)

    def update(self):
        self.locked = True

        if self.protocol.num_messages > 0:
            if self.decode:
                if self.proto_view == 0:
                    self.display_data = [msg.decoded_bits for msg in self.protocol.messages]
                elif self.proto_view == 1:
                    self.display_data = [msg.decoded_hex_array for msg in self.protocol.messages]
                elif self.proto_view == 2:
                    self.display_data = [msg.decoded_ascii_array for msg in self.protocol.messages]
            else:
                # Generator Model
                if self.proto_view == 0:
                    self.display_data = [msg.plain_bits for msg in self.protocol.messages]
                elif self.proto_view == 1:
                    self.display_data = [msg.plain_hex_array for msg in self.protocol.messages]
                else:
                    self.display_data = [msg.plain_ascii_array for msg in self.protocol.messages]

            visible_messages = [msg for i, msg in enumerate(self.display_data) if i not in self.hidden_rows]
            if len(visible_messages) == 0:
                self.col_count = 0
            else:
                self.col_count = max(len(msg) + self.get_alignment_offset_at(i)
                                     for i, msg in enumerate(self.display_data) if i not in self.hidden_rows)

            if self._refindex >= 0:
                self._diffs = self.find_differences(self._refindex)
            else:
                self._diffs.clear()

            self.row_count = self.protocol.num_messages
            self.find_protocol_value(self.search_value)
        else:
            self.col_count = 0
            self.row_count = 0
            self.display_data = None

        # Cache background colors for performance
        self.refresh_bgcolors()
        self.refresh_fonts()  # Will be overriden
        self.refresh_vertical_header()

        self.beginResetModel()
        self.endResetModel()
        self.locked = False

    def insert_column(self, index: int, rows: list):
        if self.protocol is None or not self.is_writeable:
            return

        insert_action = InsertColumn(self.protocol, index, rows, self.proto_view)
        self.undo_stack.push(insert_action)

    def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
        return self.col_count

    def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
        return self.row_count

    def refresh_bgcolors(self):
        self.background_colors.clear()
        label_colors = constants.LABEL_COLORS

        for i, message in enumerate(self.protocol.messages):
            for lbl in message.message_type:
                bg_color = label_colors[lbl.color_index]
                a = self.get_alignment_offset_at(i)
                start, end = message.get_label_range(lbl, self.proto_view, self.decode)
                for j in range(start, end):
                    self.background_colors[i, j + a] = bg_color

    def refresh_fonts(self):
        """
        Will be overriden

        :return:
        """
        pass

    def refresh_vertical_header(self):
        self.vertical_header_colors.clear()
        self.vertical_header_text.clear()
        use_colors = False
        for i in range(self.row_count):
            try:
                participant = self.protocol.messages[i].participant
            except IndexError:
                participant = None
            if participant:
                self.vertical_header_text[i] = "{0} ({1})".format(i + 1, participant.shortname)
                self.vertical_header_colors[i] = constants.PARTICIPANT_COLORS[participant.color_index]
                use_colors = True
            else:
                self.vertical_header_text[i] = str(i + 1)

        self.vertical_header_color_status_changed.emit(use_colors)

    def data(self, index: QModelIndex, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        i = index.row()
        j = index.column()
        if role == Qt.DisplayRole and self.display_data:
            try:
                alignment_offset = self.get_alignment_offset_at(i)
                if j < alignment_offset:
                    return self.ALIGNMENT_CHAR

                if self.proto_view == 0:
                    return self.display_data[i][j - alignment_offset]
                elif self.proto_view == 1:
                    return "{0:x}".format(self.display_data[i][j - alignment_offset])
                elif self.proto_view == 2:
                    return chr(self.display_data[i][j - alignment_offset])
            except IndexError:
                return None

        elif role == Qt.TextAlignmentRole:
            if i in self.first_messages:
                return Qt.AlignHCenter + Qt.AlignBottom
            else:
                return Qt.AlignCenter

        elif role == Qt.BackgroundColorRole:
            return self.background_colors[i, j]

        elif role == Qt.FontRole:
            font = QFont()
            font.setBold(self.bold_fonts[i, j])
            font.setItalic(self.italic_fonts[i, j])
            return font

        elif role == Qt.TextColorRole:
            return self.text_colors[i, j]

        elif role == Qt.ToolTipRole:
            return self.get_tooltip(i, j)
        else:
            return None

    def get_tooltip(self, row: int, column: int) -> str:
        msg = self.protocol.messages[row]
        try:
            lbl = next(lbl for lbl in msg.message_type
                       if column in range(*msg.get_label_range(lbl, self.proto_view, self.decode)))
        except StopIteration:
            return ""

        result = lbl.name
        if isinstance(lbl, ChecksumLabel):
            calculated_crc = lbl.calculate_checksum_for_message(msg, use_decoded_bits=self.decode)
            start, end = msg.get_label_range(lbl=lbl, view=0, decode=self.decode)
            bits = msg.decoded_bits if self.decode else msg.plain_bits
            color = "green" if bits[start:end] == calculated_crc else "red"
            expected = util.convert_bits_to_string(calculated_crc, self.proto_view)
            result += '<br><font color="{}">Expected <b>{}</b></font>'.format(color, expected)

        return result

    def setData(self, index: QModelIndex, value, role=Qt.DisplayRole):
        if role != Qt.EditRole:
            return True

        i = index.row()
        j = index.column()
        a = self.get_alignment_offset_at(i)
        j -= a
        hex_chars = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f")

        if i >= len(self.protocol.messages):
            return False

        if self.proto_view == 0 and value in ("0", "1") and self.__pad_until_index(i, j + 1):
            self.protocol.messages[i][j] = bool(int(value))
            self.display_data[i][j] = int(value)
        elif self.proto_view == 1 and value in hex_chars and self.__pad_until_index(i, (j + 1) * 4):
            converted_j = self.protocol.convert_index(j, 1, 0, self.decode, message_indx=i)[0]
            bits = "{0:04b}".format(int(value, 16))
            for k in range(4):
                self.protocol.messages[i][converted_j + k] = bool(int(bits[k]))
            self.display_data[i][j] = int(value, 16)
        elif self.proto_view == 2 and len(value) == 1 and self.__pad_until_index(i, (j + 1) * 8):
            converted_j = self.protocol.convert_index(j, 2, 0, self.decode, message_indx=i)[0]
            bits = "{0:08b}".format(ord(value))
            for k in range(8):
                self.protocol.messages[i][converted_j + k] = bool(int(bits[k]))
            self.display_data[i][j] = ord(value)
        else:
            return False

        self.data_edited.emit(i, j)
        return True

    def find_protocol_value(self, value):
        self.search_results.clear()
        if self.proto_view == 1:
            value = value.lower()

        self.search_value = value

        if len(value) == 0:
            return 0

        for i, message in enumerate(self.protocol.messages):
            if i in self.hidden_rows:
                continue

            data = message.view_to_string(self.proto_view, self.decode)
            j = data.find(value)
            while j != -1:
                self.search_results.append((i, j + self.get_alignment_offset_at(i)))
                j = data.find(value, j + 1)

        return len(self.search_results)

    def find_differences(self, refindex: int):
        """
        Search all differences between protocol messages regarding a reference message

        :param refindex: index of reference message
        :rtype: dict[int, set[int]]
        """
        differences = defaultdict(set)

        if refindex >= len(self.protocol.messages):
            return differences

        if self.proto_view == 0:
            proto = self.protocol.decoded_proto_bits_str
        elif self.proto_view == 1:
            proto = self.protocol.decoded_hex_str
        elif self.proto_view == 2:
            proto = self.protocol.decoded_ascii_str
        else:
            return differences

        ref_message = proto[refindex]
        ref_offset = self.get_alignment_offset_at(refindex)

        for i, message in enumerate(proto):
            if i == refindex:
                continue

            msg_offset = self.get_alignment_offset_at(i)
            short, long = sorted([len(ref_message) + ref_offset, len(message) + msg_offset])

            differences[i] = {
                j for j in range(max(msg_offset, ref_offset), long)
                if j >= short or message[j - msg_offset] != ref_message[j - ref_offset]
            }

        return differences

    def get_selected_label_index(self, row: int, column: int):
        if self.row_count == 0:
            return -1

        try:
            msg = self.protocol.messages[row]
        except IndexError:
            logger.warning("{} is out of range for generator protocol".format(row))
            return -1

        for i, lbl in enumerate(msg.message_type):
            if column in range(*msg.get_label_range(lbl, self.proto_view, False)):
                return i

        return -1
Esempio n. 44
0
class MapDocument(QObject):
    fileNameChanged = pyqtSignal(str, str)
    modifiedChanged = pyqtSignal()
    saved = pyqtSignal()
    ##
    # Emitted when the selected tile region changes. Sends the currently
    # selected region and the previously selected region.
    ##
    selectedAreaChanged = pyqtSignal(QRegion, QRegion)
    ##
    # Emitted when the list of selected objects changes.
    ##
    selectedObjectsChanged = pyqtSignal()
    ##
    # Emitted when the list of selected tiles from the dock changes.
    ##
    selectedTilesChanged = pyqtSignal()
    currentObjectChanged = pyqtSignal(list)
    ##
    # Emitted when the map size or its tile size changes.
    ##
    mapChanged = pyqtSignal()
    layerAdded = pyqtSignal(int)
    layerAboutToBeRemoved = pyqtSignal(int)
    layerRenamed = pyqtSignal(int)
    layerRemoved = pyqtSignal(int)
    layerChanged = pyqtSignal(int)
    ##
    # Emitted after a new layer was added and the name should be edited.
    # Applies to the current layer.
    ##
    editLayerNameRequested = pyqtSignal()
    editCurrentObject = pyqtSignal()
    ##
    # Emitted when the current layer index changes.
    ##
    currentLayerIndexChanged = pyqtSignal(int)
    ##
    # Emitted when a certain region of the map changes. The region is given in
    # tile coordinates.
    ##
    regionChanged = pyqtSignal(QRegion, Layer)
    ##
    # Emitted when a certain region of the map was edited by user input.
    # The region is given in tile coordinates.
    # If multiple layers have been edited, multiple signals will be emitted.
    ##
    regionEdited = pyqtSignal(QRegion, Layer)
    tileLayerDrawMarginsChanged = pyqtSignal(TileLayer)
    tileTerrainChanged = pyqtSignal(QList)
    tileProbabilityChanged = pyqtSignal(Tile)
    tileObjectGroupChanged = pyqtSignal(Tile)
    tileAnimationChanged = pyqtSignal(Tile)
    objectGroupChanged = pyqtSignal(ObjectGroup)
    imageLayerChanged = pyqtSignal(ImageLayer)
    tilesetAboutToBeAdded = pyqtSignal(int)
    tilesetAdded = pyqtSignal(int, Tileset)
    tilesetAboutToBeRemoved = pyqtSignal(int)
    tilesetRemoved = pyqtSignal(Tileset)
    tilesetMoved = pyqtSignal(int, int)
    tilesetFileNameChanged = pyqtSignal(Tileset)
    tilesetNameChanged = pyqtSignal(Tileset)
    tilesetTileOffsetChanged = pyqtSignal(Tileset)
    tilesetChanged = pyqtSignal(Tileset)
    objectsAdded = pyqtSignal(QList)
    objectsInserted = pyqtSignal(ObjectGroup, int, int)
    objectsRemoved = pyqtSignal(QList)
    objectsChanged = pyqtSignal(QList)
    objectsIndexChanged = pyqtSignal(ObjectGroup, int, int)
    propertyAdded = pyqtSignal(Object, str)
    propertyRemoved = pyqtSignal(Object, str)
    propertyChanged = pyqtSignal(Object, str)
    propertiesChanged = pyqtSignal(Object)

    ##
    # Constructs a map document around the given map. The map document takes
    # ownership of the map.
    ##
    def __init__(self, map, fileName = QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())

    ##
    # Destructor.
    ##
    def __del__(self):
        # Unregister tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReferences(self.mMap.tilesets())
        del self.mRenderer
        del self.mMap

    ##
    # Saves the map to its current file name. Returns whether or not the file
    # was saved successfully. If not, <i>error</i> will be set to the error
    # message if it is not 0.
    ##
    def save(self, *args):
        l = len(args)
        if l==0:
            args = ('')
        if l==1:
            arg = args[0]
            file = QFileInfo(arg)
            if not file.isFile():
                fileName = self.fileName()
                error = args[0]
            else:
                fileName = arg
                error = ''
            return self.save(fileName, error)
        if l==2:
            ##
            # Saves the map to the file at \a fileName. Returns whether or not the
            # file was saved successfully. If not, <i>error</i> will be set to the
            # error message if it is not 0.
            #
            # If the save was successful, the file name of this document will be set
            # to \a fileName.
            #
            # The map format will be the same as this map was opened with.
            ##
            fileName, error = args
            mapFormat = self.mWriterFormat
            
            tmxMapFormat = TmxMapFormat()
            if (not mapFormat):
                mapFormat = tmxMapFormat
            if (not mapFormat.write(self.map(), fileName)):
                if (error):
                   error = mapFormat.errorString()
                return False

            self.undoStack().setClean()
            self.setFileName(fileName)
            self.mLastSaved = QFileInfo(fileName).lastModified()
            self.saved.emit()
            return True

    ##
    # Loads a map and returns a MapDocument instance on success. Returns 0
    # on error and sets the \a error message.
    ##
    def load(fileName, mapFormat = None):
        error = ''
        tmxMapFormat = TmxMapFormat()
        
        if (not mapFormat and not tmxMapFormat.supportsFile(fileName)):
            # Try to find a plugin that implements support for this format
            formats = PluginManager.objects()
            for format in formats:
                if (format.supportsFile(fileName)):
                    mapFormat = format
                    break

        map = None
        errorString = ''

        if mapFormat:
            map = mapFormat.read(fileName)
            errorString = mapFormat.errorString()
        else:
            map = tmxMapFormat.read(fileName)
            errorString = tmxMapFormat.errorString()

        if (not map):
            error = errorString
            return None, error

        mapDocument = MapDocument(map, fileName)
        if mapFormat:
            mapDocument.setReaderFormat(mapFormat)
            if mapFormat.hasCapabilities(MapFormat.Write):
                mapDocument.setWriterFormat(mapFormat)

        return mapDocument, error

    def fileName(self):
        return self.mFileName

    def lastExportFileName(self):
        return self.mLastExportFileName

    def setLastExportFileName(self, fileName):
        self.mLastExportFileName = fileName

    def readerFormat(self):
        return self.mReaderFormat

    def setReaderFormat(self, format):
        self.mReaderFormat = format

    def writerFormat(self):
        return self.mWriterFormat

    def setWriterFormat(self, format):
        self.mWriterFormat = format

    def exportFormat(self):
        return self.mExportFormat

    def setExportFormat(self, format):
        self.mExportFormat = format
        
    ##
    # Returns the name with which to display this map. It is the file name without
    # its path, or 'untitled.tmx' when the map has no file name.
    ##
    def displayName(self):
        displayName = QFileInfo(self.mFileName).fileName()
        if len(displayName)==0:
            displayName = self.tr("untitled.tmx")
        return displayName

    ##
    # Returns whether the map has unsaved changes.
    ##
    def isModified(self):
        return not self.mUndoStack.isClean()

    def lastSaved(self):
        return self.mLastSaved
        
    ##
    # Returns the map instance. Be aware that directly modifying the map will
    # not allow the GUI to update itself appropriately.
    ##
    def map(self):
        return self.mMap

    ##
    # Sets the current layer to the given index.
    ##
    def setCurrentLayerIndex(self, index):
        changed = self.mCurrentLayerIndex != index
        self.mCurrentLayerIndex = index
        ## This function always sends the following signal, even if the index
        # didn't actually change. This is because the selected index in the layer
        # table view might be out of date anyway, and would otherwise not be
        # properly updated.
        #
        # This problem happens due to the selection model not sending signals
        # about changes to its current index when it is due to insertion/removal
        # of other items. The selected item doesn't change in that case, but our
        # layer index does.
        ##
        self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex)
        if (changed and self.mCurrentLayerIndex != -1):
            self.setCurrentObject(self.currentLayer())

    ##
    # Returns the index of the currently selected layer. Returns -1 if no
    # layer is currently selected.
    ##
    def currentLayerIndex(self):
        return self.mCurrentLayerIndex

    ##
    # Returns the currently selected layer, or 0 if no layer is currently
    # selected.
    ##
    def currentLayer(self):
        if (self.mCurrentLayerIndex == -1):
            return None
        return self.mMap.layerAt(self.mCurrentLayerIndex)

    ##
    # Resize this map to the given \a size, while at the same time shifting
    # the contents by \a offset.
    ##
    def resizeMap(self, size, offset):
        movedSelection = self.mSelectedArea.translated(offset)
        newArea = QRect(-offset, size)
        visibleArea = self.mRenderer.boundingRect(newArea)
        origin = self.mRenderer.tileToPixelCoords_(QPointF())
        newOrigin = self.mRenderer.tileToPixelCoords_(-offset)
        pixelOffset = origin - newOrigin
        # Resize the map and each layer
        self.mUndoStack.beginMacro(self.tr("Resize Map"))
        for i in range(self.mMap.layerCount()):
            layer = self.mMap.layerAt(i)
            x = layer.layerType()
            if x==Layer.TileLayerType:
                tileLayer = layer
                self.mUndoStack.push(ResizeTileLayer(self, tileLayer, size, offset))
            elif x==Layer.ObjectGroupType:
                objectGroup = layer
                # Remove objects that will fall outside of the map
                for o in objectGroup.objects():
                    if (not visibleIn(visibleArea, o, self.mRenderer)):
                        self.mUndoStack.push(RemoveMapObject(self, o))
                    else:
                        oldPos = o.position()
                        newPos = oldPos + pixelOffset
                        self.mUndoStack.push(MoveMapObject(self, newPos, oldPos))
            elif x==Layer.ImageLayerType:
                # Currently not adjusted when resizing the map
                break

        self.mUndoStack.push(ResizeMap(self, size))
        self.mUndoStack.push(ChangeSelectedArea(self, movedSelection))
        self.mUndoStack.endMacro()
        # TODO: Handle layers that don't match the map size correctly

    ##
    # Offsets the layers at \a layerIndexes by \a offset, within \a bounds,
    # and optionally wraps on the X or Y axis.
    ##
    def offsetMap(self, layerIndexes, offset, bounds, wrapX, wrapY):
        if (layerIndexes.empty()):
            return
        if (layerIndexes.size() == 1):
            self.mUndoStack.push(OffsetLayer(self, layerIndexes.first(), offset,
                                             bounds, wrapX, wrapY))
        else:
            self.mUndoStack.beginMacro(self.tr("Offset Map"))
            for layerIndex in layerIndexes:
                self.mUndoStack.push(OffsetLayer(self, layerIndex, offset,
                                                 bounds, wrapX, wrapY))

            self.mUndoStack.endMacro()

    ##
    # Flips the selected objects in the given \a direction.
    ##
    def flipSelectedObjects(self, direction):
        if (self.mSelectedObjects.isEmpty()):
            return
        self.mUndoStack.push(FlipMapObjects(self, self.mSelectedObjects, direction))

    ##
    # Rotates the selected objects.
    ##
    def rotateSelectedObjects(self, direction):
        if (self.mSelectedObjects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Rotate %n Object(s)", "", self.mSelectedObjects.size()))
        # TODO: Rotate them properly as a group
        for mapObject in self.mSelectedObjects:
            oldRotation = mapObject.rotation()
            newRotation = oldRotation
            if (direction == RotateDirection.RotateLeft):
                newRotation -= 90
                if (newRotation < -180):
                    newRotation += 360
            else:
                newRotation += 90
                if (newRotation > 180):
                    newRotation -= 360

            self.mUndoStack.push(RotateMapObject(self, mapObject, newRotation, oldRotation))

        self.mUndoStack.endMacro()

    ##
    # Adds a layer of the given type to the top of the layer stack. After adding
    # the new layer, emits editLayerNameRequested().
    ##
    def addLayer(self, layerType):
        layer = None
        name = QString()
        x = layerType
        if x==Layer.TileLayerType:
            name = self.tr("Tile Layer %d"%(self.mMap.tileLayerCount() + 1))
            layer = TileLayer(name, 0, 0, self.mMap.width(), self.mMap.height())
        elif x==Layer.ObjectGroupType:
            name = self.tr("Object Layer %d"%(self.mMap.objectGroupCount() + 1))
            layer = ObjectGroup(name, 0, 0, self.mMap.width(), self.mMap.height())
        elif x==Layer.ImageLayerType:
            name = self.tr("Image Layer %d"%(self.mMap.imageLayerCount() + 1))
            layer = ImageLayer(name, 0, 0, self.mMap.width(), self.mMap.height())

        index = self.mMap.layerCount()
        self.mUndoStack.push(AddLayer(self, index, layer))
        self.setCurrentLayerIndex(index)
        self.editLayerNameRequested.emit()

    ##
    # Duplicates the currently selected layer.
    ##
    def duplicateLayer(self):
        if (self.mCurrentLayerIndex == -1):
            return
        duplicate = self.mMap.layerAt(self.mCurrentLayerIndex).clone()
        duplicate.setName(self.tr("Copy of %s"%duplicate.name()))
        index = self.mCurrentLayerIndex + 1
        cmd = AddLayer(self, index, duplicate)
        cmd.setText(self.tr("Duplicate Layer"))
        self.mUndoStack.push(cmd)
        self.setCurrentLayerIndex(index)

    ##
    # Merges the currently selected layer with the layer below. This only works
    # when the layers can be merged.
    #
    # \see Layer.canMergeWith
    ##
    def mergeLayerDown(self):
        if (self.mCurrentLayerIndex < 1):
            return
        upperLayer = self.mMap.layerAt(self.mCurrentLayerIndex)
        lowerLayer = self.mMap.layerAt(self.mCurrentLayerIndex - 1)
        if (not lowerLayer.canMergeWith(upperLayer)):
            return
        merged = lowerLayer.mergedWith(upperLayer)
        self.mUndoStack.beginMacro(self.tr("Merge Layer Down"))
        self.mUndoStack.push(AddLayer(self, self.mCurrentLayerIndex - 1, merged))
        self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex))
        self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex))
        self.mUndoStack.endMacro()

    ##
    # Moves the given layer up. Does nothing when no valid layer index is
    # given.
    ##
    def moveLayerUp(self, index):
        if index<0 or index>=self.mMap.layerCount() - 1:
            return
        self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Up))

    ##
    # Moves the given layer down. Does nothing when no valid layer index is
    # given.
    ##
    def moveLayerDown(self, index):
        if index<1 or index>=self.mMap.layerCount():
            return
        self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Down))

    ##
    # Removes the given layer.
    ##
    def removeLayer(self, index):
        if index<0 or index>=self.mMap.layerCount():
            return
        self.mUndoStack.push(RemoveLayer(self, index))

    ##
    # Show or hide all other layers except the layer at the given index.
    # If any other layer is visible then all layers will be hidden, otherwise
    # the layers will be shown.
    ##
    def toggleOtherLayers(self, index):
        self.mLayerModel.toggleOtherLayers(index)

    ##
    # Adds a tileset to this map at the given \a index. Emits the appropriate
    # signal.
    ##
    def insertTileset(self, index, tileset):
        self.tilesetAboutToBeAdded.emit(index)
        self.mMap.insertTileset(index, tileset)
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReference(tileset)
        self.tilesetAdded.emit(index, tileset)

    ##
    # Removes the tileset at the given \a index from this map. Emits the
    # appropriate signal.
    #
    # \warning Does not make sure that any references to tiles in the removed
    #          tileset are cleared.
    ##
    def removeTilesetAt(self, index):
        self.tilesetAboutToBeRemoved.emit(index)
        tileset = self.mMap.tilesets().at(index)
        if (tileset == self.mCurrentObject or isFromTileset(self.mCurrentObject, tileset)):
            self.setCurrentObject(None)
        self.mMap.removeTilesetAt(index)
        self.tilesetRemoved.emit(tileset)
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReference(tileset)

    def moveTileset(self, _from, to):
        if (_from == to):
            return
        tileset = self.mMap.tilesets().at(_from)
        self.mMap.removeTilesetAt(_from)
        self.mMap.insertTileset(to, tileset)
        self.tilesetMoved.emit(_from, to)

    def setTilesetFileName(self, tileset, fileName):
        tileset.setFileName(fileName)
        self.tilesetFileNameChanged.emit(tileset)

    def setTilesetName(self, tileset, name):
        tileset.setName(name)
        self.tilesetNameChanged.emit(tileset)

    def setTilesetTileOffset(self, tileset, tileOffset):
        tileset.setTileOffset(tileOffset)
        self.mMap.recomputeDrawMargins()
        self.tilesetTileOffsetChanged.emit(tileset)

    def duplicateObjects(self, objects):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Duplicate %n Object(s)", "", objects.size()))
        clones = QList()
        for mapObject in objects:
            clone = mapObject.clone()
            clones.append(clone)
            self.mUndoStack.push(AddMapObject(self,
                                              mapObject.objectGroup(),
                                              clone))

        self.mUndoStack.endMacro()
        self.setSelectedObjects(clones)

    def removeObjects(self, objects):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Remove %n Object(s)", "", objects.size()))
        for mapObject in objects:
            self.mUndoStack.push(RemoveMapObject(self, mapObject))
        self.mUndoStack.endMacro()

    def moveObjectsToGroup(self, objects, objectGroup):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Move %n Object(s) to Layer", "",
                                  objects.size()))
        for mapObject in objects:
            if (mapObject.objectGroup() == objectGroup):
                continue
            self.mUndoStack.push(MoveMapObjectToGroup(self,
                                                      mapObject,
                                                      objectGroup))

        self.mUndoStack.endMacro()

    def setProperty(self, object, name, value):
        hadProperty = object.hasProperty(name)
        object.setProperty(name, value)
        if (hadProperty):
            self.propertyChanged.emit(object, name)
        else:
            self.propertyAdded.emit(object, name)

    def setProperties(self, object, properties):
        object.setProperties(properties)
        self.propertiesChanged.emit(object)

    def removeProperty(self, object, name):
        object.removeProperty(name)
        self.propertyRemoved.emit(object, name)

    ##
    # Returns the layer model. Can be used to modify the layer stack of the
    # map, and to display the layer stack in a view.
    ##
    def layerModel(self):
        return self.mLayerModel

    def mapObjectModel(self):
        return self.mMapObjectModel

    def terrainModel(self):
        return self.mTerrainModel

    ##
    # Returns the map renderer.
    ##
    def renderer(self):
        return self.mRenderer

    ##
    # Creates the map renderer. Should be called after changing the map
    # orientation.
    ##
    def createRenderer(self):
        if (self.mRenderer):
            del self.mRenderer
        x = self.mMap.orientation()
        if x==Map.Orientation.Isometric:
            self.mRenderer = IsometricRenderer(self.mMap)
        elif x==Map.Orientation.Staggered:
            self.mRenderer = StaggeredRenderer(self.mMap)
        elif x==Map.Orientation.Hexagonal:
            self.mRenderer = HexagonalRenderer(self.mMap)
        else:
            self.mRenderer = OrthogonalRenderer(self.mMap)

    ##
    # Returns the undo stack of this map document. Should be used to push any
    # commands on that modify the map.
    ##
    def undoStack(self):
        return self.mUndoStack

    ##
    # Returns the selected area of tiles.
    ##
    def selectedArea(self):
        return QRegion(self.mSelectedArea)

    ##
    # Sets the selected area of tiles.
    ##
    def setSelectedArea(self, selection):
        if (self.mSelectedArea != selection):
            oldSelectedArea = self.mSelectedArea
            self.mSelectedArea = selection
            self.selectedAreaChanged.emit(self.mSelectedArea, oldSelectedArea)

    ##
    # Returns the list of selected objects.
    ##
    def selectedObjects(self):
        return self.mSelectedObjects

    ##
    # Sets the list of selected objects, emitting the selectedObjectsChanged
    # signal.
    ##
    def setSelectedObjects(self, selectedObjects):
        if selectedObjects.nequal(self.mSelectedObjects):
            self.mSelectedObjects = selectedObjects
            self.selectedObjectsChanged.emit()
            if (selectedObjects.size() == 1):
                self.setCurrentObject(selectedObjects.first())

    ##
    # Returns the list of selected tiles.
    ##
    def selectedTiles(self):
        return self.mSelectedTiles

    def setSelectedTiles(self, selectedTiles):
        self.mSelectedTiles = selectedTiles
        self.selectedTilesChanged.emit()

    def currentObject(self):
        return self.mCurrentObject

    def setCurrentObject(self, object):
        if (object == self.mCurrentObject):
            return
        self.mCurrentObject = object
        self.currentObjectChanged.emit([object])

    def currentObjects(self):
        objects = QList()
        if (self.mCurrentObject):
            if (self.mCurrentObject.typeId() == Object.MapObjectType and not self.mSelectedObjects.isEmpty()):
                for mapObj in self.mSelectedObjects:
                    objects.append(mapObj)
            elif (self.mCurrentObject.typeId() == Object.TileType and not self.mSelectedTiles.isEmpty()):
                for tile in self.mSelectedTiles:
                    objects.append(tile)

            else:
                objects.append(self.mCurrentObject)

        return objects

    def unifyTilesets(self, *args):
        l = len(args)
        if l==1:
            ##
            # Makes sure the all tilesets which are used at the given \a map will be
            # present in the map document.
            #
            # To reach the aim, all similar tilesets will be replaced by the version
            # in the current map document and all missing tilesets will be added to
            # the current map document.
            #
            # \warning This method assumes that the tilesets in \a map are managed by
            #          the TilesetManager!
            ##
            map = args[0]
            undoCommands = QList()
            existingTilesets = self.mMap.tilesets()
            tilesetManager = TilesetManager.instance()
            # Add tilesets that are not yet part of this map
            for tileset in map.tilesets():
                if (existingTilesets.contains(tileset)):
                    continue
                replacement = tileset.findSimilarTileset(existingTilesets)
                if (not replacement):
                    undoCommands.append(AddTileset(self, tileset))
                    continue

                # Merge the tile properties
                sharedTileCount = min(tileset.tileCount(), replacement.tileCount())
                for i in range(sharedTileCount):
                    replacementTile = replacement.tileAt(i)
                    properties = replacementTile.properties()
                    properties.merge(tileset.tileAt(i).properties())
                    undoCommands.append(ChangeProperties(self,
                                                             self.tr("Tile"),
                                                             replacementTile,
                                                             properties))

                map.replaceTileset(tileset, replacement)
                tilesetManager.addReference(replacement)
                tilesetManager.removeReference(tileset)

            if (not undoCommands.isEmpty()):
                self.mUndoStack.beginMacro(self.tr("Tileset Changes"))
                for command in undoCommands:
                    self.mUndoStack.push(command)
                self.mUndoStack.endMacro()
        elif l==2:
            map, missingTilesets = args
            
            existingTilesets = self.mMap.tilesets()
            tilesetManager = TilesetManager.instance()

            for tileset in map.tilesets():
                # tileset already added
                if existingTilesets.contains(tileset):
                    continue

                replacement = tileset.findSimilarTileset(existingTilesets)

                # tileset not present and no replacement tileset found
                if not replacement:
                    if not missingTilesets.contains(tileset):
                        missingTilesets.append(tileset)
                    continue

                # replacement tileset found, change given map
                map.replaceTileset(tileset, replacement)

                tilesetManager.addReference(replacement)
                tilesetManager.removeReference(tileset)

    ##
    # Emits the map changed signal. This signal should be emitted after changing
    # the map size or its tile size.
    ##
    def emitMapChanged(self):
        self.mapChanged.emit()

    ##
    # Emits the region changed signal for the specified region. The region
    # should be in tile coordinates. This method is used by the TilePainter.
    ##
    def emitRegionChanged(self, region, layer):
        self.regionChanged.emit(region, layer)

    ##
    # Emits the region edited signal for the specified region and tile layer.
    # The region should be in tile coordinates. This should be called from
    # all map document changing classes which are triggered by user input.
    ##
    def emitRegionEdited(self, region, layer):
        self.regionEdited.emit(region, layer)

    def emitTileLayerDrawMarginsChanged(self, layer):
        self.tileLayerDrawMarginsChanged.emit(layer)

    ##
    # Emits the tileset changed signal. This signal is currently used when adding
    # or removing tiles from a tileset.
    #
    # @todo Emit more specific signals.
    ##
    def emitTilesetChanged(self, tileset):
        self.tilesetChanged.emit(tileset)

    ##
    # Emits the signal notifying about the terrain probability of a tile changing.
    ##
    def emitTileProbabilityChanged(self, tile):
        self.tileProbabilityChanged.emit(tile)

    ##
    # Emits the signal notifying tileset models about changes to tile terrain
    # information. All the \a tiles need to be from the same tileset.
    ##
    def emitTileTerrainChanged(self, tiles):
        if (not tiles.isEmpty()):
            self.tileTerrainChanged.emit(tiles)

    ##
    # Emits the signal notifying the TileCollisionEditor about the object group
    # of a tile changing.
    ##
    def emitTileObjectGroupChanged(self, tile):
        self.tileObjectGroupChanged.emit(tile)

    ##
    # Emits the signal notifying about the animation of a tile changing.
    ##
    def emitTileAnimationChanged(self, tile):
        self.tileAnimationChanged.emit(tile)

    ##
    # Emits the objectGroupChanged signal, should be called when changing the
    # color or drawing order of an object group.
    ##
    def emitObjectGroupChanged(self, objectGroup):
        self.objectGroupChanged.emit(objectGroup)

    ##
    # Emits the imageLayerChanged signal, should be called when changing the
    # image or the transparent color of an image layer.
    ##
    def emitImageLayerChanged(self, imageLayer):
        self.imageLayerChanged.emit(imageLayer)

    ##
    # Emits the editLayerNameRequested signal, to get renamed.
    ##
    def emitEditLayerNameRequested(self):
        self.editLayerNameRequested.emit()

    ##
    # Emits the editCurrentObject signal, which makes the Properties window become
    # visible and take focus.
    ##
    def emitEditCurrentObject(self):
        self.editCurrentObject.emit()

    ##
    # Before forwarding the signal, the objects are removed from the list of
    # selected objects, triggering a selectedObjectsChanged signal when
    # appropriate.
    ##
    def onObjectsRemoved(self, objects):
        self.deselectObjects(objects)
        self.objectsRemoved.emit(objects)

    def onMapObjectModelRowsInserted(self, parent, first, last):
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        if (not objectGroup): # we're not dealing with insertion of objects
            return
        self.objectsInserted.emit(objectGroup, first, last)
        self.onMapObjectModelRowsInsertedOrRemoved(parent, first, last)

    def onMapObjectModelRowsInsertedOrRemoved(self, parent, first, last):
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        if (not objectGroup):
            return
        # Inserting or removing objects changes the index of any that come after
        lastIndex = objectGroup.objectCount() - 1
        if (last < lastIndex):
            self.objectsIndexChanged.emit(objectGroup, last + 1, lastIndex)

    def onObjectsMoved(self, parent, start, end, destination, row):
        if (parent != destination):
            return
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        # Determine the full range over which object indexes changed
        first = min(start, row)
        last = max(end, row - 1)
        self.objectsIndexChanged.emit(objectGroup, first, last)

    def onLayerAdded(self, index):
        self.layerAdded.emit(index)
        # Select the first layer that gets added to the map
        if (self.mMap.layerCount() == 1):
            self.setCurrentLayerIndex(0)

    def onLayerAboutToBeRemoved(self, index):
        layer = self.mMap.layerAt(index)
        if (layer == self.mCurrentObject):
            self.setCurrentObject(None)
        # Deselect any objects on this layer when necessary
        og = layer
        if type(og) == ObjectGroup:
            self.deselectObjects(og.objects())
        self.layerAboutToBeRemoved.emit(index)

    def onLayerRemoved(self, index):
        # Bring the current layer index to safety
        currentLayerRemoved = self.mCurrentLayerIndex == self.mMap.layerCount()
        if (currentLayerRemoved):
            self.mCurrentLayerIndex = self.mCurrentLayerIndex - 1
        self.layerRemoved.emit(index)
        # Emitted after the layerRemoved signal so that the MapScene has a chance
        # of synchronizing before adapting to the newly selected index
        if (currentLayerRemoved):
            self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex)

    def onTerrainRemoved(self, terrain):
        if (terrain == self.mCurrentObject):
            self.setCurrentObject(None)

    def setFileName(self, fileName):
        if (self.mFileName == fileName):
            return
        oldFileName = self.mFileName
        self.mFileName = fileName
        self.fileNameChanged.emit(fileName, oldFileName)

    def deselectObjects(self, objects):
        # Unset the current object when it was part of this list of objects
        if (self.mCurrentObject and self.mCurrentObject.typeId() == Object.MapObjectType):
            if (objects.contains(self.mCurrentObject)):
                self.setCurrentObject(None)
        removedCount = 0
        for object in objects:
            removedCount += self.mSelectedObjects.removeAll(object)
        if (removedCount > 0):
            self.selectedObjectsChanged.emit()

    def disconnect(self):
        try:
            super().disconnect()
        except:
            pass
Esempio n. 45
0
class DiagramScene(QGraphicsScene):
    """
    This class implements the main Diagram Scene.
    """
    GridPen = QPen(QColor(80, 80, 80), 0, Qt.SolidLine)
    GridSize = 20
    MinSize = 2000
    MaxSize = 1000000
    RecentNum = 5

    sgnInsertionEnded = pyqtSignal('QGraphicsItem', int)
    sgnItemAdded = pyqtSignal('QGraphicsItem')
    sgnModeChanged = pyqtSignal(DiagramMode)
    sgnItemRemoved = pyqtSignal('QGraphicsItem')
    sgnUpdated = pyqtSignal()

    ####################################################################################################################
    #                                                                                                                  #
    #   DIAGRAM SCENE IMPLEMENTATION                                                                                   #
    #                                                                                                                  #
    ####################################################################################################################

    def __init__(self, mainwindow, parent=None):
        """
        Initialize the diagram scene.
        :type mainwindow: MainWindow
        :type parent: QWidget
        """
        super().__init__(parent)
        self.document = File(parent=self)
        self.guid = GUID(self)
        self.factory = ItemFactory(self)
        self.index = ItemIndex(self)
        self.meta = PredicateMetaIndex(self)
        self.undostack = QUndoStack(self)
        self.undostack.setUndoLimit(50)
        self.validator = OWL2Validator(self)
        self.mainwindow = mainwindow
        self.pasteOffsetX = Clipboard.PasteOffsetX
        self.pasteOffsetY = Clipboard.PasteOffsetY
        self.mode = DiagramMode.Idle
        self.modeParam = Item.Undefined
        self.mouseOverNode = None
        self.mousePressEdge = None
        self.mousePressPos = None
        self.mousePressNode = None
        self.mousePressNodePos = None
        self.mousePressData = {}

        connect(self.sgnItemAdded, self.index.add)
        connect(self.sgnItemRemoved, self.index.remove)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def dragEnterEvent(self, dragEvent):
        """
        Executed when a dragged element enters the scene area.
        :type dragEvent: QGraphicsSceneDragDropEvent
        """
        super().dragEnterEvent(dragEvent)
        if dragEvent.mimeData().hasFormat('text/plain'):
            dragEvent.setDropAction(Qt.CopyAction)
            dragEvent.accept()
        else:
            dragEvent.ignore()

    def dragMoveEvent(self, dragEvent):
        """
        Executed when an element is dragged over the scene.
        :type dragEvent: QGraphicsSceneDragDropEvent
        """
        super().dragMoveEvent(dragEvent)
        if dragEvent.mimeData().hasFormat('text/plain'):
            dragEvent.setDropAction(Qt.CopyAction)
            dragEvent.accept()
        else:
            dragEvent.ignore()

    def dropEvent(self, dropEvent):
        """
        Executed when a dragged element is dropped on the scene.
        :type dropEvent: QGraphicsSceneDragDropEvent
        """
        super().dropEvent(dropEvent)
        if dropEvent.mimeData().hasFormat('text/plain'):
            item = Item.forValue(dropEvent.mimeData().text())
            node = self.factory.create(item=item, scene=self)
            node.setPos(snap(dropEvent.scenePos(), DiagramScene.GridSize, self.mainwindow.snapToGrid))
            self.undostack.push(CommandNodeAdd(scene=self, node=node))
            self.sgnInsertionEnded.emit(node, dropEvent.modifiers())
            dropEvent.setDropAction(Qt.CopyAction)
            dropEvent.accept()
        else:
            dropEvent.ignore()

    def mousePressEvent(self, mouseEvent):
        """
        Executed when a mouse button is clicked on the scene.
        :type mouseEvent: QGraphicsSceneMouseEvent
        """
        mouseButtons = mouseEvent.buttons()
        mousePos = mouseEvent.scenePos()

        if mouseButtons & Qt.LeftButton:

            if self.mode is DiagramMode.InsertNode:

                ########################################################################################################
                #                                                                                                      #
                #                                         NODE INSERTION                                               #
                #                                                                                                      #
                ########################################################################################################

                item = Item.forValue(self.modeParam)
                node = self.factory.create(item, self)
                node.setPos(snap(mousePos, DiagramScene.GridSize, self.mainwindow.snapToGrid))
                self.undostack.push(CommandNodeAdd(self, node))
                self.sgnInsertionEnded.emit(node, mouseEvent.modifiers())

                super().mousePressEvent(mouseEvent)

            elif self.mode is DiagramMode.InsertEdge:

                ########################################################################################################
                #                                                                                                      #
                #                                         EDGE INSERTION                                               #
                #                                                                                                      #
                ########################################################################################################

                node = self.itemOnTopOf(mousePos, edges=False)
                if node:
                    item = Item.forValue(self.modeParam)
                    edge = self.factory.create(item, self, source=node)
                    edge.updateEdge(mousePos)
                    self.mousePressEdge = edge
                    self.addItem(edge)

                super().mousePressEvent(mouseEvent)

            else:

                super().mousePressEvent(mouseEvent)

                if self.mode is DiagramMode.Idle:

                    ####################################################################################################
                    #                                                                                                  #
                    #                                      ITEM SELECTION                                              #
                    #                                                                                                  #
                    ####################################################################################################

                    # See if we have some nodes selected in the scene: this is needed because itemOnTopOf
                    # will discard labels, so if we have a node whose label is overlapping the node shape,
                    # clicking on the label will make itemOnTopOf return the node item instead of the label.
                    selected = self.selectedNodes()

                    if selected:

                        # We have some nodes selected in the scene so we probably are going to do a
                        # move operation, prepare data for mouse move event => select a node that will act
                        # as mouse grabber to compute delta movements for each componenet in the selection.
                        self.mousePressNode = self.itemOnTopOf(mousePos, edges=False)

                        if self.mousePressNode:

                            self.mousePressNodePos = self.mousePressNode.pos()
                            self.mousePressPos = mousePos

                            self.mousePressData = {
                                'nodes': {
                                    node: {
                                        'anchors': {k: v for k, v in node.anchors.items()},
                                        'pos': node.pos(),
                                    } for node in selected},
                                'edges': {}
                            }

                            # Figure out if the nodes we are moving are sharing edges: if so, move the edge
                            # together with the nodes (which actually means moving the edge breakpoints).
                            for node in self.mousePressData['nodes']:
                                for edge in node.edges:
                                    if edge not in self.mousePressData['edges']:
                                        if edge.other(node).isSelected():
                                            self.mousePressData['edges'][edge] = edge.breakpoints[:]

    def mouseMoveEvent(self, mouseEvent):
        """
        Executed when then mouse is moved on the scene.
        :type mouseEvent: QGraphicsSceneMouseEvent
        """
        mouseButtons = mouseEvent.buttons()
        mousePos = mouseEvent.scenePos()

        if mouseButtons & Qt.LeftButton:

            if self.mode is DiagramMode.InsertEdge:

                ########################################################################################################
                #                                                                                                      #
                #                                         EDGE INSERTION                                               #
                #                                                                                                      #
                ########################################################################################################

                if self.mousePressEdge:

                    edge = self.mousePressEdge
                    edge.updateEdge(mousePos)
                    currentNode = self.itemOnTopOf(mousePos, edges=False, skip={edge.source})
                    previousNode = self.mouseOverNode
                    statusBar = self.mainwindow.statusBar()

                    if previousNode:
                        previousNode.updateBrush(selected=False)

                    if currentNode:
                        self.mouseOverNode = currentNode
                        res = self.validator.result(edge.source, edge, currentNode)
                        currentNode.updateBrush(selected=False, valid=res.valid)
                        if not res.valid:
                            statusBar.showMessage(res.message)
                        else:
                            statusBar.clearMessage()
                    else:
                        statusBar.clearMessage()
                        self.mouseOverNode = None
                        self.validator.clear()

            else:

                if self.mode is DiagramMode.Idle:
                    if self.mousePressNode:
                        self.setMode(DiagramMode.MoveNode)

                if self.mode is DiagramMode.MoveNode:

                    ####################################################################################################
                    #                                                                                                  #
                    #                                       ITEM MOVEMENT                                              #
                    #                                                                                                  #
                    ####################################################################################################

                    point = self.mousePressNodePos + mousePos - self.mousePressPos
                    point = snap(point, DiagramScene.GridSize, self.mainwindow.snapToGrid)
                    delta = point - self.mousePressNodePos
                    edges = set()

                    # Update all the breakpoints positions.
                    for edge, breakpoints in self.mousePressData['edges'].items():
                        for i in range(len(breakpoints)):
                            edge.breakpoints[i] = breakpoints[i] + delta

                    # Move all the selected nodes.
                    for node, data in self.mousePressData['nodes'].items():
                        edges |= set(node.edges)
                        node.setPos(data['pos'] + delta)
                        for edge, pos in data['anchors'].items():
                            node.setAnchor(edge, pos + delta)

                    # Update edges.
                    for edge in edges:
                        edge.updateEdge()

        super().mouseMoveEvent(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        """
        Executed when the mouse is released from the scene.
        :type mouseEvent: QGraphicsSceneMouseEvent
        """
        mouseButton = mouseEvent.button()
        mousePos = mouseEvent.scenePos()

        if mouseButton == Qt.LeftButton:

            if self.mode is DiagramMode.InsertEdge:

                ########################################################################################################
                #                                                                                                      #
                #                                         EDGE INSERTION                                               #
                #                                                                                                      #
                ########################################################################################################

                if self.mousePressEdge:

                    edge = self.mousePressEdge
                    edge.source.updateBrush(selected=False)
                    currentNode = self.itemOnTopOf(mousePos, edges=False, skip={edge.source})
                    insertEdge = False

                    if currentNode:
                        currentNode.updateBrush(selected=False)
                        if self.validator.valid(edge.source, edge, currentNode):
                            edge.target = currentNode
                            insertEdge = True

                    # We remove the item temporarily from the graphics scene and we perform the add using
                    # the undo command that will also emit the sgnItemAdded signal hence all the widgets will
                    # be notified of the edge insertion. We do this because while creating the edge we need
                    # to display it so the users knows what is he connecting, but we don't want to truly insert
                    # it till it's necessary (when the mouse is released and the validator allows the insertion)
                    self.removeItem(edge)

                    if insertEdge:
                        self.undostack.push(CommandEdgeAdd(self, edge))
                        edge.updateEdge()

                    self.mouseOverNode = None
                    self.mousePressEdge = None
                    self.clearSelection()
                    self.validator.clear()
                    statusBar = self.mainwindow.statusBar()
                    statusBar.clearMessage()

                    self.sgnInsertionEnded.emit(edge, mouseEvent.modifiers())

            elif self.mode is DiagramMode.MoveNode:

                ########################################################################################################
                #                                                                                                      #
                #                                         ITEM MOVEMENT                                                #
                #                                                                                                      #
                ########################################################################################################

                data = {
                    'undo': self.mousePressData,
                    'redo': {
                        'nodes': {
                            node: {
                                'anchors': {k: v for k, v in node.anchors.items()},
                                'pos': node.pos(),
                            } for node in self.mousePressData['nodes']},
                        'edges': {x: x.breakpoints[:] for x in self.mousePressData['edges']}
                    }
                }

                self.undostack.push(CommandNodeMove(self, data))
                self.setMode(DiagramMode.Idle)

        elif mouseButton == Qt.RightButton:

            if self.mode is not DiagramMode.SceneDrag:

                ########################################################################################################
                #                                                                                                      #
                #                                         CONTEXT MENU                                                 #
                #                                                                                                      #
                ########################################################################################################

                item = self.itemOnTopOf(mousePos)
                if item:
                    self.clearSelection()
                    item.setSelected(True)

                self.mousePressPos = mousePos
                menu = self.mainwindow.menuFactory.create(self.mainwindow, self, item, mousePos)
                menu.exec_(mouseEvent.screenPos())

        super().mouseReleaseEvent(mouseEvent)

        self.mousePressPos = None
        self.mousePressNode = None
        self.mousePressNodePos = None
        self.mousePressData = None

    ####################################################################################################################
    #                                                                                                                  #
    #   AXIOMS COMPOSITION                                                                                             #
    #                                                                                                                  #
    ####################################################################################################################

    def propertyAxiomComposition(self, source, restriction):
        """
        Returns a collection of items to be added to the given source node to compose a property axiom.
        :type source: AbstractNode
        :type restriction: class
        :rtype: set
        """
        node = restriction(scene=self)
        edge = InputEdge(scene=self, source=source, target=node)

        size = DiagramScene.GridSize

        offsets = (
            QPointF(snapF(+source.width() / 2 + 70, size), 0),
            QPointF(snapF(-source.width() / 2 - 70, size), 0),
            QPointF(0, snapF(-source.height() / 2 - 70, size)),
            QPointF(0, snapF(+source.height() / 2 + 70, size)),
            QPointF(snapF(+source.width() / 2 + 70, size), snapF(-source.height() / 2 - 70, size)),
            QPointF(snapF(-source.width() / 2 - 70, size), snapF(-source.height() / 2 - 70, size)),
            QPointF(snapF(+source.width() / 2 + 70, size), snapF(+source.height() / 2 + 70, size)),
            QPointF(snapF(-source.width() / 2 - 70, size), snapF(+source.height() / 2 + 70, size)),
        )

        pos = None
        num = sys.maxsize
        rad = QPointF(node.width() / 2, node.height() / 2)

        for o in offsets:
            count = len(self.items(QRectF(source.pos() + o - rad, source.pos() + o + rad)))
            if count < num:
                num = count
                pos = source.pos() + o

        node.setPos(pos)

        return {node, edge}

    def propertyDomainAxiomComposition(self, source):
        """
        Returns a collection of items to be added to the given source node to compose a property domain.
        :type source: AbstractNode
        :rtype: set
        """
        return self.propertyAxiomComposition(source, DomainRestrictionNode)

    def propertyRangeAxiomComposition(self, source):
        """
        Returns a collection of items to be added to the given source node to compose a property range.
        :type source: AbstractNode
        :rtype: set
        """
        return self.propertyAxiomComposition(source, RangeRestrictionNode)

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot()
    def clear(self):
        """
        Clear the diagram by removing all the elements.
        """
        self.index.clear()
        self.undostack.clear()
        super().clear()

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def edge(self, eid):
        """
        Returns the edge matching the given edge id.
        :type eid: str
        """
        return self.index.edgeForId(eid)

    def edges(self):
        """
        Returns a view on all the edges of the diagram.
        :rtype: view
        """
        return self.index.edges()

    def itemOnTopOf(self, point, nodes=True, edges=True, skip=None):
        """
        Returns the shape which is on top of the given point.
        :type point: QPointF
        :type nodes: bool
        :type edges: bool
        :type skip: iterable
        :rtype: Item
        """
        skip = skip or {}
        data = [x for x in self.items(point) if (nodes and x.node or edges and x.edge) and x not in skip]
        if data:
            return max(data, key=lambda x: x.zValue())
        return None

    def node(self, nid):
        """
        Returns the node matching the given node id.
        :type nid: str
        """
        return self.index.nodeForId(nid)

    def nodes(self):
        """
        Returns a view on all the nodes in the diagram.
        :rtype: view
        """
        return self.index.nodes()

    def selectedEdges(self):
        """
        Returns the edges selected in the scene.
        :rtype: list
        """
        return [x for x in super(DiagramScene, self).selectedItems() if x.edge]

    def selectedItems(self):
        """
        Returns the items selected in the scene (will filter out labels since we don't need them).
        :rtype: list
        """
        return [x for x in super(DiagramScene, self).selectedItems() if x.node or x.edge]

    def selectedNodes(self):
        """
        Returns the nodes selected in the scene.
        :rtype: list
        """
        return [x for x in super(DiagramScene, self).selectedItems() if x.node]

    def setMode(self, mode, param=None):
        """
        Set the operation mode.
        :type mode: DiagramMode
        :type param: int
        """
        if self.mode != mode or self.modeParam != param:
            self.mode = mode
            self.modeParam = param
            self.sgnModeChanged.emit(mode)

    def visibleRect(self, margin=0):
        """
        Returns a rectangle matching the area of visible items.
        :type margin: float
        :rtype: QRectF
        """
        bound = self.itemsBoundingRect()
        topLeft = QPointF(bound.left() - margin, bound.top() - margin)
        bottomRight = QPointF(bound.right() + margin, bound.bottom() + margin)
        return QRectF(topLeft, bottomRight)
Esempio n. 46
0
class IconEditorGrid(QWidget):
    """
    Class implementing the icon editor grid.
    
    @signal canRedoChanged(bool) emitted after the redo status has changed
    @signal canUndoChanged(bool) emitted after the undo status has changed
    @signal clipboardImageAvailable(bool) emitted to signal the availability
        of an image to be pasted
    @signal colorChanged(QColor) emitted after the drawing color was changed
    @signal imageChanged(bool) emitted after the image was modified
    @signal positionChanged(int, int) emitted after the cursor poition was
        changed
    @signal previewChanged(QPixmap) emitted to signal a new preview pixmap
    @signal selectionAvailable(bool) emitted to signal a change of the
        selection
    @signal sizeChanged(int, int) emitted after the size has been changed
    @signal zoomChanged(int) emitted to signal a change of the zoom value
    """
    canRedoChanged = pyqtSignal(bool)
    canUndoChanged = pyqtSignal(bool)
    clipboardImageAvailable = pyqtSignal(bool)
    colorChanged = pyqtSignal(QColor)
    imageChanged = pyqtSignal(bool)
    positionChanged = pyqtSignal(int, int)
    previewChanged = pyqtSignal(QPixmap)
    selectionAvailable = pyqtSignal(bool)
    sizeChanged = pyqtSignal(int, int)
    zoomChanged = pyqtSignal(int)
    
    Pencil = 1
    Rubber = 2
    Line = 3
    Rectangle = 4
    FilledRectangle = 5
    Circle = 6
    FilledCircle = 7
    Ellipse = 8
    FilledEllipse = 9
    Fill = 10
    ColorPicker = 11
    
    RectangleSelection = 20
    CircleSelection = 21
    
    MarkColor = QColor(255, 255, 255, 255)
    NoMarkColor = QColor(0, 0, 0, 0)
    
    ZoomMinimum = 100
    ZoomMaximum = 10000
    ZoomStep = 100
    ZoomDefault = 1200
    ZoomPercent = True
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(IconEditorGrid, self).__init__(parent)
        
        self.setAttribute(Qt.WA_StaticContents)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        
        self.__curColor = Qt.black
        self.__zoom = 12
        self.__curTool = self.Pencil
        self.__startPos = QPoint()
        self.__endPos = QPoint()
        self.__dirty = False
        self.__selecting = False
        self.__selRect = QRect()
        self.__isPasting = False
        self.__clipboardSize = QSize()
        self.__pasteRect = QRect()
        
        self.__undoStack = QUndoStack(self)
        self.__currentUndoCmd = None
        
        self.__image = QImage(32, 32, QImage.Format_ARGB32)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.__markImage = QImage(self.__image)
        self.__markImage.fill(self.NoMarkColor.rgba())
        
        self.__compositingMode = QPainter.CompositionMode_SourceOver
        self.__lastPos = (-1, -1)
        
        self.__gridEnabled = True
        self.__selectionAvailable = False
        
        self.__initCursors()
        self.__initUndoTexts()
        
        self.setMouseTracking(True)
        
        self.__undoStack.canRedoChanged.connect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.connect(self.canUndoChanged)
        self.__undoStack.cleanChanged.connect(self.__cleanChanged)
        
        self.imageChanged.connect(self.__updatePreviewPixmap)
        QApplication.clipboard().dataChanged.connect(self.__checkClipboard)
        
        self.__checkClipboard()
    
    def __initCursors(self):
        """
        Private method to initialize the various cursors.
        """
        self.__normalCursor = QCursor(Qt.ArrowCursor)
        
        pix = QPixmap(":colorpicker-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__colorPickerCursor = QCursor(pix, 1, 21)
        
        pix = QPixmap(":paintbrush-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__paintCursor = QCursor(pix, 0, 19)
        
        pix = QPixmap(":fill-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__fillCursor = QCursor(pix, 3, 20)
        
        pix = QPixmap(":aim-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__aimCursor = QCursor(pix, 10, 10)
        
        pix = QPixmap(":eraser-cursor.xpm")
        mask = pix.createHeuristicMask()
        pix.setMask(mask)
        self.__rubberCursor = QCursor(pix, 1, 16)
    
    def __initUndoTexts(self):
        """
        Private method to initialize texts to be associated with undo commands
        for the various drawing tools.
        """
        self.__undoTexts = {
            self.Pencil: self.tr("Set Pixel"),
            self.Rubber: self.tr("Erase Pixel"),
            self.Line: self.tr("Draw Line"),
            self.Rectangle: self.tr("Draw Rectangle"),
            self.FilledRectangle: self.tr("Draw Filled Rectangle"),
            self.Circle: self.tr("Draw Circle"),
            self.FilledCircle: self.tr("Draw Filled Circle"),
            self.Ellipse: self.tr("Draw Ellipse"),
            self.FilledEllipse: self.tr("Draw Filled Ellipse"),
            self.Fill: self.tr("Fill Region"),
        }
    
    def isDirty(self):
        """
        Public method to check the dirty status.
        
        @return flag indicating a modified status (boolean)
        """
        return self.__dirty
    
    def setDirty(self, dirty, setCleanState=False):
        """
        Public slot to set the dirty flag.
        
        @param dirty flag indicating the new modification status (boolean)
        @param setCleanState flag indicating to set the undo stack to clean
            (boolean)
        """
        self.__dirty = dirty
        self.imageChanged.emit(dirty)
        
        if not dirty and setCleanState:
            self.__undoStack.setClean()
    
    def sizeHint(self):
        """
        Public method to report the size hint.
        
        @return size hint (QSize)
        """
        size = self.__zoom * self.__image.size()
        if self.__zoom >= 3 and self.__gridEnabled:
            size += QSize(1, 1)
        return size
    
    def setPenColor(self, newColor):
        """
        Public method to set the drawing color.
        
        @param newColor reference to the new color (QColor)
        """
        self.__curColor = QColor(newColor)
        self.colorChanged.emit(QColor(newColor))
    
    def penColor(self):
        """
        Public method to get the current drawing color.
        
        @return current drawing color (QColor)
        """
        return QColor(self.__curColor)
    
    def setCompositingMode(self, mode):
        """
        Public method to set the compositing mode.
        
        @param mode compositing mode to set (QPainter.CompositionMode)
        """
        self.__compositingMode = mode
    
    def compositingMode(self):
        """
        Public method to get the compositing mode.
        
        @return compositing mode (QPainter.CompositionMode)
        """
        return self.__compositingMode
    
    def setTool(self, tool):
        """
        Public method to set the current drawing tool.
        
        @param tool drawing tool to be used
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        self.__curTool = tool
        self.__lastPos = (-1, -1)
        
        if self.__curTool in [self.RectangleSelection, self.CircleSelection]:
            self.__selecting = True
        else:
            self.__selecting = False
        
        if self.__curTool in [self.RectangleSelection, self.CircleSelection,
                              self.Line, self.Rectangle, self.FilledRectangle,
                              self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse]:
            self.setCursor(self.__aimCursor)
        elif self.__curTool == self.Fill:
            self.setCursor(self.__fillCursor)
        elif self.__curTool == self.ColorPicker:
            self.setCursor(self.__colorPickerCursor)
        elif self.__curTool == self.Pencil:
            self.setCursor(self.__paintCursor)
        elif self.__curTool == self.Rubber:
            self.setCursor(self.__rubberCursor)
        else:
            self.setCursor(self.__normalCursor)
    
    def tool(self):
        """
        Public method to get the current drawing tool.
        
        @return current drawing tool
            (IconEditorGrid.Pencil ... IconEditorGrid.CircleSelection)
        """
        return self.__curTool
    
    def setIconImage(self, newImage, undoRedo=False, clearUndo=False):
        """
        Public method to set a new icon image.
        
        @param newImage reference to the new image (QImage)
        @keyparam undoRedo flag indicating an undo or redo operation (boolean)
        @keyparam clearUndo flag indicating to clear the undo stack (boolean)
        """
        if newImage != self.__image:
            self.__image = newImage.convertToFormat(QImage.Format_ARGB32)
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())
            
            self.__markImage = QImage(self.__image)
            self.__markImage.fill(self.NoMarkColor.rgba())
            
            if undoRedo:
                self.setDirty(not self.__undoStack.isClean())
            else:
                self.setDirty(False)
            
            if clearUndo:
                self.__undoStack.clear()
            
            self.sizeChanged.emit(*self.iconSize())
    
    def iconImage(self):
        """
        Public method to get a copy of the icon image.
        
        @return copy of the icon image (QImage)
        """
        return QImage(self.__image)
    
    def iconSize(self):
        """
        Public method to get the size of the icon.
        
        @return width and height of the image as a tuple (integer, integer)
        """
        return self.__image.width(), self.__image.height()
    
    def setZoomFactor(self, newZoom):
        """
        Public method to set the zoom factor in percent.
        
        @param newZoom zoom factor (integer >= 100)
        """
        newZoom = max(100, newZoom)   # must not be less than 100
        if newZoom != self.__zoom:
            self.__zoom = newZoom // 100
            self.update()
            self.updateGeometry()
            self.resize(self.sizeHint())
            self.zoomChanged.emit(int(self.__zoom * 100))
    
    def zoomFactor(self):
        """
        Public method to get the current zoom factor in percent.
        
        @return zoom factor (integer)
        """
        return self.__zoom * 100
    
    def setGridEnabled(self, enable):
        """
        Public method to enable the display of grid lines.
        
        @param enable enabled status of the grid lines (boolean)
        """
        if enable != self.__gridEnabled:
            self.__gridEnabled = enable
            self.update()
    
    def isGridEnabled(self):
        """
        Public method to get the grid lines status.
        
        @return enabled status of the grid lines (boolean)
        """
        return self.__gridEnabled
    
    def paintEvent(self, evt):
        """
        Protected method called to repaint some of the widget.
        
        @param evt reference to the paint event object (QPaintEvent)
        """
        painter = QPainter(self)
        
        if self.__zoom >= 3 and self.__gridEnabled:
            painter.setPen(self.palette().windowText().color())
            i = 0
            while i <= self.__image.width():
                painter.drawLine(
                    self.__zoom * i, 0,
                    self.__zoom * i, self.__zoom * self.__image.height())
                i += 1
            j = 0
            while j <= self.__image.height():
                painter.drawLine(
                    0, self.__zoom * j,
                    self.__zoom * self.__image.width(), self.__zoom * j)
                j += 1
        
        col = QColor("#aaa")
        painter.setPen(Qt.DashLine)
        for i in range(0, self.__image.width()):
            for j in range(0, self.__image.height()):
                rect = self.__pixelRect(i, j)
                if evt.region().intersects(rect):
                    color = QColor.fromRgba(self.__image.pixel(i, j))
                    painter.fillRect(rect, QBrush(Qt.white))
                    painter.fillRect(QRect(rect.topLeft(), rect.center()), col)
                    painter.fillRect(QRect(rect.center(), rect.bottomRight()),
                                     col)
                    painter.fillRect(rect, QBrush(color))
                
                    if self.__isMarked(i, j):
                        painter.drawRect(rect.adjusted(0, 0, -1, -1))
        
        painter.end()
    
    def __pixelRect(self, i, j):
        """
        Private method to determine the rectangle for a given pixel coordinate.
        
        @param i x-coordinate of the pixel in the image (integer)
        @param j y-coordinate of the pixel in the image (integer)
        @return rectangle for the given pixel coordinates (QRect)
        """
        if self.__zoom >= 3 and self.__gridEnabled:
            return QRect(self.__zoom * i + 1, self.__zoom * j + 1,
                         self.__zoom - 1, self.__zoom - 1)
        else:
            return QRect(self.__zoom * i, self.__zoom * j,
                         self.__zoom, self.__zoom)
    
    def mousePressEvent(self, evt):
        """
        Protected method to handle mouse button press events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__isPasting:
                self.__isPasting = False
                self.editPaste(True)
                self.__markImage.fill(self.NoMarkColor.rgba())
                self.update(self.__pasteRect)
                self.__pasteRect = QRect()
                return
            
            if self.__curTool == self.Pencil:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Rubber:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                self.__currentUndoCmd = cmd
            elif self.__curTool == self.Fill:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                self.__drawFlood(i, j, col)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
            elif self.__curTool == self.ColorPicker:
                i, j = self.__imageCoordinates(evt.pos())
                col = QColor()
                col.setRgba(self.__image.pixel(i, j))
                self.setPenColor(col)
            else:
                self.__unMark()
                self.__startPos = evt.pos()
                self.__endPos = evt.pos()
    
    def mouseMoveEvent(self, evt):
        """
        Protected method to handle mouse move events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        self.positionChanged.emit(*self.__imageCoordinates(evt.pos()))
        
        if self.__isPasting and not (evt.buttons() & Qt.LeftButton):
            self.__drawPasteRect(evt.pos())
            return
        
        if evt.buttons() & Qt.LeftButton:
            if self.__curTool == self.Pencil:
                self.__setImagePixel(evt.pos(), True)
                self.setDirty(True)
            elif self.__curTool == self.Rubber:
                self.__setImagePixel(evt.pos(), False)
                self.setDirty(True)
            elif self.__curTool in [self.Fill, self.ColorPicker]:
                pass    # do nothing
            else:
                self.__drawTool(evt.pos(), True)
    
    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle mouse button release events.
        
        @param evt reference to the mouse event object (QMouseEvent)
        """
        if evt.button() == Qt.LeftButton:
            if self.__curTool in [self.Pencil, self.Rubber]:
                if self.__currentUndoCmd:
                    self.__currentUndoCmd.setAfterImage(self.__image)
                    self.__currentUndoCmd = None
            
            if self.__curTool not in [self.Pencil, self.Rubber,
                                      self.Fill, self.ColorPicker,
                                      self.RectangleSelection,
                                      self.CircleSelection]:
                cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
                                      self.__image)
                if self.__drawTool(evt.pos(), False):
                    self.__undoStack.push(cmd)
                    cmd.setAfterImage(self.__image)
                    self.setDirty(True)
    
    def __setImagePixel(self, pos, opaque):
        """
        Private slot to set or erase a pixel.
        
        @param pos position of the pixel in the widget (QPoint)
        @param opaque flag indicating a set operation (boolean)
        """
        i, j = self.__imageCoordinates(pos)
        
        if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos:
            if opaque:
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawPoint(i, j)
            else:
                self.__image.setPixel(i, j, qRgba(0, 0, 0, 0))
            self.__lastPos = (i, j)
        
            self.update(self.__pixelRect(i, j))
    
    def __imageCoordinates(self, pos):
        """
        Private method to convert from widget to image coordinates.
        
        @param pos widget coordinate (QPoint)
        @return tuple with the image coordinates (tuple of two integers)
        """
        i = pos.x() // self.__zoom
        j = pos.y() // self.__zoom
        return i, j
    
    def __drawPasteRect(self, pos):
        """
        Private slot to draw a rectangle for signaling a paste operation.
        
        @param pos widget position of the paste rectangle (QPoint)
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__pasteRect.isValid():
            self.__updateImageRect(
                self.__pasteRect.topLeft(),
                self.__pasteRect.bottomRight() + QPoint(1, 1))
        
        x, y = self.__imageCoordinates(pos)
        isize = self.__image.size()
        if x + self.__clipboardSize.width() <= isize.width():
            sx = self.__clipboardSize.width()
        else:
            sx = isize.width() - x
        if y + self.__clipboardSize.height() <= isize.height():
            sy = self.__clipboardSize.height()
        else:
            sy = isize.height() - y
        
        self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1))
        
        painter = QPainter(self.__markImage)
        painter.setPen(self.MarkColor)
        painter.drawRect(self.__pasteRect)
        painter.end()
        
        self.__updateImageRect(self.__pasteRect.topLeft(),
                               self.__pasteRect.bottomRight() + QPoint(1, 1))
    
    def __drawTool(self, pos, mark):
        """
        Private method to perform a draw operation depending of the current
        tool.
        
        @param pos widget coordinate to perform the draw operation at (QPoint)
        @param mark flag indicating a mark operation (boolean)
        @return flag indicating a successful draw (boolean)
        """
        self.__unMark()
        
        if mark:
            self.__endPos = QPoint(pos)
            drawColor = self.MarkColor
            img = self.__markImage
        else:
            drawColor = self.penColor()
            img = self.__image
        
        start = QPoint(*self.__imageCoordinates(self.__startPos))
        end = QPoint(*self.__imageCoordinates(pos))
        
        painter = QPainter(img)
        painter.setPen(drawColor)
        painter.setCompositionMode(self.__compositingMode)
        
        if self.__curTool == self.Line:
            painter.drawLine(start, end)
        
        elif self.__curTool in [self.Rectangle, self.FilledRectangle,
                                self.RectangleSelection]:
            left = min(start.x(), end.x())
            top = min(start.y(), end.y())
            right = max(start.x(), end.x())
            bottom = max(start.y(), end.y())
            if self.__curTool == self.RectangleSelection:
                painter.setBrush(QBrush(drawColor))
            if self.__curTool == self.FilledRectangle:
                for y in range(top, bottom + 1):
                    painter.drawLine(left, y, right, y)
            else:
                painter.drawRect(left, top, right - left, bottom - top)
            if self.__selecting:
                self.__selRect = QRect(
                    left, top, right - left + 1, bottom - top + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)
        
        elif self.__curTool in [self.Circle, self.FilledCircle,
                                self.CircleSelection]:
            r = max(abs(start.x() - end.x()), abs(start.y() - end.y()))
            if self.__curTool in [self.FilledCircle, self.CircleSelection]:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r, r)
            if self.__selecting:
                self.__selRect = QRect(start.x() - r, start.y() - r,
                                       2 * r + 1, 2 * r + 1)
                self.__selectionAvailable = True
                self.selectionAvailable.emit(True)
        
        elif self.__curTool in [self.Ellipse, self.FilledEllipse]:
            r1 = abs(start.x() - end.x())
            r2 = abs(start.y() - end.y())
            if r1 == 0 or r2 == 0:
                return False
            if self.__curTool == self.FilledEllipse:
                painter.setBrush(QBrush(drawColor))
            painter.drawEllipse(start, r1, r2)
        
        painter.end()
        
        if self.__curTool in [self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse]:
            self.update()
        else:
            self.__updateRect(self.__startPos, pos)
        
        return True
    
    def __drawFlood(self, i, j, oldColor, doUpdate=True):
        """
        Private method to perform a flood fill operation.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @param oldColor reference to the color at position i, j (QColor)
        @param doUpdate flag indicating an update is requested (boolean)
            (used for speed optimizations)
        """
        if not self.__image.rect().contains(i, j) or \
           self.__image.pixel(i, j) != oldColor.rgba() or \
           self.__image.pixel(i, j) == self.penColor().rgba():
            return
        
        self.__image.setPixel(i, j, self.penColor().rgba())
        
        self.__drawFlood(i, j - 1, oldColor, False)
        self.__drawFlood(i, j + 1, oldColor, False)
        self.__drawFlood(i - 1, j, oldColor, False)
        self.__drawFlood(i + 1, j, oldColor, False)
        
        if doUpdate:
            self.update()
    
    def __updateRect(self, pos1, pos2):
        """
        Private slot to update parts of the widget.
        
        @param pos1 top, left position for the update in widget coordinates
            (QPoint)
        @param pos2 bottom, right position for the update in widget
            coordinates (QPoint)
        """
        self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)),
                               QPoint(*self.__imageCoordinates(pos2)))
    
    def __updateImageRect(self, ipos1, ipos2):
        """
        Private slot to update parts of the widget.
        
        @param ipos1 top, left position for the update in image coordinates
            (QPoint)
        @param ipos2 bottom, right position for the update in image
            coordinates (QPoint)
        """
        r1 = self.__pixelRect(ipos1.x(), ipos1.y())
        r2 = self.__pixelRect(ipos2.x(), ipos2.y())
        
        left = min(r1.x(), r2.x())
        top = min(r1.y(), r2.y())
        right = max(r1.x() + r1.width(), r2.x() + r2.width())
        bottom = max(r1.y() + r1.height(), r2.y() + r2.height())
        self.update(left, top, right - left + 1, bottom - top + 1)
    
    def __unMark(self):
        """
        Private slot to remove the mark indicator.
        """
        self.__markImage.fill(self.NoMarkColor.rgba())
        if self.__curTool in [self.Circle, self.FilledCircle,
                              self.Ellipse, self.FilledEllipse,
                              self.CircleSelection]:
            self.update()
        else:
            self.__updateRect(self.__startPos, self.__endPos)
        
        if self.__selecting:
            self.__selRect = QRect()
            self.__selectionAvailable = False
            self.selectionAvailable.emit(False)
    
    def __isMarked(self, i, j):
        """
        Private method to check, if a pixel is marked.
        
        @param i x-value in image coordinates (integer)
        @param j y-value in image coordinates (integer)
        @return flag indicating a marked pixel (boolean)
        """
        return self.__markImage.pixel(i, j) == self.MarkColor.rgba()
    
    def __updatePreviewPixmap(self):
        """
        Private slot to generate and signal an updated preview pixmap.
        """
        p = QPixmap.fromImage(self.__image)
        self.previewChanged.emit(p)
    
    def previewPixmap(self):
        """
        Public method to generate a preview pixmap.
        
        @return preview pixmap (QPixmap)
        """
        p = QPixmap.fromImage(self.__image)
        return p
    
    def __checkClipboard(self):
        """
        Private slot to check, if the clipboard contains a valid image, and
        signal the result.
        """
        ok = self.__clipboardImage()[1]
        self.__clipboardImageAvailable = ok
        self.clipboardImageAvailable.emit(ok)
    
    def canPaste(self):
        """
        Public slot to check the availability of the paste operation.
        
        @return flag indicating availability of paste (boolean)
        """
        return self.__clipboardImageAvailable
    
    def __clipboardImage(self):
        """
        Private method to get an image from the clipboard.
        
        @return tuple with the image (QImage) and a flag indicating a
            valid image (boolean)
        """
        img = QApplication.clipboard().image()
        ok = not img.isNull()
        if ok:
            img = img.convertToFormat(QImage.Format_ARGB32)
        
        return img, ok
    
    def __getSelectionImage(self, cut):
        """
        Private method to get an image from the selection.
        
        @param cut flag indicating to cut the selection (boolean)
        @return image of the selection (QImage)
        """
        if cut:
            cmd = IconEditCommand(self, self.tr("Cut Selection"),
                                  self.__image)
        
        img = QImage(self.__selRect.size(), QImage.Format_ARGB32)
        img.fill(qRgba(0, 0, 0, 0))
        for i in range(0, self.__selRect.width()):
            for j in range(0, self.__selRect.height()):
                if self.__image.rect().contains(self.__selRect.x() + i,
                                                self.__selRect.y() + j):
                    if self.__isMarked(
                            self.__selRect.x() + i, self.__selRect.y() + j):
                        img.setPixel(i, j, self.__image.pixel(
                            self.__selRect.x() + i, self.__selRect.y() + j))
                        if cut:
                            self.__image.setPixel(self.__selRect.x() + i,
                                                  self.__selRect.y() + j,
                                                  qRgba(0, 0, 0, 0))
        
        if cut:
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)
        
        self.__unMark()
        
        if cut:
            self.update(self.__selRect)
        
        return img
    
    def editCopy(self):
        """
        Public slot to copy the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(False)
            QApplication.clipboard().setImage(img)
    
    def editCut(self):
        """
        Public slot to cut the selection.
        """
        if self.__selRect.isValid():
            img = self.__getSelectionImage(True)
            QApplication.clipboard().setImage(img)
    
    @pyqtSlot()
    def editPaste(self, pasting=False):
        """
        Public slot to paste an image from the clipboard.
        
        @param pasting flag indicating part two of the paste operation
            (boolean)
        """
        img, ok = self.__clipboardImage()
        if ok:
            if img.width() > self.__image.width() or \
                    img.height() > self.__image.height():
                res = E5MessageBox.yesNo(
                    self,
                    self.tr("Paste"),
                    self.tr(
                        """<p>The clipboard image is larger than the"""
                        """ current image.<br/>Paste as new image?</p>"""))
                if res:
                    self.editPasteAsNew()
                return
            elif not pasting:
                self.__isPasting = True
                self.__clipboardSize = img.size()
            else:
                cmd = IconEditCommand(self, self.tr("Paste Clipboard"),
                                      self.__image)
                self.__markImage.fill(self.NoMarkColor.rgba())
                painter = QPainter(self.__image)
                painter.setPen(self.penColor())
                painter.setCompositionMode(self.__compositingMode)
                painter.drawImage(
                    self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0,
                    self.__pasteRect.width() + 1,
                    self.__pasteRect.height() + 1)
                
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
                
                self.__updateImageRect(
                    self.__pasteRect.topLeft(),
                    self.__pasteRect.bottomRight() + QPoint(1, 1))
        else:
            E5MessageBox.warning(
                self,
                self.tr("Pasting Image"),
                self.tr("""Invalid image data in clipboard."""))
    
    def editPasteAsNew(self):
        """
        Public slot to paste the clipboard as a new image.
        """
        img, ok = self.__clipboardImage()
        if ok:
            cmd = IconEditCommand(
                self, self.tr("Paste Clipboard as New Image"),
                self.__image)
            self.setIconImage(img)
            self.setDirty(True)
            self.__undoStack.push(cmd)
            cmd.setAfterImage(self.__image)
    
    def editSelectAll(self):
        """
        Public slot to select the complete image.
        """
        self.__unMark()
        
        self.__startPos = QPoint(0, 0)
        self.__endPos = QPoint(self.rect().bottomRight())
        self.__markImage.fill(self.MarkColor.rgba())
        self.__selRect = self.__image.rect()
        self.__selectionAvailable = True
        self.selectionAvailable.emit(True)
        
        self.update()
    
    def editClear(self):
        """
        Public slot to clear the image.
        """
        self.__unMark()
        
        cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image)
        self.__image.fill(qRgba(0, 0, 0, 0))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)
    
    def editResize(self):
        """
        Public slot to resize the image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            newWidth, newHeight = dlg.getData()
            if newWidth != self.__image.width() or \
                    newHeight != self.__image.height():
                cmd = IconEditCommand(self, self.tr("Resize Image"),
                                      self.__image)
                img = self.__image.scaled(
                    newWidth, newHeight, Qt.IgnoreAspectRatio,
                    Qt.SmoothTransformation)
                self.setIconImage(img)
                self.setDirty(True)
                self.__undoStack.push(cmd)
                cmd.setAfterImage(self.__image)
    
    def editNew(self):
        """
        Public slot to generate a new, empty image.
        """
        from .IconSizeDialog import IconSizeDialog
        dlg = IconSizeDialog(self.__image.width(), self.__image.height())
        res = dlg.exec_()
        if res == QDialog.Accepted:
            width, height = dlg.getData()
            img = QImage(width, height, QImage.Format_ARGB32)
            img.fill(qRgba(0, 0, 0, 0))
            self.setIconImage(img)
    
    def grayScale(self):
        """
        Public slot to convert the image to gray preserving transparency.
        """
        cmd = IconEditCommand(self, self.tr("Convert to Grayscale"),
                              self.__image)
        for x in range(self.__image.width()):
            for y in range(self.__image.height()):
                col = self.__image.pixel(x, y)
                if col != qRgba(0, 0, 0, 0):
                    gray = qGray(col)
                    self.__image.setPixel(
                        x, y, qRgba(gray, gray, gray, qAlpha(col)))
        self.update()
        self.setDirty(True)
        self.__undoStack.push(cmd)
        cmd.setAfterImage(self.__image)

    def editUndo(self):
        """
        Public slot to perform an undo operation.
        """
        if self.__undoStack.canUndo():
            self.__undoStack.undo()
    
    def editRedo(self):
        """
        Public slot to perform a redo operation.
        """
        if self.__undoStack.canRedo():
            self.__undoStack.redo()
    
    def canUndo(self):
        """
        Public method to return the undo status.
        
        @return flag indicating the availability of undo (boolean)
        """
        return self.__undoStack.canUndo()
    
    def canRedo(self):
        """
        Public method to return the redo status.
        
        @return flag indicating the availability of redo (boolean)
        """
        return self.__undoStack.canRedo()
    
    def __cleanChanged(self, clean):
        """
        Private slot to handle the undo stack clean state change.
        
        @param clean flag indicating the clean state (boolean)
        """
        self.setDirty(not clean)
    
    def shutdown(self):
        """
        Public slot to perform some shutdown actions.
        """
        self.__undoStack.canRedoChanged.disconnect(self.canRedoChanged)
        self.__undoStack.canUndoChanged.disconnect(self.canUndoChanged)
        self.__undoStack.cleanChanged.disconnect(self.__cleanChanged)
    
    def isSelectionAvailable(self):
        """
        Public method to check the availability of a selection.
        
        @return flag indicating the availability of a selection (boolean)
        """
        return self.__selectionAvailable
Esempio n. 47
0
class BookmarksManager(QObject):
    """
    Class implementing the bookmarks manager.
    
    @signal entryAdded(BookmarkNode) emitted after a bookmark node has been
        added
    @signal entryRemoved(BookmarkNode, int, BookmarkNode) emitted after a
        bookmark node has been removed
    @signal entryChanged(BookmarkNode) emitted after a bookmark node has been
        changed
    @signal bookmarksSaved() emitted after the bookmarks were saved
    @signal bookmarksReloaded() emitted after the bookmarks were reloaded
    """
    entryAdded = pyqtSignal(BookmarkNode)
    entryRemoved = pyqtSignal(BookmarkNode, int, BookmarkNode)
    entryChanged = pyqtSignal(BookmarkNode)
    bookmarksSaved = pyqtSignal()
    bookmarksReloaded = pyqtSignal()
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        super(BookmarksManager, self).__init__(parent)
        
        self.__saveTimer = AutoSaver(self, self.save)
        self.entryAdded.connect(self.__saveTimer.changeOccurred)
        self.entryRemoved.connect(self.__saveTimer.changeOccurred)
        self.entryChanged.connect(self.__saveTimer.changeOccurred)
        
        self.__initialize()
    
    def __initialize(self):
        """
        Private method to initialize some data.
        """
        self.__loaded = False
        self.__bookmarkRootNode = None
        self.__toolbar = None
        self.__menu = None
        self.__bookmarksModel = None
        self.__commands = QUndoStack()
    
    @classmethod
    def getFileName(cls):
        """
        Class method to get the file name of the bookmark file.
        
        @return name of the bookmark file (string)
        """
        return os.path.join(Utilities.getConfigDir(), "browser",
                            "bookmarks.xbel")
    
    def close(self):
        """
        Public method to close the bookmark manager.
        """
        self.__saveTimer.saveIfNeccessary()
    
    def undoRedoStack(self):
        """
        Public method to get a reference to the undo stack.
        
        @return reference to the undo stack (QUndoStack)
        """
        return self.__commands
    
    def changeExpanded(self):
        """
        Public method to handle a change of the expanded state.
        """
        self.__saveTimer.changeOccurred()
    
    def reload(self):
        """
        Public method used to initiate a reloading of the bookmarks.
        """
        self.__initialize()
        self.load()
        self.bookmarksReloaded.emit()
    
    def load(self):
        """
        Public method to load the bookmarks.
        
        @exception RuntimeError raised to indicate an error loading the
            bookmarks
        """
        if self.__loaded:
            return
        
        self.__loaded = True
        
        bookmarkFile = self.getFileName()
        if not QFile.exists(bookmarkFile):
            from . import DefaultBookmarks_rc       # __IGNORE_WARNING__
            bookmarkFile = QFile(":/DefaultBookmarks.xbel")
            bookmarkFile.open(QIODevice.ReadOnly)
        
        from .XbelReader import XbelReader
        reader = XbelReader()
        self.__bookmarkRootNode = reader.read(bookmarkFile)
        if reader.error() != QXmlStreamReader.NoError:
            E5MessageBox.warning(
                None,
                self.tr("Loading Bookmarks"),
                self.tr(
                    """Error when loading bookmarks on line {0},"""
                    """ column {1}:\n {2}""")
                .format(reader.lineNumber(),
                        reader.columnNumber(),
                        reader.errorString()))
        
        others = []
        for index in range(
                len(self.__bookmarkRootNode.children()) - 1, -1, -1):
            node = self.__bookmarkRootNode.children()[index]
            if node.type() == BookmarkNode.Folder:
                if (node.title == self.tr("Toolbar Bookmarks") or
                    node.title == BOOKMARKBAR) and \
                   self.__toolbar is None:
                    node.title = self.tr(BOOKMARKBAR)
                    self.__toolbar = node
                
                if (node.title == self.tr("Menu") or
                    node.title == BOOKMARKMENU) and \
                   self.__menu is None:
                    node.title = self.tr(BOOKMARKMENU)
                    self.__menu = node
            else:
                others.append(node)
            self.__bookmarkRootNode.remove(node)
        
        if len(self.__bookmarkRootNode.children()) > 0:
            raise RuntimeError("Error loading bookmarks.")
        
        if self.__toolbar is None:
            self.__toolbar = BookmarkNode(BookmarkNode.Folder,
                                          self.__bookmarkRootNode)
            self.__toolbar.title = self.tr(BOOKMARKBAR)
        else:
            self.__bookmarkRootNode.add(self.__toolbar)
        
        if self.__menu is None:
            self.__menu = BookmarkNode(BookmarkNode.Folder,
                                       self.__bookmarkRootNode)
            self.__menu.title = self.tr(BOOKMARKMENU)
        else:
            self.__bookmarkRootNode.add(self.__menu)
        
        for node in others:
            self.__menu.add(node)
        
        self.__convertFromOldBookmarks()
    
    def save(self):
        """
        Public method to save the bookmarks.
        """
        if not self.__loaded:
            return
        
        from .XbelWriter import XbelWriter
        writer = XbelWriter()
        bookmarkFile = self.getFileName()
        
        # save root folder titles in English (i.e. not localized)
        self.__menu.title = BOOKMARKMENU
        self.__toolbar.title = BOOKMARKBAR
        if not writer.write(bookmarkFile, self.__bookmarkRootNode):
            E5MessageBox.warning(
                None,
                self.tr("Saving Bookmarks"),
                self.tr("""Error saving bookmarks to <b>{0}</b>.""")
                .format(bookmarkFile))
        
        # restore localized titles
        self.__menu.title = self.tr(BOOKMARKMENU)
        self.__toolbar.title = self.tr(BOOKMARKBAR)
        
        self.bookmarksSaved.emit()
    
    def addBookmark(self, parent, node, row=-1):
        """
        Public method to add a bookmark.
        
        @param parent reference to the node to add to (BookmarkNode)
        @param node reference to the node to add (BookmarkNode)
        @param row row number (integer)
        """
        if not self.__loaded:
            return
        
        self.setTimestamp(node, BookmarkNode.TsAdded,
                          QDateTime.currentDateTime())
        
        command = InsertBookmarksCommand(self, parent, node, row)
        self.__commands.push(command)
    
    def removeBookmark(self, node):
        """
        Public method to remove a bookmark.
        
        @param node reference to the node to be removed (BookmarkNode)
        """
        if not self.__loaded:
            return
        
        parent = node.parent()
        row = parent.children().index(node)
        command = RemoveBookmarksCommand(self, parent, row)
        self.__commands.push(command)
    
    def setTitle(self, node, newTitle):
        """
        Public method to set the title of a bookmark.
        
        @param node reference to the node to be changed (BookmarkNode)
        @param newTitle title to be set (string)
        """
        if not self.__loaded:
            return
        
        command = ChangeBookmarkCommand(self, node, newTitle, True)
        self.__commands.push(command)
    
    def setUrl(self, node, newUrl):
        """
        Public method to set the URL of a bookmark.
        
        @param node reference to the node to be changed (BookmarkNode)
        @param newUrl URL to be set (string)
        """
        if not self.__loaded:
            return
        
        command = ChangeBookmarkCommand(self, node, newUrl, False)
        self.__commands.push(command)
    
    def setNodeChanged(self, node):
        """
        Public method to signal changes of bookmarks other than title, URL
        or timestamp.
        
        @param node reference to the bookmark (BookmarkNode)
        """
        self.__saveTimer.changeOccurred()
    
    def setTimestamp(self, node, timestampType, timestamp):
        """
        Public method to set the URL of a bookmark.
        
        @param node reference to the node to be changed (BookmarkNode)
        @param timestampType type of the timestamp to set
            (BookmarkNode.TsAdded, BookmarkNode.TsModified,
            BookmarkNode.TsVisited)
        @param timestamp timestamp to set (QDateTime)
        """
        if not self.__loaded:
            return
        
        assert timestampType in [BookmarkNode.TsAdded,
                                 BookmarkNode.TsModified,
                                 BookmarkNode.TsVisited]
        
        if timestampType == BookmarkNode.TsAdded:
            node.added = timestamp
        elif timestampType == BookmarkNode.TsModified:
            node.modified = timestamp
        elif timestampType == BookmarkNode.TsVisited:
            node.visited = timestamp
        self.__saveTimer.changeOccurred()
    
    def bookmarks(self):
        """
        Public method to get a reference to the root bookmark node.
        
        @return reference to the root bookmark node (BookmarkNode)
        """
        if not self.__loaded:
            self.load()
        
        return self.__bookmarkRootNode
    
    def menu(self):
        """
        Public method to get a reference to the bookmarks menu node.
        
        @return reference to the bookmarks menu node (BookmarkNode)
        """
        if not self.__loaded:
            self.load()
        
        return self.__menu
    
    def toolbar(self):
        """
        Public method to get a reference to the bookmarks toolbar node.
        
        @return reference to the bookmarks toolbar node (BookmarkNode)
        """
        if not self.__loaded:
            self.load()
        
        return self.__toolbar
    
    def bookmarksModel(self):
        """
        Public method to get a reference to the bookmarks model.
        
        @return reference to the bookmarks model (BookmarksModel)
        """
        if self.__bookmarksModel is None:
            from .BookmarksModel import BookmarksModel
            self.__bookmarksModel = BookmarksModel(self, self)
        return self.__bookmarksModel
    
    def importBookmarks(self):
        """
        Public method to import bookmarks.
        """
        from .BookmarksImportDialog import BookmarksImportDialog
        dlg = BookmarksImportDialog()
        if dlg.exec_() == QDialog.Accepted:
            importRootNode = dlg.getImportedBookmarks()
            if importRootNode is not None:
                self.addBookmark(self.menu(), importRootNode)
    
    def exportBookmarks(self):
        """
        Public method to export the bookmarks.
        """
        fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            None,
            self.tr("Export Bookmarks"),
            "eric6_bookmarks.xbel",
            self.tr("XBEL bookmarks (*.xbel);;"
                    "XBEL bookmarks (*.xml);;"
                    "HTML Bookmarks (*.html)"))
        if not fileName:
            return
        
        ext = QFileInfo(fileName).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fileName += ex
        
        ext = QFileInfo(fileName).suffix()
        if ext == "html":
            from .NsHtmlWriter import NsHtmlWriter
            writer = NsHtmlWriter()
        else:
            from .XbelWriter import XbelWriter
            writer = XbelWriter()
        if not writer.write(fileName, self.__bookmarkRootNode):
            E5MessageBox.critical(
                None,
                self.tr("Exporting Bookmarks"),
                self.tr("""Error exporting bookmarks to <b>{0}</b>.""")
                .format(fileName))
    
    def __convertFromOldBookmarks(self):
        """
        Private method to convert the old bookmarks into the new ones.
        """
        bmNames = Preferences.Prefs.settings.value('Bookmarks/Names')
        bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files')
        
        if bmNames is not None and bmFiles is not None:
            if len(bmNames) == len(bmFiles):
                convertedRootNode = BookmarkNode(BookmarkNode.Folder)
                convertedRootNode.title = self.tr("Converted {0}")\
                    .format(QDate.currentDate().toString(
                        Qt.SystemLocaleShortDate))
                for i in range(len(bmNames)):
                    node = BookmarkNode(BookmarkNode.Bookmark,
                                        convertedRootNode)
                    node.title = bmNames[i]
                    url = QUrl(bmFiles[i])
                    if not url.scheme():
                        url.setScheme("file")
                    node.url = url.toString()
                self.addBookmark(self.menu(), convertedRootNode)
                
                Preferences.Prefs.settings.remove('Bookmarks')
    
    def iconChanged(self, url):
        """
        Public slot to update the icon image for an URL.
        
        @param url URL of the icon to update (QUrl or string)
        """
        if isinstance(url, QUrl):
            url = url.toString()
        nodes = self.bookmarksForUrl(url)
        for node in nodes:
            self.bookmarksModel().entryChanged(node)
    
    def bookmarkForUrl(self, url, start=StartRoot):
        """
        Public method to get a bookmark node for a given URL.
        
        @param url URL of the bookmark to search for (QUrl or string)
        @keyparam start indicator for the start of the search
            (StartRoot, StartMenu, StartToolBar)
        @return bookmark node for the given url (BookmarkNode)
        """
        if start == StartMenu:
            startNode = self.__menu
        elif start == StartToolBar:
            startNode = self.__toolbar
        else:
            startNode = self.__bookmarkRootNode
        if startNode is None:
            return None
        
        if isinstance(url, QUrl):
            url = url.toString()
        
        return self.__searchBookmark(url, startNode)
    
    def __searchBookmark(self, url, startNode):
        """
        Private method get a bookmark node for a given URL.
        
        @param url URL of the bookmark to search for (string)
        @param startNode reference to the node to start searching
            (BookmarkNode)
        @return bookmark node for the given url (BookmarkNode)
        """
        bm = None
        for node in startNode.children():
            if node.type() == BookmarkNode.Folder:
                bm = self.__searchBookmark(url, node)
            elif node.type() == BookmarkNode.Bookmark:
                if node.url == url:
                    bm = node
            if bm is not None:
                return bm
        return None
    
    def bookmarksForUrl(self, url, start=StartRoot):
        """
        Public method to get a list of bookmark nodes for a given URL.
        
        @param url URL of the bookmarks to search for (QUrl or string)
        @keyparam start indicator for the start of the search
            (StartRoot, StartMenu, StartToolBar)
        @return list of bookmark nodes for the given url (list of BookmarkNode)
        """
        if start == StartMenu:
            startNode = self.__menu
        elif start == StartToolBar:
            startNode = self.__toolbar
        else:
            startNode = self.__bookmarkRootNode
        if startNode is None:
            return None
        
        if isinstance(url, QUrl):
            url = url.toString()
        
        return self.__searchBookmarks(url, startNode)
    
    def __searchBookmarks(self, url, startNode):
        """
        Private method get a list of bookmark nodes for a given URL.
        
        @param url URL of the bookmarks to search for (string)
        @param startNode reference to the node to start searching
            (BookmarkNode)
        @return list of bookmark nodes for the given url (list of BookmarkNode)
        """
        bm = []
        for node in startNode.children():
            if node.type() == BookmarkNode.Folder:
                bm.extend(self.__searchBookmarks(url, node))
            elif node.type() == BookmarkNode.Bookmark:
                if node.url == url:
                    bm.append(node)
        return bm