# -*- coding: utf-8 -*- # # Copyright (C) 2013-2018 by Ihor E. Novikov # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. import cairo import math import os from sk1 import config, modes, events from sk1.pwidgets import Painter from uc2 import uc2const, cms, sk2const from uc2.utils import fsutils HFONT = {} VFONT = {} def load_font(color=(0, 0, 0)): fntdir = 'ruler-font%dpx' % config.ruler_font_size fntdir = os.path.join(config.resource_dir, 'fonts', fntdir) def get_colored_surface(file_name, color, vertical=False): file_name = fsutils.get_sys_path(file_name) surface = cairo.ImageSurface.create_from_png(file_name) w, h = surface.get_width(), surface.get_height() res = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) cr = cairo.Context(res) cr.set_source_rgb(*color) cr.mask_surface(surface, 0, 0) cr.fill() return h if vertical else w, res for char in '.,-0123456789': file_name = 'hdot.png' if char in '.,' else 'h%s.png' % char file_name = os.path.join(fntdir, file_name) HFONT[char] = get_colored_surface(file_name, color) file_name = 'vdot.png' if char in '.,' else 'v%s.png' % char file_name = os.path.join(fntdir, file_name) VFONT[char] = get_colored_surface(file_name, color, True) CAIRO_WHITE = [1.0, 1.0, 1.0] CAIRO_BLACK = [0.0, 0.0, 0.0] CORNER = { sk2const.DOC_ORIGIN_CENTER: ((8, 1, 8, 6), (8, 11, 8, 16), (8, 8, 8, 9), (1, 8, 6, 8), (11, 8, 16, 8),), sk2const.DOC_ORIGIN_LL: ((3, 1, 3, 16), (2, 2, 5, 2), (1, 13, 16, 13), (14, 12, 14, 15), (5, 11, 6, 10), (7, 9, 8, 8), (9, 7, 10, 6), (11, 5, 12, 4), (13, 3, 14, 2)), sk2const.DOC_ORIGIN_LU: ((3, 1, 3, 16), (2, 14, 5, 14), (1, 3, 16, 3), (14, 2, 14, 5), (5, 5, 6, 6), (7, 7, 8, 8), (9, 9, 10, 10), (11, 11, 12, 12), (13, 13, 14, 14)) } class RulerCorner(Painter): presenter = None eventloop = None origin = sk2const.DOC_ORIGIN_LL mtds = None def __init__(self, presenter): self.presenter = presenter self.eventloop = presenter.eventloop self.dc = self.presenter.app.mw.mdi.corner Painter.__init__(self) self.eventloop.connect(self.eventloop.DOC_MODIFIED, self.changes) self.changes() def changes(self): if not self.origin == self.presenter.model.doc_origin: self.origin = self.presenter.model.doc_origin self.dc.refresh() def mouse_left_up(self, *args): origin = self.presenter.model.doc_origin if origin < sk2const.ORIGINS[-1]: origin += 1 else: origin = sk2const.ORIGINS[0] self.presenter.api.set_doc_origin(origin) def paint(self): w, h = self.dc.get_size() fg = cms.val_255(config.ruler_fg) bg = cms.val_255(config.ruler_bg) self.dc.set_stroke(None) self.dc.set_fill(bg) self.dc.draw_rect(0, 0, w, h) self.dc.draw_linear_gradient((0, h - 1, w * 2, 1), bg, fg) self.dc.draw_linear_gradient((w - 1, 0, 1, h * 2), bg, fg, True) self.dc.set_stroke(fg) shift = (w - 19) / 2 + 1 for x0, y0, x1, y1 in CORNER[self.origin]: self.dc.draw_line(x0 + shift, y0 + shift, x1 + shift, y1 + shift) class Ruler(Painter): presenter = None eventloop = None vertical = False init_flag = False draw_guide = False surface = None ctx = None default_cursor = None guide_cursor = None mouse_captured = False width = 0 height = 0 pointer = [] def __init__(self, presenter, vertical=True): self.presenter = presenter self.eventloop = presenter.eventloop self.vertical = vertical Painter.__init__(self) mdi = self.presenter.app.mw.mdi self.dc = mdi.vruler if vertical else mdi.hruler if not VFONT: load_font(config.ruler_fg) self.default_cursor = self.dc.get_cursor() if not self.vertical: self.guide_cursor = self.presenter.app.cursors[modes.HGUIDE_MODE] else: self.guide_cursor = self.presenter.app.cursors[modes.VGUIDE_MODE] self.eventloop.connect(self.eventloop.VIEW_CHANGED, self.dc.refresh) events.connect(events.CONFIG_MODIFIED, self.check_config) def check_config(self, *args): if args[0] in ('ruler_font_size', 'ruler_fg'): load_font(config.ruler_fg) def calc_ruler(self): canvas = self.presenter.canvas w, h = self.presenter.get_page_size() x = y = 0 udx = udy = uc2const.unit_dict[self.presenter.model.doc_units] origin = self.presenter.model.doc_origin if origin == sk2const.DOC_ORIGIN_LL: x0, y0 = canvas.point_doc_to_win([-w / 2.0 + x, -h / 2.0 + y]) elif origin == sk2const.DOC_ORIGIN_LU: x0, y0 = canvas.point_doc_to_win([-w / 2.0 + x, h / 2.0 + y]) else: x0, y0 = canvas.point_doc_to_win([x, y]) dx = udx * canvas.zoom dy = udy * canvas.zoom sdist = config.snap_distance i = 0.0 while dx < sdist + 3: i = i + 0.5 dx = dx * 10.0 * i if dx / 2.0 > sdist + 3 and dx / 2.0 > udx * canvas.zoom: dx = dx / 2.0 i = 0.0 while dy < sdist + 3: i = i + 0.5 dy = dy * 10.0 * i if dy / 2.0 > sdist + 3 and dy / 2.0 > udy * canvas.zoom: dy = dy / 2.0 sx = (x0 / dx - math.floor(x0 / dx)) * dx sy = (y0 / dy - math.floor(y0 / dy)) * dy return x0, y0, dx, dy, sx, sy def get_ticks(self): canvas = self.presenter.canvas pw, ph = self.presenter.get_page_size() origin = self.presenter.model.doc_origin unit = uc2const.unit_dict[self.presenter.model.doc_units] w, h = self.dc.get_size() x0, y0, dx, dy, sx, sy = self.calc_ruler() small_ticks = [] text_ticks = [] if not self.vertical: i = -1 pos = 0 while pos < w: pos = sx + i * dx small_ticks.append(sx + i * dx) if dx > 10: small_ticks.append(pos + dx * .5) i += 1 coef = round(50.0 / dx) or 1.0 dxt = dx * coef sxt = (x0 / dxt - math.floor(x0 / dxt)) * dxt unit_dx = dxt / (unit * canvas.zoom) float_flag = True if unit_dx < 1.0 else False i = -1 pos = 0 shift = 0.0 if origin == sk2const.DOC_ORIGIN_CENTER else pw / 2.0 while pos < w: pos = sxt + i * dxt doc_pos = canvas.point_win_to_doc((pos, 0))[0] + shift doc_pos *= uc2const.point_dict[self.presenter.model.doc_units] if float_flag: txt = str(round(doc_pos, 4)) if doc_pos else '0' else: txt = str(int(round(doc_pos))) text_ticks.append((sxt + i * dxt, txt)) i += 1 else: i = -1 pos = 0 while pos < h: pos = sy + i * dy small_ticks.append(sy + i * dy) if dy > 10: small_ticks.append(pos + dy * .5) i += 1 coef = round(50.0 / dy) or 1.0 dyt = dy * coef syt = (y0 / dyt - math.floor(y0 / dyt)) * dyt unit_dy = dyt / (unit * canvas.zoom) float_flag = True if unit_dy < 1.0 else False i = -1 pos = 0 shift = 0.0 if origin == sk2const.DOC_ORIGIN_CENTER else ph / 2.0 shift = -shift if origin == sk2const.DOC_ORIGIN_LU else shift while pos < h: pos = syt + i * dyt doc_pos = canvas.point_win_to_doc((0, pos))[1] + shift if origin == sk2const.DOC_ORIGIN_LU: doc_pos *= -1.0 doc_pos *= uc2const.point_dict[self.presenter.model.doc_units] if float_flag: txt = str(round(doc_pos, 4)) if doc_pos else '0' else: txt = str(int(round(doc_pos))) text_ticks.append((syt + i * dyt, txt)) i += 1 return small_ticks, text_ticks def paint(self): if self.presenter is None: return w, h = self.dc.get_size() fmt = cairo.FORMAT_RGB24 if self.surface is None or self.width != w or self.height != h: self.surface = cairo.ImageSurface(fmt, w, h) self.width, self.height = w, h self.ctx = cairo.Context(self.surface) self.ctx.set_matrix(cairo.Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)) self.ctx.set_source_rgb(*config.ruler_bg) self.ctx.paint() self.ctx.set_antialias(cairo.ANTIALIAS_NONE) self.ctx.set_line_width(1.0) self.ctx.set_dash([]) self.ctx.set_source_rgb(*config.ruler_fg) if self.vertical: self.vrender(w, h) else: self.hrender(w, h) self.dc.draw_surface(self.surface, 0, 0) def hrender(self, w, h): self.ctx.move_to(0, h) self.ctx.line_to(w, h) small_ticks, text_ticks = self.get_ticks() for item in small_ticks: self.ctx.move_to(item, h - config.ruler_small_tick) self.ctx.line_to(item, h - 1) for pos, txt in text_ticks: self.ctx.move_to(pos, h - config.ruler_large_tick) self.ctx.line_to(pos, h - 1) self.ctx.stroke() vshift = config.ruler_text_vshift hshift = config.ruler_text_hshift for pos, txt in text_ticks: for character in txt: data = HFONT[character] position = int(pos) + hshift self.ctx.set_source_surface(data[1], position, vshift) self.ctx.paint() pos += data[0] def vrender(self, w, h): self.ctx.move_to(w, 0) self.ctx.line_to(w, h) small_ticks, text_ticks = self.get_ticks() for item in small_ticks: self.ctx.move_to(w - config.ruler_small_tick, item) self.ctx.line_to(w - 1, item) for item, txt in text_ticks: self.ctx.move_to(w - config.ruler_large_tick, item) self.ctx.line_to(w - 1, item) self.ctx.stroke() vshift = config.ruler_text_vshift hshift = config.ruler_text_hshift for pos, txt in text_ticks: for character in txt: data = VFONT[character] position = int(pos) - data[0] - hshift self.ctx.set_source_surface(data[1], vshift, position) self.ctx.paint() pos -= data[0] # ------ Guides creation def set_ruler_cursor(self, mode=False): self.dc.set_cursor(self.guide_cursor if mode else self.default_cursor) def capture_lost(self): self.dc.release_mouse() self.set_ruler_cursor() def mouse_left_down(self, point): if not self.presenter.methods.is_guide_editable(): return self.width, self.height = (float(item) for item in self.dc.get_size()) self.draw_guide = True self.set_ruler_cursor(True) self.dc.capture_mouse() canvas = self.presenter.canvas canvas.timer.start() canvas.set_temp_mode(modes.GUIDE_MODE) if not self.vertical: canvas.controller.mode = modes.HGUIDE_MODE else: canvas.controller.mode = modes.VGUIDE_MODE canvas.set_canvas_cursor(canvas.controller.mode) def mouse_left_up(self, point): if not self.presenter.methods.is_guide_editable(): return self.pointer = point self.dc.release_mouse() if not self.vertical: y_win = self.pointer[1] - self.height if y_win > 0.0: p = [self.pointer[0], y_win] p, p_doc = self.presenter.snap.snap_point(p, snap_x=False)[1:] guides = [[p_doc[1], uc2const.HORIZONTAL], ] self.presenter.api.create_guides(guides) else: x_win = self.pointer[0] - self.width if x_win > 0.0: p = [x_win, self.pointer[1]] p, p_doc = self.presenter.snap.snap_point(p, snap_y=False)[1:] guides = [[p_doc[0], uc2const.VERTICAL], ] self.presenter.api.create_guides(guides) self.set_ruler_cursor() self.presenter.canvas.timer.stop() self.presenter.canvas.restore_mode() self.draw_guide = False self.pointer = [] self.presenter.canvas.dragged_guide = () self.presenter.canvas.force_redraw() def mouse_move(self, point): if self.draw_guide: self.pointer = point self.repaint_guide() def repaint_guide(self): p = 0 if self.draw_guide and self.pointer: if not self.vertical: y_win = self.pointer[1] - self.height p = [self.pointer[0], y_win] p = self.presenter.snap.snap_point(p, snap_x=False)[1] else: x_win = self.pointer[0] - self.width p = [x_win, self.pointer[1]] p = self.presenter.snap.snap_point(p, snap_y=False)[1] self.presenter.canvas.controller.end = p