~ubuntu-branches/ubuntu/trusty/kate/trusty

« back to all changes in this revision

Viewing changes to addons/kate/pate/src/plugins/color_tools.py

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell, Rohan Garg, Jonathan Riddell, Philip Muškovac
  • Date: 2014-03-19 10:38:16 UTC
  • mfrom: (1.1.42)
  • Revision ID: package-import@ubuntu.com-20140319103816-f5b5t0m6hnwclzcn
Tags: 4:4.12.90-0ubuntu1
[ Rohan Garg ]
* Update install files

[ Jonathan Riddell ]
* New upstream beta release

[ Philip Muškovac ]
* Override license-problem-json-evil for js_lint.py as the file only
  references the evil file

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#
3
 
# Kate/Pâté color plugins
4
 
# Copyright 2010-2013 by Alex Turbov <i.zaufi@gmail.com>
5
 
# Copyright 2013 by Phil Schaf
6
 
#
7
 
#
8
 
# This software is free software: you can redistribute it and/or modify
9
 
# it under the terms of the GNU Lesser General Public License as published by
10
 
# the Free Software Foundation, either version 3 of the License, or
11
 
# (at your option) any later version.
12
 
#
13
 
# This software is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
# GNU Lesser General Public License for more details.
17
 
#
18
 
# You should have received a copy of the GNU Lesser General Public License
19
 
# along with this software.  If not, see <http://www.gnu.org/licenses/>.
20
 
#
21
 
#
22
 
# Here is a short list of plugins in this file:
23
 
#
24
 
#   Insert Color (Meta+Shift+C)
25
 
#       open color choose dialog and insert selected color as #hex string
26
 
#
27
 
#
28
 
#   Color Swatches
29
 
#       Select a color string of any kind to display its color in a tooltip
30
 
#
31
 
 
32
 
'''Utilities to work with hexadecimal colors in documents
33
 
 
34
 
    Shows a preview of a selected hexadecimal color string (e.g. #fe57a1) in a tooltip
35
 
    and/or all parsed hexadecimal colors in a 'Palette' tool view, ability to edit/insert 
36
 
    hexadecimal color strings.
37
 
 
38
 
'''
39
 
 
40
 
import os
41
 
import math
42
 
import string
43
 
 
44
 
from PyQt4 import uic
45
 
from PyQt4.QtCore import QEvent, QObject, QTimer, Qt, pyqtSlot, pyqtSignal
46
 
from PyQt4.QtGui import QColor, QFrame, QPalette, QToolTip, QWidget, QVBoxLayout
47
 
 
48
 
from PyKDE4.kdecore import i18nc
49
 
from PyKDE4.kdeui import KColorDialog, KColorCells, KPushButton
50
 
from PyKDE4.ktexteditor import KTextEditor
51
 
 
52
 
import kate
53
 
 
54
 
from libkatepate import common
55
 
 
56
 
 
57
 
_INSERT_COLOR_LCC = 'insertColor:lastUsedColor'
58
 
_CELLS_COUNT_PER_ROW = 5
59
 
 
60
 
paletteView = None
61
 
colorChooserWidget = None
62
 
 
63
 
 
64
 
def _calc_dimensions_for_items_count(count):
65
 
    '''Recalculate rows/columns of table view for given items count'''
66
 
    rows = int(math.sqrt(count))
67
 
    columns = int(count / rows) + int(bool(count % rows))
68
 
    return (rows, columns)
69
 
 
70
 
 
71
 
def _set_tooltips(rows, columns, colors):
72
 
    '''Set tool tips for color cells in a KColorCells widget'''
73
 
    stop = False
74
 
    for r in range(0, rows):
75
 
        for c in range(0, columns):
76
 
            item = colors.item(r, c)
77
 
            if item is None:
78
 
                stop = True
79
 
                break
80
 
            color = item.data(Qt.BackgroundRole)
81
 
            if color.isValid():
82
 
                item.setToolTip(color.name())
83
 
        if stop:
84
 
            break
85
 
 
86
 
 
87
 
