class PaletteView(QObject): """A toolvide to display palette of the current document""" colors = [] toolView = None colorCellsWidget = None def __init__(self, parent): super(PaletteView, self).__init__(parent) self.toolView = kate.mainInterfaceWindow().createToolView( "color_tools_pate_plugin", kate.Kate.MainWindow.Bottom, kate.gui.loadIcon('color'), i18n("Palette")) self.toolView.installEventFilter(self) # By default, the toolview has box layout, which is not easy to delete. # For now, just add an extra widget. top = QWidget(self.toolView) # Set up the user interface from Designer. interior = uic.loadUi( os.path.join(os.path.dirname(__file__), 'color_tools_toolview.ui'), top) interior.update.clicked.connect(self.update) self.colorCellsWidget = KColorCells(interior, 1, 1) # TODO Don't know how to deal w/ drag-n-drops :( # It seems there is no signal to realize that some item has changed :( self.colorCellsWidget.setAcceptDrags(False) interior.verticalLayout.addWidget(self.colorCellsWidget) self.colorCellsWidget.colorSelected.connect(self.colorSelected) self.colorCellsWidget.colorDoubleClicked.connect( self.colorDoubleClicked) def __del__(self): """Plugins that use a toolview need to delete it for reloading to work.""" if self.toolView: self.toolView.deleteLater() self.toolView = None def eventFilter(self, obj, event): """Hide the Palette tool view on ESCAPE key""" if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape: kate.mainInterfaceWindow().hideToolView(self.toolView) return True return self.toolView.eventFilter(obj, event) def updateColors(self, view=None): """Scan a document for #colors""" self.colors = list() # Clear previous colors if view: document = view.document() else: try: document = kate.activeDocument() except kate.NoActiveView: return # Do nothing if we can't get a current document # Iterate over document's lines trying to find #colors for l in range(0, document.lines()): line = document.line(l) # Get the current line start = 0 # Set initial position to 0 (line start) while start < len(line): # Repeat 'till the line end start = line.find( '#', start) # Try to find a '#' character (start of #color) if start == -1: # Did we found smth? break # No! Nothing to do... # Try to get a word right after the '#' char end = start + 1 for c in line[end:]: if not (c in string.hexdigits or c in string.ascii_letters): break end += 1 color_range = KTextEditor.Range(l, start, l, end) color_str = document.text(color_range) color = QColor(color_str) if color.isValid(): self.colors.append(ColorRangePair(color, color_range)) print('PALETTE VIEW: Found %s' % color_str) start = end def updateColorCells(self): """Calculate rows*columns and fill the cells w/ #colors""" if len(self.colors): # Recalculate rows/columns columns = int(math.sqrt(len(self.colors))) rows = int(len(self.colors) / columns) + int( bool(len(self.colors) % columns)) else: columns = 1 rows = 1 self.colors.append(ColorRangePair(QColor(), KTextEditor.Range())) self.colorCellsWidget.setColumnCount(columns) self.colorCellsWidget.setRowCount(rows) self.colorCellsWidget.resizeColumnsToContents() self.colorCellsWidget.resizeRowsToContents() # Fill color cells for i, crp in enumerate(self.colors): self.colorCellsWidget.setColor(i, crp.color) @pyqtSlot() def update(self, view=None): self.updateColors(view) self.updateColorCells() @pyqtSlot(int, QColor) def colorSelected(self, idx, color): """Move cursor to the position of the selected #color and select the range""" view = kate.activeView() view.setCursorPosition(self.colors[idx].color_range.start()) view.setSelection(self.colors[idx].color_range) @pyqtSlot(int, QColor) def colorDoubleClicked(self, idx, color): """Edit selected color on double click""" insertColor() self.update()
class PaletteView(QObject): '''A toolview to display palette of the current document''' colors = [] toolView = None colorCellsWidget = None def __init__(self, parent): super(PaletteView, self).__init__(parent) self.toolView = kate.mainInterfaceWindow().createToolView( 'color_tools_pate_plugin' , kate.Kate.MainWindow.Bottom , KIcon('color').pixmap(32, 32) , i18nc('@title:tab', 'Palette') ) self.toolView.installEventFilter(self) # By default, the toolview has box layout, which is not easy to delete. # For now, just add an extra widget. top = QWidget(self.toolView) # Set up the user interface from Designer. interior = uic.loadUi(os.path.join(os.path.dirname(__file__), 'color_tools_toolview.ui'), top) interior.update.clicked.connect(self.update) self.colorCellsWidget = KColorCells(interior, 1, 1) # TODO Don't know how to deal w/ drag-n-drops :( # It seems there is no signal to realize that some item has changed :( # (at lieast I didn't find it) self.colorCellsWidget.setAcceptDrags(False) self.colorCellsWidget.setEditTriggers(self.colorCellsWidget.NoEditTriggers) interior.verticalLayout.addWidget(self.colorCellsWidget) self.colorCellsWidget.colorSelected.connect(self.colorSelected) self.colorCellsWidget.colorDoubleClicked.connect(self.colorDoubleClicked) #def __del__(self): #'''Plugins that use a toolview need to delete it for reloading to work.''' #if self.toolView: #self.toolView.deleteLater() #self.toolView = None def eventFilter(self, obj, event): '''Hide the Palette tool view on ESCAPE key''' if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape: kate.mainInterfaceWindow().hideToolView(self.toolView) return True return self.toolView.eventFilter(obj, event) def updateColors(self, view=None): '''Scan a document for #colors Returns a list of tuples: QColor and range in a document TODO Some refactoring needed to reduce code duplication (@sa _get_color_range_under_cursor()) ''' self.colors = [] # Clear previous colors if view: document = view.document() else: try: document = kate.activeDocument() except kate.NoActiveView: return # Do nothing if we can't get a current document # Iterate over document's lines trying to find #colors for l in range(0, document.lines()): line = document.line(l) # Get the current line start = 0 # Set initial position to 0 (line start) while start < len(line): # Repeat 'till the line end start = line.find('#', start) # Try to find a '#' character (start of #color) if start == -1: # Did we found smth? break # No! Nothing to do... # Try to get a word right after the '#' char end = start + 1 for c in line[end:]: if not (c in string.hexdigits): break end += 1 color_range = KTextEditor.Range(l, start, l, end) color_str = document.text(color_range) color = QColor(color_str) if color.isValid(): self.colors.append(ColorRangePair(color, color_range)) kate.kDebug('ColorUtilsToolView: scan for #colors found {}'.format(color_str)) start = end def updateColorCells(self): '''Calculate rows*columns and fill the cells w/ #colors''' if len(self.colors): # Recalculate rows/columns columns = int(self.colorCellsWidget.width() / 30) visible_rows = int(self.colorCellsWidget.height() / 25) if len(self.colors) < (columns * visible_rows): rows = visible_rows else: visible_cells = columns * visible_rows rest = len(self.colors) - visible_cells rows = visible_rows + int(rest / columns) + int(bool(rest % columns)) else: columns = 1 rows = 1 self.colors.append(ColorRangePair(QColor(), KTextEditor.Range())) self.colorCellsWidget.setColumnCount(columns) self.colorCellsWidget.setRowCount(rows) self.colorCellsWidget.resizeColumnsToContents() self.colorCellsWidget.resizeRowsToContents() # Fill color cells for i, crp in enumerate(self.colors): self.colorCellsWidget.setColor(i, crp.color) for i in range(len(self.colors), columns * rows): self.colorCellsWidget.setColor(i, QColor()) _set_tooltips(rows, columns, self.colorCellsWidget) @pyqtSlot() def update(self, view=None): self.updateColors(view) self.updateColorCells() @pyqtSlot(int, QColor) def colorSelected(self, idx, color): '''Move cursor to the position of the selected #color and select the range''' view = kate.activeView() view.setCursorPosition(self.colors[idx].color_range.start()) view.setSelection(self.colors[idx].color_range) @pyqtSlot(int, QColor) def colorDoubleClicked(self, idx, color): '''Edit selected color on double click''' insertColor() self.update()
class ColorChooser(QFrame): ''' Completion-like widget to quick select hexadecimal colors used in a document TODO Make cell/widget size configurable? ''' colorSelected = pyqtSignal(QColor) def __init__(self, parent): super(ColorChooser, self).__init__(parent) self.colors = KColorCells(self, 1, 1) self.colors.setAcceptDrags(False) self.colors.setEditTriggers(self.colors.NoEditTriggers) self.otherBtn = KPushButton(self) self.otherBtn.setText(i18nc('@action:button', '&Other...')) layout = QVBoxLayout(self) layout.addWidget(self.colors) layout.addWidget(self.otherBtn) self.setFocusPolicy(Qt.StrongFocus) self.setFrameShape(QFrame.Panel) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup); # Subscribe to observe widget events # - open KColorDialog on 'Other...' button click self.otherBtn.clicked.connect(self.show_color_dialog) # - select color by RMB click or ENTER on keyboard self.colors.cellActivated.connect(self.color_selected) self.installEventFilter(self) def setColors(self, colors): if len(colors): rows, columns = _calc_dimensions_for_items_count(len(colors)) else: self.hide() self.show_color_dialog(True) return self.colors.setColumnCount(columns) self.colors.setRowCount(rows) self.colors.setMinimumSize(20 * columns, 25 * rows) self.updateGeometry() self.adjustSize() self.colors.resizeColumnsToContents() self.colors.resizeRowsToContents() # Fill color cells for i, color in enumerate(colors): self.colors.setColor(i, color) _set_tooltips(rows, columns, self.colors) # Set tooltips for all valid color cells self.colors.setFocus() # Give a focus to widget self.show() # Show it! @pyqtSlot(bool) def show_color_dialog(self, f): '''Get color using KColorDialog''' # Preselect last used color color = QColor(kate.sessionConfiguration[_INSERT_COLOR_LCC]) result = KColorDialog.getColor(color) if result == KColorDialog.Accepted: # Did user press OK? self.emitSelectedColorHideSelf(color) @pyqtSlot(int, int) def color_selected(self, row, column): '''Smth has selected in KColorCells''' color = self.colors.item(row, column).data(Qt.BackgroundRole) #kate.kDebug('ColorUtils: activated row={}, column={}, color={}'.format(row, column, color)) self.emitSelectedColorHideSelf(color) def emitSelectedColorHideSelf(self, color): # Remember last selected color for future preselect kate.sessionConfiguration[_INSERT_COLOR_LCC] = color.name() self.colorSelected.emit(color) self.hide() def eventFilter(self, obj, event): '''Hide self on Esc key''' if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape: self.hide() return True return super(ColorChooser, self).eventFilter(obj, event) def moveAround(self, position): '''Smart positioning self around cursor''' self.colors.resizeColumnsToContents() self.colors.resizeRowsToContents() mwg = kate.mainWindow().geometry() wc = mwg.center() pos = mwg.topLeft() + position pos.setY(pos.y() + 40) if wc.y() < pos.y(): pos.setY(pos.y() - self.height()) if wc.x() < pos.x(): pos.setX(pos.x() - self.width()) self.move(pos)
class ColorChooser(QFrame): ''' Completion-like widget to quick select hexadecimal colors used in a document TODO Make cell/widget size configurable? ''' _INSERT_COLOR_LCC = 'insertColor:lastUsedColor' colorSelected = pyqtSignal(QColor) def __init__(self): super(ColorChooser, self).__init__(None) self.colors = KColorCells(self, 1, 1) self.colors.setAcceptDrags(False) self.colors.setEditTriggers(self.colors.NoEditTriggers) self.otherBtn = KPushButton(self) self.otherBtn.setText(i18nc('@action:button', '&Other...')) layout = QVBoxLayout(self) layout.addWidget(self.colors) layout.addWidget(self.otherBtn) self.setFocusPolicy(Qt.StrongFocus) self.setFrameShape(QFrame.Panel) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) # Set default value for last used #color if not configured yet if ColorChooser._INSERT_COLOR_LCC not in kate.sessionConfiguration: kate.sessionConfiguration[ColorChooser._INSERT_COLOR_LCC] = '#ffffff' # Subscribe to observe widget events # - open KColorDialog on 'Other...' button click self.otherBtn.clicked.connect(self._show_color_dialog) # - select color by RMB click or ENTER on keyboard self.colors.cellActivated.connect(self._color_selected) # - self.colorSelected.connect(self._insert_color_into_active_document) self.installEventFilter(self) def __del__(self): mw = kate.mainInterfaceWindow() if mw: self.blockSignals(True) self.removeEventFilter(self) def updateColors(self, document): '''Scan a given document and collect unique colors Returns a list of QColor objects. ''' result = [] # Iterate over document's lines trying to find #colors for l in range(0, document.lines()): line = document.line(l) # Get the current line start = 0 # Set initial position to 0 (line start) while start < len(line): # Repeat 'till the line end start = line.find('#', start) # Try to find a '#' character (start of #color) if start == -1: # Did we found smth? break # No! Nothing to do... # Try to get a word right after the '#' char end = start + 1 for c in line[end:]: if not (c in string.hexdigits or c in string.ascii_letters): break end += 1 color_range = KTextEditor.Range(l, start, l, end) color_str = document.text(color_range) color = QColor(color_str) if color.isValid() and color not in result: result.append(color) kate.kDebug('ColorUtils: scan for #colors found {}'.format(color_str)) start = end self._set_colors(result) def moveAround(self, position): '''Smart positioning self around cursor''' self.colors.resizeColumnsToContents() self.colors.resizeRowsToContents() mwg = kate.mainWindow().geometry() wc = mwg.center() pos = mwg.topLeft() + position pos.setY(pos.y() + 40) if wc.y() < pos.y(): pos.setY(pos.y() - self.height()) if wc.x() < pos.x(): pos.setX(pos.x() - self.width()) self.move(pos) def emitSelectedColorHideSelf(self, color): # Remember last selected color for future preselect kate.sessionConfiguration[ColorChooser._INSERT_COLOR_LCC] = color.name() self.colorSelected.emit(color) self.hide() def eventFilter(self, obj, event): '''Hide self on Esc key''' if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape: self.hide() return True return super(ColorChooser, self).eventFilter(obj, event) def _set_colors(self, colors): if len(colors): rows, columns = ColorChooser._calc_dimensions_for_items_count(len(colors)) else: self.hide() self._show_color_dialog(True) return self.colors.setColumnCount(columns) self.colors.setRowCount(rows) self.colors.setMinimumSize(20 * columns, 25 * rows) self.updateGeometry() self.adjustSize() self.colors.resizeColumnsToContents() self.colors.resizeRowsToContents() # Fill color cells for i, color in enumerate(colors): self.colors.setColor(i, color) setTooltips(rows, columns, self.colors) # Set tooltips for all valid color cells self.colors.setFocus() # Give a focus to widget self.show() # Show it! @pyqtSlot(bool) def _show_color_dialog(self, f): '''Get color using KColorDialog''' # Preselect last used color color = QColor(kate.sessionConfiguration[ColorChooser._INSERT_COLOR_LCC]) result = KColorDialog.getColor(color) if result == KColorDialog.Accepted: # Did user press OK? self.emitSelectedColorHideSelf(color) @pyqtSlot(int, int) def _color_selected(self, row, column): '''Smth has selected in KColorCells''' color = self.colors.item(row, column).data(Qt.BackgroundRole) #kate.kDebug('ColorUtils: activated row={}, column={}, color={}'.format(row, column, color)) self.emitSelectedColorHideSelf(color) @pyqtSlot(QColor) def _insert_color_into_active_document(self, color): color_str = color.name() # Get it as color string #kate.kDebug('ColorUtils: selected color: {}'.format(color_str)) document = kate.activeDocument() view = kate.activeView() has_selection = view.selection() # Remember current selection state color_range = ColorChooser._get_color_range_under_cursor(view) document.startEditing() document.replaceText(color_range, color_str) # Replace selected/found range w/ a new text document.endEditing() # Select just entered #color, if something was selected before if has_selection: start_pos = color_range.start() view.setSelection(KTextEditor.Range(start_pos, len(color_str))) @staticmethod def _calc_dimensions_for_items_count(count): '''Recalculate rows/columns of table view for given items count''' rows = int(math.sqrt(count)) columns = int(count / rows) + int(bool(count % rows)) return (rows, columns) @staticmethod def _get_color_range_under_cursor(view): assert(view is not None) if view.selection(): # Some text selected, just use it as input... color_range = view.selectionRange() else: # If no selection, try to get a #color under cursor color_range = common.getBoundTextRangeSL( common.IDENTIFIER_BOUNDARIES - {'#'} , common.IDENTIFIER_BOUNDARIES , view.cursorPosition() , view.document() ) # Check if a word under cursor is a valid #color color = QColor(view.document().text(color_range)) if not color.isValid(): color_range = KTextEditor.Range(view.cursorPosition(), view.cursorPosition()) return color_range