class ColorChooser(QFrame):
88
 
    '''Completion-like widget to quick select hexadecimal colors used in a document'''
89
 
    colorSelected = pyqtSignal(QColor)
90
 
 
91
 
    def __init__(self, parent):
92
 
        super(ColorChooser, self).__init__(parent)
93
 
        self.colors = KColorCells(self, 1, 1)
94
 
        self.colors.setAcceptDrags(False)
95
 
        self.colors.setEditTriggers(self.colors.NoEditTriggers)
96
 
        self.otherBtn = KPushButton(self)
97
 
        self.otherBtn.setText(i18nc('@action:button', '&Other...'))
98
 
        layout = QVBoxLayout(self)
99
 
        layout.addWidget(self.colors)
100
 
        layout.addWidget(self.otherBtn)
101
 
        self.setFocusPolicy(Qt.StrongFocus)
102
 
        self.setFrameShape(QFrame.Panel)
103
 
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup);
104
 
 
105
 
        # Subscribe to observe widget events
106
 
        # - open KColorDialog on 'Other...' button click
107
 
        self.otherBtn.clicked.connect(self.show_color_dialog)
108
 
        # - select color by RMB click or ENTER on keyboard
109
 
        self.colors.cellActivated.connect(self.color_selected)
110
 
 
111
 
        self.installEventFilter(self)
112
 
 
113
 
 
114
 
    def setColors(self, colors):
115
 
        if len(colors):
116
 
            rows, columns = _calc_dimensions_for_items_count(len(colors))
117
 
        else:
118
 
            self.hide()
119
 
            self.show_color_dialog(True)
120
 
            return
121
 
        print('ColorUtils: colors={}, recalc size: rows={}, columns={}'.format(len(colors), rows, columns))
122
 
        self.colors.setColumnCount(columns)
123
 
        self.colors.setRowCount(rows)
124
 
        print('ColorUtils: before: recalc size: ww={}, wh={}'.format(self.width(), self.height()))
125
 
        self.colors.setMinimumSize(20 * columns, 25 * rows)
126
 
        self.updateGeometry()
127
 
        #self.resize(20 * columns + 30, 25 * rows + 18)
128
 
        self.adjustSize()
129
 
        print('ColorUtils: after: recalc size: ww={}, wh={}'.format(self.width(), self.height()))
130
 
        self.colors.resizeColumnsToContents()
131
 
        self.colors.resizeRowsToContents()
132
 
        # Fill color cells
133
 
        for i, color in enumerate(colors):
134
 
            self.colors.setColor(i, color)
135
 
        _set_tooltips(rows, columns, self.colors)           # Set tooltips for all valid color cells
136
 
        self.colors.setFocus()                              # Give a focus to widget
137
 
        self.show()                                         # Show it!
138
 
 
139
 
 
140
 
    @pyqtSlot(bool)
141
 
    def show_color_dialog(self, f):
142
 
        '''Get color using KColorDialog'''
143
 
        print('ColorUtils: Dialog requested')
144
 
        # Preselect last used color
145
 
        color = QColor(kate.configuration[_INSERT_COLOR_LCC])
146
 
        result = KColorDialog.getColor(color)
147
 
        if result == KColorDialog.Accepted:                  # Did user press OK?
148
 
            self.emitSelectedColorHideSelf(color)
149
 
 
150
 
 
151
 
    @pyqtSlot(int, int)
152
 
    def color_selected(self, row, column):
153
 
        '''Smth has selected in KColorCells'''
154
 
        color = self.colors.item(row, column).data(Qt.BackgroundRole)
155
 
        print('ColorUtils: activated row={}, column={}, color={}'.format(row, column, color))
156
 
        self.emitSelectedColorHideSelf(color)
157
 
 
158
 
 
159
 
    def emitSelectedColorHideSelf(self, color):
160
 
        # Remember last selected color for future preselect
161
 
        kate.configuration[_INSERT_COLOR_LCC] = color.name()
162
 
        self.colorSelected.emit(color)
163
 
        self.hide()
164
 
 
165
 
 
166
 
    def eventFilter(self, obj, event):
167
 
        '''Hide self on Esc key'''
168
 
        if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape:
169
 
            self.hide()
170
 
            return True
171
 
        return super(ColorChooser, self).eventFilter(obj, event)
172
 
 
173
 
 
174
 
    def moveAround(self, position):
175
 
        '''Smart positioning self around cursor'''
176
 
        print('ColorUtils: cursor position={}'.format(position))
177
 
        print('ColorUtils: kwm.geo={}'.format(kate.mainWindow().geometry()))
178
 
        print('ColorUtils: kwm.geo.center={}'.format(kate.mainWindow().geometry().center()))
179
 
        print('ColorUtils: p={}'.format(kate.mainWindow().geometry().topLeft() + position))
180
 
        self.colors.resizeColumnsToContents()
181
 
        self.colors.resizeRowsToContents()
182
 
        mwg = kate.mainWindow().geometry()
183
 
        wc = mwg.center()
184
 
        pos = mwg.topLeft() + position
185
 
        pos.setY(pos.y() + 40)
186
 
        if wc.y() < pos.y():
187
 
            pos.setY(pos.y() - self.height())
188
 
        if wc.x() < pos.x():
189
 
            pos.setX(pos.x() - self.width())
190
 
 
191
 
        self.move(pos)
192
 
 
193
 
 
194
 
def _collect_colors(document):
195
 
    '''Scan a given document and collect unique colors
196
 
 
197
 
        Returns a list of QColor objects.
198
 
    '''
199
 
    result = []
200
 
    # Iterate over document's lines trying to find #colors
201
 
    for l in range(0, document.lines()):
202
 
        line = document.line(l)                             # Get the current line
203
 
        start = 0                                           # Set initial position to 0 (line start)
204
 
        while start < len(line):                            # Repeat 'till the line end
205
 
            start = line.find('#', start)                   # Try to find a '#' character (start of #color)
206
 
            if start == -1:                                 # Did we found smth?
207
 
                break                                       # No! Nothing to do...
208
 
            # Try to get a word right after the '#' char
209
 
            end = start + 1
210
 
            for c in line[end:]:
211
 
                if not (c in string.hexdigits or c in string.ascii_letters):
212
 
                    break
213
 
                end += 1
214
 
            color_range = KTextEditor.Range(l, start, l, end)
215
 
            color_str = document.text(color_range)
216
 
            color = QColor(color_str)
217
 
            if color.isValid() and color not in result:
218
 
                result.append(color)
219
 
                print('ColorUtils: scan for #colors found {}'.format(color_str))
220
 
            start = end
221
 
    return result
222
 
 
223
 
 
224
 
def _get_color_range_under_cursor(view):
225
 
    assert(view is not None)
226
 
    if view.selection():                                    # Some text selected, just use it as input...
227
 
        color_range = view.selectionRange()
228
 
    else:                                                   # If no selection, try to get a #color under cursor
229
 
        color_range = common.getBoundTextRangeSL(
230
 
            common.IDENTIFIER_BOUNDARIES - {'#'}
231
 
          , common.IDENTIFIER_BOUNDARIES
232
 
          , view.cursorPosition()
233
 
          , view.document()
234
 
          )
235
 
        # Check if a word under cursor is a valid #color
236
 
        color = QColor(view.document().text(color_range))
237
 
        if not color.isValid():
238
 
            color_range = KTextEditor.Range(view.cursorPosition(), view.cursorPosition())
239
 
    return color_range
240
 
 
241
 
 
242
 
@kate.action(i18nc('@action:inmenu', 'Insert Color'), shortcut='Meta+Shift+C', icon='color', menu='Tools')
243
 
def insertColor():
244
 
    '''Insert/edit color string using color chooser dialog
245
 
 
246
 
    If cursor positioned in a color string, this action will edit it, otherwise
247
 
    a new color string will be inserted into a document.
248
 
    '''
249
 
    document = kate.activeDocument()
250
 
    view = kate.activeView()
251
 
 
252
 
    global colorChooserWidget
253
 
    colorChooserWidget.setColors(_collect_colors(document))
254
 
    colorChooserWidget.moveAround(view.cursorPositionCoordinates())
255
 
 
256
 
 
257
 
@pyqtSlot(QColor)
258
 
def _insertColorIntoActiveDocument(color):
259
 
    color_str = color.name()                                # Get it as color string
260
 
    print('ColorUtils: selected color: {}'.format(color_str))
261
 
    document = kate.activeDocument()
262
 
    view = kate.activeView()
263
 
    cursor = view.cursorPosition()
264
 
    has_selection = view.selection()                        # Remember current selection state
265
 
    color_range = _get_color_range_under_cursor(view)
266
 
    document.startEditing()
267
 
    document.replaceText(color_range, color_str)            # Replace selected/found range w/ a new text
268
 
    document.endEditing()
269
 
    # Select just entered #color, if something was selected before
270
 
    if has_selection:
271
 
        start_pos = color_range.start()
272
 
        view.setSelection(KTextEditor.Range(start_pos, len(color_str)))
273
 
 
274
 
 
275
 
class ColorSwatcher:
276
 
    '''Class encapsuling the ability to show color swatches (colored tooltips)'''
277
 
    swatch_template = '<div>{}</div>'.format('&nbsp;' * 10)
278
 
 
279
 
    def __init__(self):
280
 
        self.old_palette = None
281
 
        kate.viewChanged(self.view_changed)
282
 
        self.view_changed()
283
 
 
284
 
    def view_changed(self):
285
 
        '''Connects a swatch showing slot to each view’s selection change signal'''
286
 
        view = kate.activeView()
287
 
        if view:
288
 
            view.selectionChanged.connect(self.show_swatch)
289
 
 
290
 
    def show_swatch(self, view):
291
 
        '''Shows the swatch if a valid color is selected'''
292
 
        if view.selection():
293
 
            color = QColor(view.selectionText())
294
 
            if color.isValid():
295
 
                cursor_pos = view.cursorPositionCoordinates()
296
 
                QToolTip.showText(cursor_pos, self.swatch_template)
297
 
                self.change_palette(color)
298
 
 
299
 
    def change_palette(self, color):
300
 
        '''Sets the global tooltip background to the given color and initializes reset'''
301
 
        self.old_palette = QToolTip.palette()
302
 
        p = QPalette(self.old_palette)
303
 
        p.setColor(QPalette.All, QPalette.ToolTipBase, color)
304
 
        QToolTip.setPalette(p)
305
 
 
306
 
        self.timer = QTimer()
307
 
        self.timer.timeout.connect(self.try_reset_palette)
308
 
        self.timer.start(300) #short enough not to flicker a wrongly colored tooltip
309
 
 
310
 
    def try_reset_palette(self):
311
 
        '''Resets the global tooltip background color as soon as the swatch is hidden'''
312
 
        if self.old_palette is not None and not QToolTip.isVisible():
313
 
            QToolTip.setPalette(self.old_palette)
314
 
            self.old_palette = None
315
 
 
316
 
 
317
 
class ColorRangePair:
318
 
    '''Simple class to store a #color associated w/ a location in a document'''
319
 
    color = None
320
 
    color_range = None
321
 
 
322
 
    def __init__(self, color, color_range):
323
 
        self.color = color
324
 
        self.color_range = color_range
325
 
 
326
 
 
327
 
class PaletteView(QObject):
328
 
    '''A toolview to display palette of the current document'''
329
 
    colors = []
330
 
    toolView = None
331
 
    colorCellsWidget = None
332
 
 
333
 
    def __init__(self, parent):
334
 
        super(PaletteView, self).__init__(parent)
335
 
        self.toolView = kate.mainInterfaceWindow().createToolView(
336
 
            'color_tools_pate_plugin'
337
 
          , kate.Kate.MainWindow.Bottom
338
 
          , kate.gui.loadIcon('color')
339
 
          , i18nc('@title:tab', 'Palette')
340
 
          )
341
 
        self.toolView.installEventFilter(self)
342
 
        # By default, the toolview has box layout, which is not easy to delete.
343
 
        # For now, just add an extra widget.
344
 
        top = QWidget(self.toolView)
345
 
        # Set up the user interface from Designer.
346
 
        interior = uic.loadUi(os.path.join(os.path.dirname(__file__), 'color_tools_toolview.ui'), top)
347
 
        interior.update.clicked.connect(self.update)
348
 
        self.colorCellsWidget = KColorCells(interior, 1, 1)
349
 
        # TODO Don't know how to deal w/ drag-n-drops :(
350
 
        # It seems there is no signal to realize that some item has changed :(
351
 
        # (at lieast I didn't find it)
352
 
        self.colorCellsWidget.setAcceptDrags(False)
353
 
        self.colorCellsWidget.setEditTriggers(self.colorCellsWidget.NoEditTriggers)
354
 
        interior.verticalLayout.addWidget(self.colorCellsWidget)
355
 
        self.colorCellsWidget.colorSelected.connect(self.colorSelected)
356
 
        self.colorCellsWidget.colorDoubleClicked.connect(self.colorDoubleClicked)
357
 
 
358
 
    def __del__(self):
359
 
        '''Plugins that use a toolview need to delete it for reloading to work.'''
360
 
        if self.toolView:
361
 
            self.toolView.deleteLater()
362
 
            self.toolView = None
363
 
 
364
 
    def eventFilter(self, obj, event):
365
 
        '''Hide the Palette tool view on ESCAPE key'''
366
 
        if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape:
367
 
            kate.mainInterfaceWindow().hideToolView(self.toolView)
368
 
            return True
369
 
        return self.toolView.eventFilter(obj, event)
370
 
 
371
 
    def updateColors(self, view=None):
372
 
        '''Scan a document for #colors
373
 
 
374
 
            Returns a list of tuples: QColor and range in a document
375
 
            TODO Some refactoring needed to reduce code duplication
376
 
            (@sa _get_color_range_under_cursor())
377
 
        '''
378
 
        self.colors = []                                    # Clear previous colors
379
 
        if view:
380
 
            document = view.document()
381
 
        else:
382
 
            try:
383
 
                document = kate.activeDocument()
384
 
            except kate.NoActiveView:
385
 
                return                                      # Do nothing if we can't get a current document
386
 
        # Iterate over document's lines trying to find #colors
387
 
        for l in range(0, document.lines()):
388
 
            line = document.line(l)                         # Get the current line
389
 
            start = 0                                       # Set initial position to 0 (line start)
390
 
            while start < len(line):                        # Repeat 'till the line end
391
 
                start = line.find('#', start)               # Try to find a '#' character (start of #color)
392
 
                if start == -1:                             # Did we found smth?
393
 
                    break                                   # No! Nothing to do...
394
 
                # Try to get a word right after the '#' char
395
 
                end = start + 1
396
 
                for c in line[end:]:
397
 
                    if not (c in string.hexdigits):
398
 
                        break
399
 
                    end += 1
400
 
                color_range = KTextEditor.Range(l, start, l, end)
401
 
                color_str = document.text(color_range)
402
 
                color = QColor(color_str)
403
 
                if color.isValid():
404
 
                    self.colors.append(ColorRangePair(color, color_range))
405
 
                    print('ColorUtilsToolView: scan for #colors found {}'.format(color_str))
406
 
                start = end
407
 
 
408
 
    def updateColorCells(self):
409
 
        '''Calculate rows*columns and fill the cells w/ #colors'''
410
 
        if len(self.colors):
411
 
            print('ColorToolView: colors={}'.format(len(self.colors)))
412
 
            # Recalculate rows/columns
413
 
            print('ColorToolView: width={}, heigth={}'.format(self.colorCellsWidget.width(), self.colorCellsWidget.height()))
414
 
            columns = int(self.colorCellsWidget.width() / 30)
415
 
            print('ColorToolView: columns={}'.format(columns))
416
 
            visible_rows = int(self.colorCellsWidget.height() / 25)
417
 
            print('ColorToolView: visible_rows={}'.format(visible_rows))
418
 
            if len(self.colors) < (columns * visible_rows):
419
 
                rows = visible_rows
420
 
                print('ColorToolView: rows={}'.format(rows))
421
 
            else:
422
 
                visible_cells = columns * visible_rows
423
 
                print('ColorToolView: visible_cells={}'.format(visible_cells))
424
 
                rest = len(self.colors) - visible_cells
425
 
                print('ColorToolView: rest={}'.format(rest))
426
 
                rows = visible_rows + int(rest / columns) + int(bool(rest % columns))
427
 
                print('ColorToolView: rows={}'.format(rows))
428
 
        else:
429
 
            columns = 1
430
 
            rows = 1
431
 
            self.colors.append(ColorRangePair(QColor(), KTextEditor.Range()))
432
 
        self.colorCellsWidget.setColumnCount(columns)
433
 
        self.colorCellsWidget.setRowCount(rows)
434
 
        self.colorCellsWidget.resizeColumnsToContents()
435
 
        self.colorCellsWidget.resizeRowsToContents()
436
 
        # Fill color cells
437
 
        for i, crp in enumerate(self.colors):
438
 
            self.colorCellsWidget.setColor(i, crp.color)
439
 
        for i in range(len(self.colors), columns * rows):
440
 
            self.colorCellsWidget.setColor(i, QColor())
441
 
        _set_tooltips(rows, columns, self.colorCellsWidget)
442
 
 
443
 
 
444
 
    @pyqtSlot()
445
 
    def update(self, view=None):
446
 
        self.updateColors(view)
447
 
        self.updateColorCells()
448
 
 
449
 
 
450
 
    @pyqtSlot(int, QColor)
451
 
    def colorSelected(self, idx, color):
452
 
        '''Move cursor to the position of the selected #color and select the range'''
453
 
        view = kate.activeView()
454
 
        view.setCursorPosition(self.colors[idx].color_range.start())
455
 
        view.setSelection(self.colors[idx].color_range)
456
 
 
457
 
 
458
 
    @pyqtSlot(int, QColor)
459
 
    def colorDoubleClicked(self, idx, color):
460
 
        '''Edit selected color on double click'''
461
 
        insertColor()
462
 
        self.update()
463
 
 
464
 
 
465
 
@kate.viewChanged
466
 
@kate.viewCreated
467
 
def viewChanged(view=None):
468
 
    ''' Rescan current document on view create and/or change'''
469
 
    global paletteView
470
 
    return paletteView.update(view)
471
 
 
472
 
 
473
 
@kate.init
474
 
def init():
475
 
    '''Iniialize global variables and read config'''
476
 
    # Set default value for last used #color if not configured yet
477
 
    if _INSERT_COLOR_LCC not in kate.configuration:
478
 
        kate.configuration[_INSERT_COLOR_LCC] = '#ffffff'
479
 
 
480
 
    swatcher = ColorSwatcher()
481
 
 
482
 
    # Make an instance of a palette tool view
483
 
    global paletteView
484
 
    if paletteView is None:
485
 
        paletteView = PaletteView(kate.mainWindow())
486
 
 
487
 
    # Make an instance of a color chooser widget,
488
 
    # connect it to active document updater
489
 
    global colorChooserWidget
490
 
    if colorChooserWidget is None:
491
 
        colorChooserWidget = ColorChooser(kate.mainWindow())
492
 
        colorChooserWidget.colorSelected.connect(_insertColorIntoActiveDocument)
493
 
 
494
 
 
495
 
@kate.unload
496
 
def destroy():
497
 
    '''Plugins that use a toolview need to delete it for reloading to work.'''
498
 
    global paletteView
499
 
    if paletteView:
500
 
        del paletteView
501
 
        paletteView = None
502
 
 
503
 
    global colorChooserWidget
504
 
    if colorChooserWidget:
505
 
        del colorChooserWidget
506
 
        colorChooserWidget = None
507
 
 
508
 
 
509
 
# kate: space-indent on; indent-width 4;