forked from Mirrors/apostrophe
removed build
parent
fd22793e8b
commit
08385d6e91
|
@ -1,35 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import locale
|
||||
from locale import gettext as _
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
from uberwriter_lib.AboutDialog import AboutDialog
|
||||
|
||||
# See uberwriter_lib.AboutDialog.py for more details about how this class works.
|
||||
class AboutUberwriterDialog(AboutDialog):
|
||||
__gtype_name__ = "AboutUberwriterDialog"
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
"""Set up the about dialog"""
|
||||
super(AboutUberwriterDialog, self).finish_initializing(builder)
|
||||
|
||||
# Code for other initialization actions should be added here.
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import re
|
||||
from gi.repository import Gtk
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
logger.critical("RASDAOD JKJD ASJD SJ")
|
||||
|
||||
class FixTable():
|
||||
|
||||
def __init__(self, TextBuffer):
|
||||
self.TextBuffer = TextBuffer
|
||||
|
||||
@staticmethod
|
||||
def create_seperator(widths, char):
|
||||
"""
|
||||
Generate a line of + and - as sepeartor
|
||||
|
||||
Example:
|
||||
>>> create_separarator([2, 4], '-')
|
||||
'+----+------+'
|
||||
"""
|
||||
line = []
|
||||
|
||||
for w in widths:
|
||||
line.append("+" + char * (w + 2))
|
||||
|
||||
line.append("+")
|
||||
return ''.join(line)
|
||||
|
||||
@staticmethod
|
||||
def create_line(columns, widths):
|
||||
"""
|
||||
Crea una fila de la tabla separando los campos con un '|'.
|
||||
|
||||
El argumento `columns` es una lista con las celdas que se
|
||||
quieren imprimir y el argumento `widths` tiene el ancho
|
||||
de cada columna. Si la columna es mas ancha que el texto
|
||||
a imprimir se agregan espacios vacíos.
|
||||
|
||||
Example
|
||||
>>> create_line(['nombre', 'apellido'], [7, 10])
|
||||
'| nombre | apellido |'
|
||||
"""
|
||||
|
||||
line = zip(columns, widths)
|
||||
result = []
|
||||
|
||||
for text, width in line:
|
||||
spaces = " " * (width - len(text))
|
||||
text = "| " + text + spaces + " "
|
||||
result.append(text)
|
||||
|
||||
result.append("|")
|
||||
return ''.join(result)
|
||||
|
||||
@staticmethod
|
||||
def create_table(content):
|
||||
"""Imprime una tabla en formato restructuredText.
|
||||
|
||||
El argumento `content` tiene que ser una lista
|
||||
de celdas.
|
||||
|
||||
Example:
|
||||
|
||||
>>> print create_table([['software', 'vesion'], ['python', '3.2'],
|
||||
['vim', '7.3']])
|
||||
+----------+--------+
|
||||
| software | vesion |
|
||||
+==========+========+
|
||||
| python | 3.2 |
|
||||
+----------+--------+
|
||||
| vim | 7.3 |
|
||||
+----------+--------+
|
||||
"""
|
||||
|
||||
# obtiene las columnas de toda la tabla.
|
||||
columns = zip(*content)
|
||||
# calcula el tamano maximo que debe tener cada columna.
|
||||
# replace with len()
|
||||
widths = [max([len(x) for x in i]) for i in columns]
|
||||
|
||||
result = []
|
||||
|
||||
result.append(FixTable.create_seperator(widths, '-'))
|
||||
print(content, widths)
|
||||
result.append(FixTable.create_line(content[0], widths))
|
||||
result.append(FixTable.create_seperator(widths, '='))
|
||||
|
||||
for line in content[1:]:
|
||||
result.append(FixTable.create_line(line, widths))
|
||||
result.append(FixTable.create_seperator(widths, '-'))
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
@staticmethod
|
||||
def are_in_a_table(current_line):
|
||||
"Line in a table?"
|
||||
return "|" in current_line or "+" in current_line
|
||||
|
||||
@staticmethod
|
||||
def are_in_a_paragraph(current_line):
|
||||
"Line in a paragraph?"
|
||||
return len(current_line.strip()) >= 1
|
||||
|
||||
def get_table_bounds(self, are_in_callback):
|
||||
"""
|
||||
Gets the row number where the table begins and ends.
|
||||
are_in_callback argument must be a function
|
||||
indicating whether a particular line belongs or not
|
||||
to the table to be measured (or create).
|
||||
Returns two values as a tuple
|
||||
"""
|
||||
top = 0
|
||||
|
||||
buf = self.TextBuffer
|
||||
start_iter = buf.get_start_iter()
|
||||
end_iter = buf.get_end_iter()
|
||||
|
||||
text = self.TextBuffer.get_text(start_iter, end_iter, False).split('\n')
|
||||
logger.debug(text)
|
||||
length = len(text)
|
||||
bottom = length - 1
|
||||
|
||||
insert_mark = self.TextBuffer.get_insert()
|
||||
insert_iter = self.TextBuffer.get_iter_at_mark(insert_mark)
|
||||
current_row_index = insert_iter.get_line()
|
||||
|
||||
for a in range(current_row_index, top, -1):
|
||||
if not are_in_callback(text[a]):
|
||||
top = a + 1
|
||||
break
|
||||
|
||||
for b in range(current_row_index, length):
|
||||
if not are_in_callback(text[b]):
|
||||
bottom = b - 1
|
||||
break
|
||||
|
||||
return top, bottom
|
||||
|
||||
@staticmethod
|
||||
def remove_spaces(string):
|
||||
"""Remove unnecessary spaces"""
|
||||
return re.sub("\s\s+", " ", string)
|
||||
|
||||
@staticmethod
|
||||
def create_separators_removing_spaces(string):
|
||||
return re.sub("\s\s+", "|", string)
|
||||
|
||||
@staticmethod
|
||||
def extract_cells_as_list(string):
|
||||
"Extrae el texto de una fila de tabla y lo retorna como una lista."
|
||||
string = FixTable.remove_spaces(string)
|
||||
return [item.strip() for item in string.split('|') if item]
|
||||
|
||||
@staticmethod
|
||||
def extract_table(text, top, bottom):
|
||||
full_table_text = text[top:bottom]
|
||||
# selecciona solamente las lineas que tienen celdas con texto.
|
||||
only_text_lines = [x for x in full_table_text if '|' in x]
|
||||
# extrae las celdas y descarta los separadores innecesarios.
|
||||
return [FixTable.extract_cells_as_list(x) for x in only_text_lines]
|
||||
|
||||
@staticmethod
|
||||
def extract_words_as_lists(text, top, bottom):
|
||||
"Genera una lista de palabras para crear una lista."
|
||||
|
||||
lines = text[top:bottom + 1]
|
||||
return [FixTable.create_separators_removing_spaces(line).split('|') for line in lines]
|
||||
|
||||
def fix_table(self):
|
||||
"""
|
||||
Fix Table, so all columns have the same width (again)
|
||||
|
||||
`initial_row` is a int idicationg the current row index
|
||||
"""
|
||||
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
cursor_iter.set_line(cursor_iter.get_line())
|
||||
|
||||
end_line = cursor_iter.copy()
|
||||
end_line.forward_to_line_end()
|
||||
|
||||
|
||||
line_text = self.TextBuffer.get_text(cursor_iter, end_line, False)
|
||||
if FixTable.are_in_a_table(line_text):
|
||||
|
||||
# obtiene el indice donde comienza y termina la tabla.
|
||||
r1, r2 = self.get_table_bounds(FixTable.are_in_a_table)
|
||||
|
||||
logger.debug('asdasd ')
|
||||
|
||||
# extrae de la tabla solo las celdas de texto
|
||||
buf = self.TextBuffer
|
||||
start_iter = buf.get_start_iter()
|
||||
end_iter = buf.get_end_iter()
|
||||
|
||||
text = self.TextBuffer.get_text(start_iter, end_iter, False).split('\n')
|
||||
|
||||
table_as_list = FixTable.extract_table(text, r1, r2)
|
||||
logger.debug(table_as_list)
|
||||
# genera una nueva tabla tipo restructured text y la dibuja en el buffer.
|
||||
table_content = FixTable.create_table(table_as_list)
|
||||
logger.debug(table_content)
|
||||
# Insert table back into Buffer ...
|
||||
self.TextBuffer.insert(start_iter, table_content, -1)
|
||||
else:
|
||||
logger.debug("Not in a table")
|
||||
print("Not in table")
|
|
@ -1,217 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
||||
import locale
|
||||
|
||||
import re
|
||||
from gi.repository import Gtk, Gdk # pylint: disable=E0611
|
||||
from gi.repository import Pango # pylint: disable=E0611
|
||||
|
||||
from locale import gettext as _
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
from . MarkupBuffer import MarkupBuffer
|
||||
|
||||
class FormatShortcuts():
|
||||
|
||||
def __init__(self, textbuffer, texteditor):
|
||||
self.TextBuffer = textbuffer
|
||||
self.TextEditor = texteditor
|
||||
|
||||
def rule(self):
|
||||
self.TextBuffer.insert_at_cursor("\n\n-------\n")
|
||||
self.TextEditor.scroll_mark_onscreen(self.TextBuffer.get_insert())
|
||||
self.regex = MarkupBuffer.regex
|
||||
|
||||
def bold(self):
|
||||
self.apply_format("**")
|
||||
|
||||
def italic(self):
|
||||
self.apply_format("*")
|
||||
|
||||
def apply_format(self, wrap = "*"):
|
||||
if self.TextBuffer.get_has_selection():
|
||||
## Find current highlighting
|
||||
|
||||
(start, end) = self.TextBuffer.get_selection_bounds()
|
||||
moved = False
|
||||
if (
|
||||
start.get_offset() >= len(wrap) and
|
||||
end.get_offset() <= self.TextBuffer.get_char_count() - len(wrap)
|
||||
):
|
||||
moved = True
|
||||
ext_start = start.copy()
|
||||
ext_start.backward_chars(len(wrap))
|
||||
ext_end = end.copy()
|
||||
ext_end.forward_chars(len(wrap))
|
||||
text = self.TextBuffer.get_text(ext_start, ext_end, True)
|
||||
else:
|
||||
text = self.TextBuffer.get_text(start, end, True)
|
||||
|
||||
if moved and text.startswith(wrap) and text.endswith(wrap):
|
||||
text = text[len(wrap):-len(wrap)]
|
||||
new_text = text
|
||||
self.TextBuffer.delete(ext_start, ext_end)
|
||||
move_back = 0
|
||||
else:
|
||||
if moved:
|
||||
text = text[len(wrap):-len(wrap)]
|
||||
new_text = text.lstrip().rstrip()
|
||||
text = text.replace(new_text, wrap + new_text + wrap)
|
||||
|
||||
self.TextBuffer.delete(start, end)
|
||||
move_back = len(wrap)
|
||||
|
||||
self.TextBuffer.insert_at_cursor(text)
|
||||
text_length = len(new_text)
|
||||
|
||||
else:
|
||||
helptext = ""
|
||||
if wrap == "*":
|
||||
helptext = _("emphasized text")
|
||||
elif wrap == "**":
|
||||
helptext = _("strong text")
|
||||
|
||||
self.TextBuffer.insert_at_cursor(wrap + helptext + wrap)
|
||||
text_length = len(helptext)
|
||||
move_back = len(wrap)
|
||||
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
cursor_iter.backward_chars(move_back)
|
||||
self.TextBuffer.move_mark_by_name('selection_bound', cursor_iter)
|
||||
cursor_iter.backward_chars(text_length)
|
||||
self.TextBuffer.move_mark_by_name('insert', cursor_iter)
|
||||
|
||||
def unordered_list_item(self):
|
||||
helptext = _("List item")
|
||||
text_length = len(helptext)
|
||||
move_back = 0
|
||||
if self.TextBuffer.get_has_selection():
|
||||
(start, end) = self.TextBuffer.get_selection_bounds()
|
||||
if start.starts_line():
|
||||
text = self.TextBuffer.get_text(start, end, False)
|
||||
if text.startswith(("- ", "* ", "+ ")):
|
||||
delete_end = start.forward_chars(2)
|
||||
self.TextBuffer.delete(start, delete_end)
|
||||
else:
|
||||
self.TextBuffer.insert(start, "- ")
|
||||
else:
|
||||
move_back = 0
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
|
||||
start_ext = cursor_iter.copy()
|
||||
start_ext.backward_lines(3)
|
||||
text = self.TextBuffer.get_text(cursor_iter, start_ext, False)
|
||||
lines = text.splitlines()
|
||||
|
||||
for line in reversed(lines):
|
||||
if len(line) and line.startswith(("- ", "* ", "+ ")):
|
||||
if cursor_iter.starts_line():
|
||||
self.TextBuffer.insert_at_cursor(line[:2] + helptext)
|
||||
else:
|
||||
self.TextBuffer.insert_at_cursor("\n" + line[:2] + helptext)
|
||||
break
|
||||
else:
|
||||
if len(lines[-1]) == 0 and len(lines[-2]) == 0:
|
||||
self.TextBuffer.insert_at_cursor("- " + helptext)
|
||||
elif len(lines[-1]) == 0:
|
||||
if cursor_iter.starts_line():
|
||||
self.TextBuffer.insert_at_cursor("- " + helptext)
|
||||
else:
|
||||
self.TextBuffer.insert_at_cursor("\n- " + helptext)
|
||||
else:
|
||||
self.TextBuffer.insert_at_cursor("\n\n- " + helptext)
|
||||
break
|
||||
|
||||
self.select_edit(move_back, text_length)
|
||||
|
||||
def ordered_list_item(self):
|
||||
pass
|
||||
|
||||
def select_edit(self, move_back, text_length):
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
cursor_iter.backward_chars(move_back)
|
||||
self.TextBuffer.move_mark_by_name('selection_bound', cursor_iter)
|
||||
cursor_iter.backward_chars(text_length)
|
||||
self.TextBuffer.move_mark_by_name('insert', cursor_iter)
|
||||
self.TextEditor.scroll_mark_onscreen(self.TextBuffer.get_insert())
|
||||
|
||||
def above(self, linestart = ""):
|
||||
if not cursor_iter.starts_line():
|
||||
return ""
|
||||
else:
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
|
||||
start_ext = cursor_iter.copy()
|
||||
start_ext.backward_lines(2)
|
||||
text = self.TextBuffer.get_text(cursor_iter, start_ext, False)
|
||||
lines = text.splitlines()
|
||||
|
||||
#if line[-1].startswith
|
||||
|
||||
def get_lines(self, cursor_iter):
|
||||
|
||||
start_ext = cursor_iter.copy()
|
||||
start_ext.backward_lines(2)
|
||||
text = self.TextBuffer.get_text(cursor_iter, start_ext, False)
|
||||
lines = text.splitlines()
|
||||
|
||||
abs_line = cursor_iter.get_line()
|
||||
|
||||
return reversed(lines)
|
||||
|
||||
def heading(self, level = 0):
|
||||
helptext = _("Heading")
|
||||
before = ""
|
||||
if self.TextBuffer.get_has_selection():
|
||||
(start, end) = self.TextBuffer.get_selection_bounds()
|
||||
text = self.TextBuffer.get_text(start, end, False)
|
||||
self.TextBuffer.delete(start, end)
|
||||
else:
|
||||
text = helptext
|
||||
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
|
||||
#lines = self.get_lines(cursor_iter)
|
||||
|
||||
#if cursor_iter.starts_line():
|
||||
# if lines[1] != '':
|
||||
# before = before + "\n"
|
||||
#else:
|
||||
# match = re.match(r'([\#]+ )(.+)', lines[0])
|
||||
# if match:
|
||||
# if match.group(1):
|
||||
#
|
||||
# print match.group(0)
|
||||
# if len(match.group(0)) < 6:
|
||||
# before = before + "#" * (len(match.group(0)) + 1)
|
||||
# else:
|
||||
# before = before + "#"
|
||||
# else:
|
||||
# before = before + "\n\n"
|
||||
#
|
||||
#
|
||||
# check_text = self.TextBuffer.get_text(start, cursor_iter, False).decode("utf-8")
|
||||
# print check_text
|
||||
|
||||
self.TextBuffer.insert_at_cursor("#" + " " + text)
|
||||
self.select_edit(0, len(text))
|
|
@ -1,316 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import re
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
from gi.repository import Pango # pylint: disable=E0611
|
||||
|
||||
|
||||
class MarkupBuffer():
|
||||
|
||||
def __init__(self, Parent, TextBuffer, base_leftmargin):
|
||||
self.multiplier = 10
|
||||
self.parent = Parent
|
||||
self.TextBuffer = TextBuffer
|
||||
|
||||
# Styles
|
||||
self.italic = self.TextBuffer.create_tag("italic",
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.emph = self.TextBuffer.create_tag("emph",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
self.bolditalic = self.TextBuffer.create_tag("bolditalic",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.headline_two = self.TextBuffer.create_tag("headline_two",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
self.normal_indent = self.TextBuffer.create_tag('normal_indent', indent=100)
|
||||
|
||||
self.green_text = self.TextBuffer.create_tag(
|
||||
"greentext",
|
||||
foreground="#00364C"
|
||||
)
|
||||
|
||||
self.grayfont = self.TextBuffer.create_tag('graytag',
|
||||
foreground="gray")
|
||||
|
||||
self.blackfont = self.TextBuffer.create_tag('blacktag',
|
||||
foreground="#222")
|
||||
|
||||
self.underline = self.TextBuffer.create_tag(
|
||||
"underline",
|
||||
underline=Pango.Underline.SINGLE
|
||||
)
|
||||
|
||||
self.underline.set_property('weight', Pango.Weight.BOLD)
|
||||
|
||||
self.strikethrough = self.TextBuffer.create_tag(
|
||||
"strikethrough",
|
||||
strikethrough=True
|
||||
)
|
||||
|
||||
self.centertext = self.TextBuffer.create_tag(
|
||||
"centertext",
|
||||
justification=Gtk.Justification.CENTER
|
||||
)
|
||||
|
||||
self.TextBuffer.apply_tag(
|
||||
self.normal_indent,
|
||||
self.TextBuffer.get_start_iter(),
|
||||
self.TextBuffer.get_end_iter()
|
||||
)
|
||||
|
||||
self.rev_leftmargin = []
|
||||
for i in range(0, 6):
|
||||
name = "rev_marg_indent_left" + str(i)
|
||||
self.rev_leftmargin.append(self.TextBuffer.create_tag(name))
|
||||
self.rev_leftmargin[i].set_property("left-margin", 90 - 10 * (i + 1))
|
||||
self.rev_leftmargin[i].set_property("indent", - 10 * (i + 1) - 10)
|
||||
#self.leftmargin[i].set_property("background", "gray")
|
||||
|
||||
self.leftmargin = []
|
||||
|
||||
for i in range(0, 6):
|
||||
name = "marg_indent_left" + str(i)
|
||||
self.leftmargin.append(self.TextBuffer.create_tag(name))
|
||||
self.leftmargin[i].set_property("left-margin", base_leftmargin + 10 + 10 * (i + 1))
|
||||
self.leftmargin[i].set_property("indent", - 10 * (i + 1) - 10)
|
||||
|
||||
self.leftindent = []
|
||||
|
||||
for i in range(0, 15):
|
||||
name = "indent_left" + str(i)
|
||||
self.leftindent.append(self.TextBuffer.create_tag(name))
|
||||
self.leftindent[i].set_property("indent", - 10 * (i + 1) - 20)
|
||||
|
||||
self.table_env = self.TextBuffer.create_tag('table_env')
|
||||
self.table_env.set_property('wrap-mode', Gtk.WrapMode.NONE)
|
||||
# self.table_env.set_property('font', 'Ubuntu Mono 13px')
|
||||
self.table_env.set_property('pixels-above-lines', 0)
|
||||
self.table_env.set_property('pixels-below-lines', 0)
|
||||
regex = {
|
||||
"ITALIC": re.compile(r"\*\w(.+?)\*| _\w(.+?)_ ", re.UNICODE), # *asdasd* // _asdasd asd asd_
|
||||
"STRONG": re.compile(r"\*{2}\w(.+?)\*{2}| [_]{2}\w(.+?)[_]{2} ", re.UNICODE), # **as das** // __asdasdasd asd ad a__
|
||||
"STRONGITALIC": re.compile(r"\*{3}\w(.+?)\*{3}| [_]{3}\w(.+?)[_]{3} "),
|
||||
"BLOCKQUOTE": re.compile(r"^([\>]+ )", re.MULTILINE),
|
||||
"STRIKETHROUGH": re.compile(r"~~[^ `~\n].+?~~"),
|
||||
"LIST": re.compile(r"^[\-\*\+] ", re.MULTILINE),
|
||||
"NUMERICLIST": re.compile(r"^((\d|[a-z]|\#)+[\.\)]) ", re.MULTILINE),
|
||||
"INDENTEDLIST": re.compile(r"^(\t{1,6})((\d|[a-z]|\#)+[\.\)]|[\-\*\+]) ", re.MULTILINE),
|
||||
"HEADINDICATOR": re.compile(r"^(#{1,6}) ", re.MULTILINE),
|
||||
"HEADLINE": re.compile(r"^(#{1,6} [^\n]+)", re.MULTILINE),
|
||||
"HEADLINE_TWO": re.compile(r"^\w.+\n[\=\-]{3,}", re.MULTILINE),
|
||||
"MATH": re.compile(r"[\$]{1,2}([^` ].+?[^`\\ ])[\$]{1,2}"),
|
||||
"HORIZONTALRULE": re.compile(r"(\n\n[\*\-]{3,}\n)"),
|
||||
"TABLE": re.compile(r"^[\-\+]{5,}\n(.+?)\n[\-\+]{5,}\n", re.DOTALL),
|
||||
"LINK": re.compile(r"\(http(.+?)\)")
|
||||
}
|
||||
|
||||
def markup_buffer(self, mode=0):
|
||||
buf = self.TextBuffer
|
||||
|
||||
# Modes:
|
||||
# 0 -> start to end
|
||||
# 1 -> around the cursor
|
||||
# 2 -> n.d.
|
||||
|
||||
if mode == 0:
|
||||
context_start = buf.get_start_iter()
|
||||
context_end = buf.get_end_iter()
|
||||
context_offset = 0
|
||||
elif mode == 1:
|
||||
cursor_mark = buf.get_insert()
|
||||
context_start = buf.get_iter_at_mark(cursor_mark)
|
||||
context_start.backward_lines(3)
|
||||
context_end = buf.get_iter_at_mark(cursor_mark)
|
||||
context_end.forward_lines(2)
|
||||
context_offset = context_start.get_offset()
|
||||
|
||||
text = buf.get_slice(context_start, context_end, False)
|
||||
|
||||
self.TextBuffer.remove_tag(self.italic, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["ITALIC"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.italic, startIter, endIter)
|
||||
|
||||
self.TextBuffer.remove_tag(self.emph, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["STRONG"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.emph, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["STRONGITALIC"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.bolditalic, startIter, endIter)
|
||||
|
||||
self.TextBuffer.remove_tag(self.strikethrough, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["STRIKETHROUGH"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.strikethrough, startIter, endIter)
|
||||
|
||||
self.TextBuffer.remove_tag(self.green_text, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["MATH"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.green_text, startIter, endIter)
|
||||
|
||||
for margin in self.rev_leftmargin:
|
||||
self.TextBuffer.remove_tag(margin, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["LIST"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.rev_leftmargin[0], startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["NUMERICLIST"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 1
|
||||
if index < len(self.rev_leftmargin):
|
||||
margin = self.rev_leftmargin[index]
|
||||
self.TextBuffer.apply_tag(margin, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["BLOCKQUOTE"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 2
|
||||
if index < len(self.leftmargin):
|
||||
self.TextBuffer.apply_tag(self.leftmargin[index], startIter, endIter)
|
||||
|
||||
for leftindent in self.leftindent:
|
||||
self.TextBuffer.remove_tag(leftindent, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["INDENTEDLIST"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = (len(match.group(1)) - 1) * 2 + len(match.group(2))
|
||||
if index < len(self.leftindent):
|
||||
self.TextBuffer.apply_tag(self.leftindent[index], startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADINDICATOR"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 1
|
||||
if index < len(self.rev_leftmargin):
|
||||
margin = self.rev_leftmargin[index]
|
||||
self.TextBuffer.apply_tag(margin, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["HORIZONTALRULE"], text)
|
||||
rulecontext = context_start.copy()
|
||||
rulecontext.forward_lines(3)
|
||||
self.TextBuffer.remove_tag(self.centertext, rulecontext, context_end)
|
||||
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
startIter.forward_chars(2)
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.centertext, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADLINE"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.emph, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADLINE_TWO"], text)
|
||||
self.TextBuffer.remove_tag(self.headline_two, rulecontext, context_end)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.headline_two, startIter, endIter)
|
||||
|
||||
matches = re.finditer(self.regex["TABLE"], text)
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
endIter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.TextBuffer.apply_tag(self.table_env, startIter, endIter)
|
||||
|
||||
if self.parent.focusmode:
|
||||
self.focusmode_highlight()
|
||||
|
||||
def focusmode_highlight(self):
|
||||
self.TextBuffer.apply_tag(self.grayfont,
|
||||
self.TextBuffer.get_start_iter(),
|
||||
self.TextBuffer.get_end_iter())
|
||||
|
||||
self.TextBuffer.remove_tag(self.blackfont,
|
||||
self.TextBuffer.get_start_iter(),
|
||||
self.TextBuffer.get_end_iter())
|
||||
|
||||
cursor = self.TextBuffer.get_mark("insert")
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor)
|
||||
|
||||
end_sentence = cursor_iter.copy()
|
||||
end_sentence.forward_sentence_end()
|
||||
|
||||
end_line = cursor_iter.copy()
|
||||
end_line.forward_to_line_end()
|
||||
|
||||
comp = end_line.compare(end_sentence)
|
||||
# if comp < 0, end_line is BEFORE end_sentence
|
||||
if comp <= 0:
|
||||
end_sentence = end_line
|
||||
|
||||
start_sentence = cursor_iter.copy()
|
||||
start_sentence.backward_sentence_start()
|
||||
|
||||
self.TextBuffer.apply_tag(self.blackfont,
|
||||
start_sentence, end_sentence)
|
||||
|
||||
def set_multiplier(self, multiplier):
|
||||
self.multiplier = multiplier
|
||||
|
||||
def recalculate(self, lm):
|
||||
multiplier = self.multiplier
|
||||
for i in range(0, 6):
|
||||
self.rev_leftmargin[i].set_property("left-margin", (lm - multiplier) - multiplier * (i + 1))
|
||||
self.rev_leftmargin[i].set_property("indent", - multiplier * (i + 1) - multiplier)
|
||||
|
||||
for i in range(0, 6):
|
||||
self.leftmargin[i].set_property("left-margin", (lm - multiplier) + multiplier + multiplier * (i + 1))
|
||||
self.leftmargin[i].set_property("indent", - (multiplier - 1) * (i + 1) - multiplier)
|
||||
|
||||
def dark_mode(self, active=False):
|
||||
if active:
|
||||
self.green_text.set_property("foreground", "#FA5B0F")
|
||||
self.grayfont.set_property("foreground", "#666")
|
||||
self.blackfont.set_property("foreground", "#CCC")
|
||||
else:
|
||||
self.green_text.set_property("foreground", "#00364C")
|
||||
self.grayfont.set_property("foreground", "gray")
|
||||
self.blackfont.set_property("foreground", "#222")
|
|
@ -1,255 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
from gi.repository import Gtk
|
||||
import os
|
||||
import subprocess
|
||||
import locale
|
||||
from locale import gettext as _
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
from uberwriter_lib.AdvancedExportDialog import AdvancedExportDialog
|
||||
|
||||
# See uberwriter_lib.AboutDialog.py for more details about how this class works.
|
||||
class UberwriterAdvancedExportDialog(AdvancedExportDialog):
|
||||
__gtype_name__ = "UberwriterAdvancedExportDialog"
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
"""Set up the about dialog"""
|
||||
super(UberwriterAdvancedExportDialog, self).finish_initializing(builder)
|
||||
|
||||
# Code for other initialization actions should be added here.
|
||||
|
||||
self.builder.get_object("highlight_style").set_active(0)
|
||||
|
||||
format_store = Gtk.ListStore(int, str)
|
||||
for fmt_id in self.formats_dict:
|
||||
format_store.append([fmt_id, self.formats_dict[fmt_id]["name"]])
|
||||
self.format_field = builder.get_object('choose_format')
|
||||
self.format_field.set_model(format_store)
|
||||
|
||||
format_renderer = Gtk.CellRendererText()
|
||||
self.format_field.pack_start(format_renderer, True)
|
||||
self.format_field.add_attribute(format_renderer, "text", 1)
|
||||
self.format_field.set_active(0)
|
||||
self.show_all()
|
||||
|
||||
formats_dict = {
|
||||
1: {
|
||||
"name": "LaTeX Source",
|
||||
"ext": "tex",
|
||||
"to": "latex"
|
||||
},
|
||||
2: {
|
||||
"name": "LaTeX PDF",
|
||||
"ext": "pdf",
|
||||
"to": "pdf"
|
||||
},
|
||||
3: {
|
||||
"name": "LaTeX beamer slide show Source .tex",
|
||||
"ext": "tex",
|
||||
"to": "beamer"
|
||||
},
|
||||
4: {
|
||||
"name": "LaTeX beamer slide show PDF",
|
||||
"ext": "pdf",
|
||||
"to": "beamer"
|
||||
},
|
||||
5: {
|
||||
"name": "HTML",
|
||||
"ext": "html",
|
||||
"to": "html"
|
||||
},
|
||||
6: {
|
||||
"name": "Textile",
|
||||
"ext": "txt",
|
||||
"to": "textile"
|
||||
},
|
||||
7: {
|
||||
"name": "OpenOffice text document",
|
||||
"ext": "odt",
|
||||
"to": "odt"
|
||||
},
|
||||
8: {
|
||||
"name": "Word docx",
|
||||
"ext": "docx",
|
||||
"to": "docx"
|
||||
},
|
||||
9: {
|
||||
"name": "reStructuredText txt",
|
||||
"ext": "txt",
|
||||
"to": "rst"
|
||||
},
|
||||
10: {
|
||||
"name": "ConTeXt tex",
|
||||
"ext": "tex",
|
||||
"to": "context"
|
||||
},
|
||||
11: {
|
||||
"name": "groff man",
|
||||
"ext": "man",
|
||||
"to": "man"
|
||||
},
|
||||
12: {
|
||||
"name": "MediaWiki markup",
|
||||
"ext": "txt",
|
||||
"to": "mediawiki"
|
||||
},
|
||||
13: {
|
||||
"name": "OpenDocument XML",
|
||||
"ext": "xml",
|
||||
"to": "opendocument"
|
||||
},
|
||||
14: {
|
||||
"name": "OpenDocument XML",
|
||||
"ext": "texi",
|
||||
"to": "texinfo"
|
||||
},
|
||||
15: {
|
||||
"name": "Slidy HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "slidy"
|
||||
},
|
||||
16: {
|
||||
"name": "Slideous HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "slideous"
|
||||
},
|
||||
17: {
|
||||
"name": "HTML5 + javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "dzslides"
|
||||
},
|
||||
18: {
|
||||
"name": "S5 HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "s5"
|
||||
},
|
||||
19: {
|
||||
"name": "EPub electronic publication",
|
||||
"ext": "epub",
|
||||
"to": "epub"
|
||||
},
|
||||
20: {
|
||||
"name": "RTF Rich Text Format",
|
||||
"ext": "rtf",
|
||||
"to": "rtf"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def advanced_export(self, text = ""):
|
||||
tree_iter = self.format_field.get_active_iter()
|
||||
if tree_iter != None:
|
||||
model = self.format_field.get_model()
|
||||
row_id, name = model[tree_iter][:2]
|
||||
|
||||
fmt = self.formats_dict[row_id]
|
||||
logger.info(fmt)
|
||||
filechooser = Gtk.FileChooserDialog(
|
||||
"Export as %s" % fmt["name"],
|
||||
self,
|
||||
Gtk.FileChooserAction.SAVE,
|
||||
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
|
||||
)
|
||||
|
||||
filechooser.set_do_overwrite_confirmation(True)
|
||||
|
||||
response = filechooser.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
filename = filechooser.get_filename()
|
||||
filechooser.destroy()
|
||||
else:
|
||||
filechooser.destroy()
|
||||
return
|
||||
|
||||
output_dir = os.path.abspath(os.path.join(filename, os.path.pardir))
|
||||
|
||||
basename = os.path.basename(filename)
|
||||
|
||||
args = ['pandoc', '--from=markdown']
|
||||
|
||||
to = "--to=%s" % fmt["to"]
|
||||
|
||||
if basename.endswith("." + fmt["ext"]):
|
||||
output_file = "--output=%s" % basename
|
||||
else:
|
||||
output_file = "--output=%s.%s" % (basename, fmt["ext"])
|
||||
|
||||
if self.builder.get_object("toc").get_active():
|
||||
args.append('--toc')
|
||||
if self.builder.get_object("normalize").get_active():
|
||||
args.append('--normalize')
|
||||
if self.builder.get_object("smart").get_active():
|
||||
args.append('--smart')
|
||||
|
||||
if self.builder.get_object("highlight").get_active == False:
|
||||
args.append('--no-highlight')
|
||||
else:
|
||||
hs = self.builder.get_object("highlight_style").get_active_text()
|
||||
args.append("--highlight-style=%s" % hs)
|
||||
|
||||
if self.builder.get_object("standalone").get_active():
|
||||
args.append("--standalone")
|
||||
|
||||
if self.builder.get_object("number_sections").get_active():
|
||||
args.append("--number-sections")
|
||||
|
||||
if self.builder.get_object("strict").get_active():
|
||||
args.append("--strict")
|
||||
|
||||
if self.builder.get_object("incremental").get_active():
|
||||
args.append("--incremental")
|
||||
|
||||
if self.builder.get_object("self_contained").get_active():
|
||||
args.append("--self-contained")
|
||||
|
||||
if self.builder.get_object("html5").get_active():
|
||||
if fmt["to"] == "html":
|
||||
to = "--to=%s" % "html5"
|
||||
|
||||
# Pandoc can't handle verbose -t pdf (#571)
|
||||
|
||||
if fmt["to"] != "pdf":
|
||||
args.append(to)
|
||||
|
||||
css_uri = self.builder.get_object("css_filechooser").get_uri()
|
||||
if css_uri:
|
||||
if css_uri.startswith("file://"):
|
||||
css_uri = css_uri[7:]
|
||||
args.append("--css=%s" % css_uri)
|
||||
|
||||
bib_uri = self.builder.get_object("bib_filechooser").get_uri()
|
||||
if bib_uri:
|
||||
if bib_uri.startswith("file://"):
|
||||
bib_uri = bib_uri[7:]
|
||||
args.append("--bibliography=%s" % bib_uri)
|
||||
|
||||
|
||||
args.append(output_file)
|
||||
|
||||
logger.info(args)
|
||||
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, cwd=output_dir)
|
||||
|
||||
output = p.communicate(text)[0]
|
||||
|
||||
return filename
|
|
@ -1,288 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import re
|
||||
import http.client
|
||||
import urllib
|
||||
from urllib.error import URLError, HTTPError
|
||||
import webbrowser
|
||||
import locale
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import threading
|
||||
|
||||
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
|
||||
from uberwriter_lib import LatexToPNG
|
||||
|
||||
from .FixTable import FixTable
|
||||
|
||||
from .MarkupBuffer import MarkupBuffer
|
||||
|
||||
from locale import gettext as _
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
GObject.threads_init() # Still needed?
|
||||
|
||||
# TODO:
|
||||
# - Don't insert a span with id, it breaks the text to often
|
||||
# Would be better to search for the nearest title and generate
|
||||
# A jumping URL from that (for preview)
|
||||
# Also, after going to preview, set cursor back to where it was
|
||||
|
||||
def check_url(url, item, spinner):
|
||||
logger.debug("thread started, checking url")
|
||||
error = False
|
||||
try:
|
||||
response = urllib.request.urlopen(url)
|
||||
except URLError as e:
|
||||
error = True
|
||||
text = "Error! Reason: %s" % e.reason
|
||||
|
||||
if not error:
|
||||
if (response.code / 100) >= 4:
|
||||
logger.debug("Website not available")
|
||||
text = _("Website is not available")
|
||||
else:
|
||||
text = _("Website is available")
|
||||
logger.debug("Response: %s" % text)
|
||||
spinner.destroy()
|
||||
item.set_label(text)
|
||||
|
||||
|
||||
def get_web_thumbnail(url, item, spinner):
|
||||
logger.debug("thread started, generating thumb")
|
||||
|
||||
# error = False
|
||||
|
||||
filename = tempfile.mktemp(suffix='.png')
|
||||
thumb_size = '256' # size can only be 32, 64, 96, 128 or 256!
|
||||
args = ['gnome-web-photo', '--mode=thumbnail', '-s', thumb_size, url, filename]
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
output = p.communicate()[0]
|
||||
|
||||
image = Gtk.Image.new_from_file(filename)
|
||||
image.show()
|
||||
|
||||
# if not error:
|
||||
# if (response.code / 100) >= 4:
|
||||
# logger.debug("Website not available")
|
||||
# text = _("Website is not available")
|
||||
# else:
|
||||
# text = _("Website is available")
|
||||
|
||||
spinner.destroy()
|
||||
item.add(image)
|
||||
item.show()
|
||||
|
||||
|
||||
class UberwriterInlinePreview():
|
||||
|
||||
def __init__(self, view, text_buffer):
|
||||
self.TextView = view
|
||||
self.TextBuffer = text_buffer
|
||||
self.LatexConverter = LatexToPNG.LatexToPNG()
|
||||
cursor_mark = self.TextBuffer.get_insert()
|
||||
cursor_iter = self.TextBuffer.get_iter_at_mark(cursor_mark)
|
||||
self.ClickMark = self.TextBuffer.create_mark('click', cursor_iter)
|
||||
# Events for popup menu
|
||||
self.TextView.connect_after('populate-popup', self.populate_popup)
|
||||
self.TextView.connect_after('popup-menu', self.move_popup)
|
||||
self.TextView.connect('button-press-event', self.click_move_button)
|
||||
|
||||
def click_move_button(self, widget, event):
|
||||
if event.button == 3:
|
||||
x, y = self.TextView.window_to_buffer_coords(2,
|
||||
int(event.x),
|
||||
int(event.y))
|
||||
self.TextBuffer.move_mark(self.ClickMark,
|
||||
self.TextView.get_iter_at_location(x, y))
|
||||
|
||||
def fix_table(self, widget, data=None):
|
||||
logger.debug('fixing that table')
|
||||
f = FixTable(self.TextBuffer)
|
||||
f.fix_table()
|
||||
|
||||
def populate_popup(self, editor, menu, data=None):
|
||||
|
||||
item = Gtk.MenuItem.new()
|
||||
item.set_name("PreviewMenuItem")
|
||||
separator = Gtk.SeparatorMenuItem.new()
|
||||
|
||||
table_item = Gtk.MenuItem.new()
|
||||
table_item.set_label('Fix that table')
|
||||
|
||||
table_item.connect('activate', self.fix_table)
|
||||
table_item.show()
|
||||
menu.prepend(table_item)
|
||||
menu.show()
|
||||
|
||||
start_iter = self.TextBuffer.get_iter_at_mark(self.ClickMark)
|
||||
# Line offset of click mark
|
||||
line_offset = start_iter.get_line_offset()
|
||||
end_iter = start_iter.copy()
|
||||
start_iter.set_line_offset(0)
|
||||
end_iter.forward_to_line_end()
|
||||
|
||||
text = self.TextBuffer.get_text(start_iter, end_iter, False)
|
||||
|
||||
math = MarkupBuffer.regex["MATH"]
|
||||
link = MarkupBuffer.regex["LINK"]
|
||||
|
||||
footnote = re.compile('\[\^([^\s]+?)\]')
|
||||
image = re.compile("!\[(.+?)\]\((.+?)\)")
|
||||
|
||||
buf = self.TextBuffer
|
||||
context_offset = 0
|
||||
|
||||
matchlist = []
|
||||
|
||||
found_match = False
|
||||
|
||||
matches = re.finditer(math, text)
|
||||
for match in matches:
|
||||
logger.debug(match.group(1))
|
||||
if match.start() < line_offset and match.end() > line_offset:
|
||||
success, result = self.LatexConverter.generatepng(match.group(1))
|
||||
if success:
|
||||
image = Gtk.Image.new_from_file(result)
|
||||
image.show()
|
||||
item.add(image)
|
||||
item.set_property('width-request', 50)
|
||||
else:
|
||||
label = Gtk.Label()
|
||||
msg = 'Formula looks incorrect:\n' + result
|
||||
label.set_alignment(0.0, 0.5)
|
||||
label.set_text(msg)
|
||||
label.show()
|
||||
item.add(label)
|
||||
item.show()
|
||||
menu.prepend(separator)
|
||||
separator.show()
|
||||
menu.prepend(item)
|
||||
menu.show()
|
||||
found_match = True
|
||||
break
|
||||
|
||||
if not found_match:
|
||||
# Links
|
||||
matches = re.finditer(link, text)
|
||||
for match in matches:
|
||||
if match.start() < line_offset and match.end() > line_offset:
|
||||
text = text[text.find("http://"):-1]
|
||||
|
||||
item.connect("activate", lambda w: webbrowser.open(text))
|
||||
|
||||
logger.debug(text)
|
||||
|
||||
statusitem = Gtk.MenuItem.new()
|
||||
statusitem.show()
|
||||
|
||||
spinner = Gtk.Spinner.new()
|
||||
spinner.start()
|
||||
statusitem.add(spinner)
|
||||
spinner.show()
|
||||
|
||||
thread = threading.Thread(target=check_url,
|
||||
args=(text, statusitem, spinner))
|
||||
thread.start()
|
||||
|
||||
webphoto_item = Gtk.MenuItem.new()
|
||||
webphoto_item.show()
|
||||
spinner_2 = Gtk.Spinner.new()
|
||||
spinner_2.start()
|
||||
webphoto_item.add(spinner_2)
|
||||
spinner_2.show()
|
||||
|
||||
thread_image = threading.Thread(target=get_web_thumbnail,
|
||||
args=(text, webphoto_item, spinner_2))
|
||||
|
||||
thread_image.start()
|
||||
|
||||
item.set_label(_("Open Link in Webbrowser"))
|
||||
item.show()
|
||||
|
||||
menu.prepend(separator)
|
||||
separator.show()
|
||||
|
||||
menu.prepend(webphoto_item)
|
||||
menu.prepend(statusitem)
|
||||
menu.prepend(item)
|
||||
menu.show()
|
||||
|
||||
|
||||
found_match = True
|
||||
break
|
||||
|
||||
if not found_match:
|
||||
matches = re.finditer(image, text)
|
||||
for match in matches:
|
||||
if match.start() < line_offset and match.end() > line_offset:
|
||||
path = match.group(2)
|
||||
if path.startswith("file://"):
|
||||
path = path[7:]
|
||||
logger.info(path)
|
||||
pb = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 400, 300)
|
||||
image = Gtk.Image.new_from_pixbuf(pb)
|
||||
image.show()
|
||||
item.add(image)
|
||||
item.set_property('width-request', 50)
|
||||
item.show()
|
||||
menu.prepend(separator)
|
||||
separator.show()
|
||||
menu.prepend(item)
|
||||
menu.show()
|
||||
found_match = True
|
||||
break
|
||||
|
||||
if not found_match:
|
||||
matches = re.finditer(footnote, text)
|
||||
for match in matches:
|
||||
if match.start() < line_offset and match.end() > line_offset:
|
||||
logger.debug(match.group(1))
|
||||
footnote_match = re.compile("\[\^" + match.group(1) + "\]: (.+(?:\n|\Z)(?:^[\t].+(?:\n|\Z))*)", re.MULTILINE)
|
||||
replace = re.compile("^\t", re.MULTILINE)
|
||||
start, end = self.TextBuffer.get_bounds()
|
||||
fn_match = re.search(footnote_match, self.TextBuffer.get_text(start, end, False))
|
||||
label = Gtk.Label()
|
||||
label.set_alignment(0.0, 0.5)
|
||||
logger.debug(fn_match)
|
||||
if fn_match:
|
||||
result = re.sub(replace, "", fn_match.group(1))
|
||||
if result.endswith("\n"):
|
||||
result = result[:-1]
|
||||
else:
|
||||
result = _("No matching footnote found")
|
||||
label.set_max_width_chars(40)
|
||||
label.set_line_wrap(True)
|
||||
label.set_text(result)
|
||||
label.show()
|
||||
item.add(label)
|
||||
item.show()
|
||||
|
||||
menu.prepend(separator)
|
||||
separator.show()
|
||||
menu.prepend(item)
|
||||
menu.show()
|
||||
found_match = True
|
||||
break
|
||||
return
|
||||
|
||||
def move_popup(self):
|
||||
pass
|
|
@ -1,150 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import os, re
|
||||
import subprocess
|
||||
from gi.repository import Gtk, Gdk
|
||||
import time
|
||||
# from plugins import plugins
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
class UberwriterSearchAndReplace():
|
||||
"""
|
||||
Adds (regex) search and replace functionality to
|
||||
uberwriter
|
||||
"""
|
||||
def __init__(self, parentwindow):
|
||||
self.parentwindow = parentwindow
|
||||
self.box = parentwindow.builder.get_object("searchreplaceholder")
|
||||
self.searchentry = parentwindow.builder.get_object("searchentrybox")
|
||||
self.searchentry.connect('changed', self.search)
|
||||
self.searchentry.connect('activate', self.scrolltonext)
|
||||
|
||||
self.open_replace_button = parentwindow.builder.get_object("replace")
|
||||
self.open_replace_button.connect("toggled", self.toggle_replace)
|
||||
|
||||
self.textbuffer = parentwindow.TextBuffer
|
||||
self.texteditor = parentwindow.TextEditor
|
||||
|
||||
self.nextbutton = parentwindow.builder.get_object("next_result")
|
||||
self.prevbutton = parentwindow.builder.get_object("previous_result")
|
||||
self.regexbutton = parentwindow.builder.get_object("regex")
|
||||
self.casesensitivebutton = parentwindow.builder.get_object("case_sensitive")
|
||||
|
||||
self.replacebox = parentwindow.builder.get_object("replacebox")
|
||||
self.replacebox.hide()
|
||||
self.replace_one_button = parentwindow.builder.get_object("replace_one")
|
||||
self.replace_all_button = parentwindow.builder.get_object("replace_all")
|
||||
self.replaceentry = parentwindow.builder.get_object("replaceentrybox")
|
||||
|
||||
self.replace_all_button.connect('clicked', self.replace_all)
|
||||
self.replace_one_button.connect('clicked', self.replace_clicked)
|
||||
self.replaceentry.connect('activate', self.replace_clicked)
|
||||
|
||||
self.nextbutton.connect('clicked', self.scrolltonext)
|
||||
self.prevbutton.connect('clicked', self.scrolltoprev)
|
||||
self.regexbutton.connect('toggled', self.search)
|
||||
self.casesensitivebutton.connect('toggled', self.search)
|
||||
self.highlight = self.textbuffer.create_tag('search_highlight',
|
||||
background="yellow")
|
||||
|
||||
def toggle_replace(self, widget, data=None):
|
||||
if widget.get_active():
|
||||
self.replacebox.show_all()
|
||||
else:
|
||||
self.replacebox.hide()
|
||||
|
||||
def toggle_search(self, widget=None, data=None):
|
||||
"""
|
||||
show search box
|
||||
"""
|
||||
if self.box.get_visible():
|
||||
self.box.hide()
|
||||
else:
|
||||
self.box.show()
|
||||
|
||||
def search(self, widget=None, data=None, scroll=True):
|
||||
searchtext = self.searchentry.get_text()
|
||||
buf = self.textbuffer
|
||||
context_start = buf.get_start_iter()
|
||||
context_end = buf.get_end_iter()
|
||||
text = buf.get_slice(context_start, context_end, False)
|
||||
|
||||
self.textbuffer.remove_tag(self.highlight, context_start, context_end)
|
||||
|
||||
# case sensitive?
|
||||
flags = False
|
||||
if not self.casesensitivebutton.get_active():
|
||||
flags = flags | re.I
|
||||
|
||||
# regex?
|
||||
if not self.regexbutton.get_active():
|
||||
searchtext = re.escape(searchtext)
|
||||
|
||||
matches = re.finditer(searchtext, text, flags)
|
||||
|
||||
self.matchiters = []
|
||||
self.active = 0
|
||||
for match in matches:
|
||||
startIter = buf.get_iter_at_offset(match.start())
|
||||
endIter = buf.get_iter_at_offset(match.end())
|
||||
self.matchiters.append((startIter, endIter))
|
||||
self.textbuffer.apply_tag(self.highlight, startIter, endIter)
|
||||
if scroll:
|
||||
self.scrollto(self.active)
|
||||
logger.debug(searchtext)
|
||||
|
||||
def scrolltonext(self, widget, data=None):
|
||||
self.scrollto(self.active + 1)
|
||||
|
||||
def scrolltoprev(self, widget, data=None):
|
||||
self.scrollto(self.active - 1)
|
||||
|
||||
def scrollto(self, index):
|
||||
if not len(self.matchiters):
|
||||
return
|
||||
if(index < len(self.matchiters)):
|
||||
self.active = index
|
||||
else:
|
||||
self.active = 0
|
||||
|
||||
matchiter = self.matchiters[self.active]
|
||||
self.texteditor.scroll_to_iter(matchiter[0], 0.0, True, 0.0, 0.5)
|
||||
|
||||
def hide(self):
|
||||
self.box.hide()
|
||||
|
||||
def replace_clicked(self, widget, data=None):
|
||||
self.replace(self.active)
|
||||
|
||||
def replace_all(self, widget=None, data=None):
|
||||
while self.matchiters:
|
||||
match = self.matchiters[0]
|
||||
self.textbuffer.delete(match[0], match[1])
|
||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
||||
self.search(scroll=False)
|
||||
|
||||
def replace(self, searchindex, inloop=False):
|
||||
match = self.matchiters[searchindex]
|
||||
self.textbuffer.delete(match[0], match[1])
|
||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
||||
active = self.active
|
||||
self.search(scroll=False)
|
||||
self.active = active
|
||||
self.parentwindow.MarkupBuffer.markup_buffer()
|
||||
self.scrollto(self.active)
|
|
@ -1,176 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
# from plugins import plugins
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
class Shelve():
|
||||
"""
|
||||
Shelve holds a collection of folders
|
||||
folders:
|
||||
List of folders
|
||||
name:
|
||||
descriptive name of shelve e.g. blog, notes etc.
|
||||
"""
|
||||
|
||||
name = ""
|
||||
folders = []
|
||||
|
||||
def __init__(self, name, folders):
|
||||
self.name = name
|
||||
self.folders = folders
|
||||
|
||||
def get_tree(self, store):
|
||||
node = {}
|
||||
for folder in self.folders:
|
||||
node[folder] = store.append(None, [os.path.basename(folder), folder])
|
||||
for root, dirs, files in os.walk(folder):
|
||||
logger.debug(root)
|
||||
for directory in dirs:
|
||||
node[root + "/" + directory] = store.append(node[root], [directory, root + "/" + directory])
|
||||
for filename in files:
|
||||
store.append(node[root], [filename, root + "/" + filename])
|
||||
|
||||
|
||||
class UberwriterSidebar():
|
||||
"""
|
||||
Presentational class for shelves and files managed by the "sidebar"
|
||||
|
||||
parentwindow:
|
||||
Reference to UberwriterWindow
|
||||
"""
|
||||
def __init__(self, parentwindow):
|
||||
"""
|
||||
Initialize Treeview and Treestore
|
||||
"""
|
||||
|
||||
self.parentwindow = parentwindow
|
||||
self.paned_window = parentwindow.paned_window
|
||||
self.sidebar_box = parentwindow.sidebar_box
|
||||
|
||||
# (GtkBox *box,
|
||||
# GtkWidget *child,
|
||||
# gboolean expand,
|
||||
# gboolean fill,
|
||||
# guint padding);
|
||||
|
||||
self.shelve_store = Gtk.ListStore(str)
|
||||
self.shelve_store.append(["testshelve"])
|
||||
self.shelves_dropdown = Gtk.ComboBox.new_with_model_and_entry(self.shelve_store)
|
||||
|
||||
self.sidebar_box.pack_start(self.shelves_dropdown, False, False, 5)
|
||||
|
||||
self.sidebar_scrolledwindow = Gtk.ScrolledWindow()
|
||||
self.sidebar_scrolledwindow.set_hexpand(True)
|
||||
self.sidebar_scrolledwindow.set_vexpand(True)
|
||||
|
||||
self.store = Gtk.TreeStore(str, str)
|
||||
self.active_shelf = Shelve("testshelve", ["/home/wolf/Documents/Texte"])
|
||||
self.active_shelf.get_tree(self.store)
|
||||
|
||||
self.treeview = Gtk.TreeView(self.store)
|
||||
self.treeview.set_headers_visible(False)
|
||||
# expand first folder (root folder, but not children)
|
||||
self.treeview.expand_row(Gtk.TreePath.new_from_string("0"), False)
|
||||
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn("Title", renderer, text=0)
|
||||
self.treeview.append_column(column)
|
||||
# new selection
|
||||
self.treeview.connect('cursor_changed', self.get_selected_file)
|
||||
# right click handler
|
||||
self.treeview.connect('button-press-event', self.handle_button_press)
|
||||
self.treeview.show()
|
||||
|
||||
self.sidebar_scrolledwindow.add(self.treeview)
|
||||
self.sidebar_box.pack_start(self.sidebar_scrolledwindow, True, True, 5)
|
||||
|
||||
self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
self.menu_button = Gtk.MenuButton.new()
|
||||
|
||||
# TODO refactor
|
||||
mb_menu = Gtk.Menu.new()
|
||||
mitem = Gtk.MenuItem.new_with_label('etstasd asd as d')
|
||||
mitem.show()
|
||||
mb_menu.append(mitem)
|
||||
mb_menu.show()
|
||||
|
||||
self.menu_button.set_popup(mb_menu)
|
||||
|
||||
self.menu_box.pack_start(self.menu_button, False, False, 5)
|
||||
self.sidebar_box.pack_end(self.menu_box, False, False, 5)
|
||||
|
||||
self.sidebar_box.show_all()
|
||||
self.paned_window.pack1(self.sidebar_box, True, True);
|
||||
self.paned_window.show_all()
|
||||
|
||||
|
||||
def get_selected_file(self, widget, data=None):
|
||||
"""
|
||||
Handle left click on file
|
||||
"""
|
||||
selection = self.treeview.get_selection()
|
||||
if not selection:
|
||||
return
|
||||
selection.set_mode(Gtk.SelectionMode.SINGLE)
|
||||
treemodel, treeiter = selection.get_selected()
|
||||
selected_file = treemodel.get_value(treeiter, 1)
|
||||
self.parentwindow.load_file(selected_file)
|
||||
logger.debug(selected_file)
|
||||
|
||||
def handle_button_press(self, widget, event):
|
||||
"""
|
||||
Handle right click (context menu)
|
||||
"""
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
||||
# reference to self to not have it garbage collected
|
||||
self.popup = Gtk.Menu.new()
|
||||
pathinfo = self.treeview.get_path_at_pos(event.x, event.y)
|
||||
if pathinfo:
|
||||
path, col, cellx, celly = pathinfo
|
||||
treeiter = self.store.get_iter(path)
|
||||
filename = self.store.get_value(treeiter, 1)
|
||||
item = Gtk.MenuItem.new()
|
||||
item.set_label("Open ...")
|
||||
item.connect("activate", self.context_menu_open_file)
|
||||
item.filename = filename
|
||||
item.show()
|
||||
self.popup.append(item)
|
||||
self.popup.show()
|
||||
self.popup.popup(None, None, None, None, event.button, event.time)
|
||||
return True
|
||||
|
||||
def get_treeview(self):
|
||||
"""
|
||||
Return Treeview to append to scrolled window
|
||||
"""
|
||||
return self.treeview
|
||||
|
||||
def context_menu_open_file(self, widget, data=None):
|
||||
"""
|
||||
Open selected file with xdg-open
|
||||
"""
|
||||
selected_file = widget.filename
|
||||
subprocess.call(["xdg-open", selected_file])
|
||||
|
||||
def close_sidebar(self):
|
||||
pass
|
|
@ -1,440 +0,0 @@
|
|||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
"""Module for the TextView widgth wich encapsulates management of TextBuffer
|
||||
and TextIter for common functionality, such as cut, copy, paste, undo, redo,
|
||||
and highlighting of text.
|
||||
|
||||
Using
|
||||
#create the TextEditor and set the text
|
||||
editor = TextEditor()
|
||||
editor.text = "Text to add to the editor"
|
||||
|
||||
#use cut, works the same for copy, paste, undo, and redo
|
||||
def __handle_on_cut(self, widget, data=None):
|
||||
self.editor.cut()
|
||||
|
||||
#add string to highlight
|
||||
self.editor.add_highlight("Ubuntu")
|
||||
self.editor.add_highlight("Quickly")
|
||||
|
||||
#remove highlights
|
||||
self.editor.clear_highlight("Ubuntu")
|
||||
self.editor.clear_all_highlight()
|
||||
|
||||
Configuring
|
||||
#Configure as a TextView
|
||||
self.editor.set_wrap_mode(Gtk.WRAP_CHAR)
|
||||
|
||||
#Access the Gtk.TextBuffer if needed
|
||||
buffer = self.editor.get_buffer()
|
||||
|
||||
Extending
|
||||
A TextEditor is Gtk.TextView
|
||||
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
except:
|
||||
print("couldn't load depencies")
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
|
||||
class UndoableInsert(object):
|
||||
"""something that has been inserted into our textbuffer"""
|
||||
def __init__(self, text_iter, text, length, fflines):
|
||||
self.offset = text_iter.get_offset() - fflines
|
||||
self.text = text
|
||||
self.length = length
|
||||
if self.length > 1 or self.text in ("\r", "\n", " "):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
|
||||
|
||||
class UndoableDelete(object):
|
||||
"""something that has ben deleted from our textbuffer"""
|
||||
def __init__(self, text_buffer, start_iter, end_iter, fflines):
|
||||
self.text = text_buffer.get_text(start_iter, end_iter, False)
|
||||
self.start = start_iter.get_offset() - fflines
|
||||
self.end = end_iter.get_offset() - fflines
|
||||
# need to find out if backspace or delete key has been used
|
||||
# so we don't mess up during redo
|
||||
insert_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
||||
if insert_iter.get_offset() <= self.start:
|
||||
self.delete_key_used = True
|
||||
else:
|
||||
self.delete_key_used = False
|
||||
if self.end - self.start > 1 or self.text in ("\r", "\n", " "):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
|
||||
|
||||
class TextEditor(Gtk.TextView):
|
||||
"""TextEditor encapsulates management of TextBuffer and TextIter for
|
||||
common functionality, such as cut, copy, paste, undo, redo, and
|
||||
highlighting of text.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Create a TextEditor
|
||||
|
||||
"""
|
||||
|
||||
Gtk.TextView.__init__(self)
|
||||
self.undo_max = None
|
||||
|
||||
self.insert_event = self.get_buffer().connect("insert-text", self.on_insert_text)
|
||||
self.delete_event = self.get_buffer().connect("delete-range", self.on_delete_range)
|
||||
display = self.get_display()
|
||||
self.clipboard = Gtk.Clipboard.get_for_display(display, Gdk.SELECTION_CLIPBOARD)
|
||||
|
||||
self.fflines = 0
|
||||
|
||||
self.undo_stack = []
|
||||
self.redo_stack = []
|
||||
self.not_undoable_action = False
|
||||
self.undo_in_progress = False
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
text - a string specifying all the text currently
|
||||
in the TextEditor's buffer.
|
||||
|
||||
This property is read/write.
|
||||
"""
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
return self.get_buffer().get_text(start_iter, end_iter, False)
|
||||
|
||||
@property
|
||||
def can_undo(self):
|
||||
return bool(self.undo_stack)
|
||||
|
||||
@property
|
||||
def can_redo(self):
|
||||
return bool(self.redo_stack)
|
||||
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self.get_buffer().set_text(text)
|
||||
|
||||
def append(self, text):
|
||||
"""append: appends text to the end of the textbuffer.
|
||||
|
||||
arguments:
|
||||
text - a string to add to the buffer. The text will be the
|
||||
last text in the buffer. The insertion cursor will not be moved.
|
||||
|
||||
"""
|
||||
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
self.get_buffer().insert(end_iter, text)
|
||||
|
||||
def prepend(self, text):
|
||||
"""prepend: appends text to the start of the textbuffer.
|
||||
|
||||
arguments:
|
||||
text - a string to add to the buffer. The text will be the
|
||||
first text in the buffer. The insertion cursor will not be moved.
|
||||
|
||||
"""
|
||||
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
self.get_buffer().insert(start_iter, text)
|
||||
insert_iter = self.get_buffer().get_iter_at_offset(len(text)-1)
|
||||
self.get_buffer().place_cursor(insert_iter)
|
||||
|
||||
def cursor_to_end(self):
|
||||
"""cursor_to_end: moves the insertion curson to the last position
|
||||
in the buffer.
|
||||
|
||||
"""
|
||||
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
self.get_buffer().place_cursor(end_iter)
|
||||
|
||||
def cursor_to_start(self):
|
||||
"""cursor_to_start: moves the insertion curson to the first position
|
||||
in the buffer.
|
||||
|
||||
"""
|
||||
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
self.get_buffer().place_cursor(start_iter)
|
||||
|
||||
def cut(self, widget=None, data=None):
|
||||
"""cut: cut currently selected text and put it on the clipboard.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
|
||||
"""
|
||||
|
||||
self.get_buffer().cut_clipboard(self.clipboard, True)
|
||||
|
||||
def copy(self, widget=None, data=None):
|
||||
"""copy: copy currently selected text to the clipboard.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
|
||||
"""
|
||||
|
||||
self.get_buffer().copy_clipboard(self.clipboard)
|
||||
|
||||
def paste(self, widget=None, data=None):
|
||||
"""paste: Insert any text currently on the clipboard into the
|
||||
buffer.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
|
||||
"""
|
||||
|
||||
self.get_buffer().paste_clipboard(self.clipboard,None,True)
|
||||
|
||||
def undo(self, widget=None, data=None):
|
||||
"""undo inserts or deletions
|
||||
undone actions are being moved to redo stack"""
|
||||
if not self.undo_stack:
|
||||
return
|
||||
self.begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
undo_action = self.undo_stack.pop()
|
||||
self.redo_stack.append(undo_action)
|
||||
buf = self.get_buffer()
|
||||
if isinstance(undo_action, UndoableInsert):
|
||||
offset = undo_action.offset + self.fflines
|
||||
start = buf.get_iter_at_offset(offset)
|
||||
stop = buf.get_iter_at_offset(
|
||||
offset + undo_action.length
|
||||
)
|
||||
buf.delete(start, stop)
|
||||
buf.place_cursor(start)
|
||||
else:
|
||||
start = buf.get_iter_at_offset(undo_action.start + self.fflines)
|
||||
buf.insert(start, undo_action.text)
|
||||
if undo_action.delete_key_used:
|
||||
buf.place_cursor(start)
|
||||
else:
|
||||
stop = buf.get_iter_at_offset(undo_action.end + self.fflines)
|
||||
buf.place_cursor(stop)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def redo(self, widget=None, data=None):
|
||||
"""redo inserts or deletions
|
||||
|
||||
redone actions are moved to undo stack"""
|
||||
if not self.redo_stack:
|
||||
return
|
||||
self.begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
redo_action = self.redo_stack.pop()
|
||||
self.undo_stack.append(redo_action)
|
||||
buf = self.get_buffer()
|
||||
if isinstance(redo_action, UndoableInsert):
|
||||
start = buf.get_iter_at_offset(redo_action.offset)
|
||||
buf.insert(start, redo_action.text)
|
||||
new_cursor_pos = buf.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
buf.place_cursor(new_cursor_pos)
|
||||
else:
|
||||
start = buf.get_iter_at_offset(redo_action.start)
|
||||
stop = buf.get_iter_at_offset(redo_action.end)
|
||||
buf.delete(start, stop)
|
||||
buf.place_cursor(start)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def on_insert_text(self, textbuffer, text_iter, text, length):
|
||||
"""
|
||||
_on_insert: internal function to handle programatically inserted
|
||||
text. Do not call directly.
|
||||
"""
|
||||
def can_be_merged(prev, cur):
|
||||
"""see if we can merge multiple inserts here
|
||||
|
||||
will try to merge words or whitespace
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge when user set the input bar somewhere else
|
||||
can't merge across word boundaries"""
|
||||
WHITESPACE = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
elif cur.offset != (prev.offset + prev.length):
|
||||
return False
|
||||
elif cur.text in WHITESPACE and not prev.text in WHITESPACE:
|
||||
return False
|
||||
elif prev.text in WHITESPACE and not cur.text in WHITESPACE:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
|
||||
logger.debug(text)
|
||||
logger.debug("b: %i, l: %i" % (length, len(text)))
|
||||
undo_action = UndoableInsert(text_iter, text, len(text), self.fflines)
|
||||
try:
|
||||
prev_insert = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_insert, UndoableInsert):
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_insert, undo_action):
|
||||
prev_insert.length += undo_action.length
|
||||
prev_insert.text += undo_action.text
|
||||
self.undo_stack.append(prev_insert)
|
||||
else:
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def on_delete_range(self, text_buffer, start_iter, end_iter):
|
||||
"""
|
||||
On delete
|
||||
"""
|
||||
def can_be_merged(prev, cur):
|
||||
"""see if we can merge multiple deletions here
|
||||
|
||||
will try to merge words or whitespace
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge if delete and backspace key were both used
|
||||
can't merge across word boundaries"""
|
||||
|
||||
WHITESPACE = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
elif prev.delete_key_used != cur.delete_key_used:
|
||||
return False
|
||||
elif prev.start != cur.start and prev.start != cur.end:
|
||||
return False
|
||||
elif cur.text not in WHITESPACE and \
|
||||
prev.text in WHITESPACE:
|
||||
return False
|
||||
elif cur.text in WHITESPACE and \
|
||||
prev.text not in WHITESPACE:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
undo_action = UndoableDelete(text_buffer, start_iter, end_iter, self.fflines)
|
||||
try:
|
||||
prev_delete = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_delete, UndoableDelete):
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_delete, undo_action):
|
||||
if prev_delete.start == undo_action.start: # delete key used
|
||||
prev_delete.text += undo_action.text
|
||||
prev_delete.end += (undo_action.end - undo_action.start)
|
||||
else: # Backspace used
|
||||
prev_delete.text = "%s%s" % (undo_action.text,
|
||||
prev_delete.text)
|
||||
prev_delete.start = undo_action.start
|
||||
self.undo_stack.append(prev_delete)
|
||||
else:
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def begin_not_undoable_action(self):
|
||||
"""don't record the next actions
|
||||
|
||||
toggles self.not_undoable_action"""
|
||||
self.not_undoable_action = True
|
||||
|
||||
def end_not_undoable_action(self):
|
||||
"""record next actions
|
||||
|
||||
toggles self.not_undoable_action"""
|
||||
self.not_undoable_action = False
|
||||
|
||||
class TestWindow(Gtk.Window):
|
||||
"""For testing and demonstrating AsycnTaskProgressBox.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
#create a window a VBox to hold the controls
|
||||
Gtk.Window.__init__(self)
|
||||
self.set_title("TextEditor Test Window")
|
||||
windowbox = Gtk.VBox(False, 2)
|
||||
windowbox.show()
|
||||
self.add(windowbox)
|
||||
self.editor = TextEditor()
|
||||
self.editor.show()
|
||||
windowbox.pack_end(self.editor, True, True, 0)
|
||||
self.set_size_request(200,200)
|
||||
self.show()
|
||||
self.maximize()
|
||||
|
||||
self.connect("destroy", Gtk.main_quit)
|
||||
self.editor.text = "this is some inserted text"
|
||||
self.editor.append("\nLine 3")
|
||||
self.editor.prepend("Line1\n")
|
||||
self.editor.cursor_to_end()
|
||||
self.editor.cursor_to_start()
|
||||
self.editor.undo_max = 100
|
||||
cut_button = Gtk.Button("Cut")
|
||||
cut_button.connect("clicked",self.editor.cut)
|
||||
cut_button.show()
|
||||
windowbox.pack_start(cut_button, False, False, 0)
|
||||
|
||||
copy_button = Gtk.Button("Copy")
|
||||
copy_button.connect("clicked",self.editor.copy)
|
||||
copy_button.show()
|
||||
windowbox.pack_start(copy_button, False, False, 0)
|
||||
|
||||
paste_button = Gtk.Button("Paste")
|
||||
paste_button.connect("clicked",self.editor.paste)
|
||||
paste_button.show()
|
||||
windowbox.pack_start(paste_button, False, False, 0)
|
||||
|
||||
undo_button = Gtk.Button("Undo")
|
||||
undo_button.connect("clicked",self.editor.undo)
|
||||
undo_button.show()
|
||||
windowbox.pack_start(undo_button, False, False, 0)
|
||||
|
||||
redo_button = Gtk.Button("Redo")
|
||||
redo_button.connect("clicked",self.editor.redo)
|
||||
redo_button.show()
|
||||
windowbox.pack_start(redo_button, False, False, 0)
|
||||
|
||||
print(self.editor.text)
|
||||
|
||||
|
||||
if __name__== "__main__":
|
||||
test = TestWindow()
|
||||
Gtk.main()
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,56 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import optparse
|
||||
|
||||
import locale
|
||||
import os
|
||||
from locale import gettext as _
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from . import UberwriterWindow
|
||||
|
||||
from uberwriter_lib import set_up_logging, get_version
|
||||
|
||||
def parse_options():
|
||||
"""Support for command line options"""
|
||||
parser = optparse.OptionParser(version="%%prog %s" % get_version())
|
||||
parser.add_option(
|
||||
"-v", "--verbose", action="count", dest="verbose",
|
||||
help=_("Show debug messages (-vv debugs uberwriter_lib also)"))
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
set_up_logging(options)
|
||||
|
||||
# print args
|
||||
|
||||
return options, args
|
||||
|
||||
def main():
|
||||
'constructor for your class instances'
|
||||
(options, args) = parse_options()
|
||||
|
||||
# Run the application.
|
||||
if args:
|
||||
for arg in args:
|
||||
window = UberwriterWindow.UberwriterWindow()
|
||||
window.load_file(arg)
|
||||
else:
|
||||
window = UberwriterWindow.UberwriterWindow()
|
||||
window.show()
|
||||
Gtk.main()
|
|
@ -1,50 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from . helpers import get_builder
|
||||
|
||||
class AboutDialog(Gtk.AboutDialog):
|
||||
__gtype_name__ = "AboutDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated AboutDialog object.
|
||||
"""
|
||||
builder = get_builder('AboutUberwriterDialog')
|
||||
new_object = builder.get_object("about_uberwriter_dialog")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initalizing should be called after parsing the ui definition
|
||||
and creating a AboutDialog object with it in order
|
||||
to finish initializing the start of the new AboutUberwriterDialog
|
||||
instance.
|
||||
|
||||
Put your initialization code in here and leave __init__ undefined.
|
||||
"""
|
||||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self)
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from . helpers import get_builder
|
||||
|
||||
class AdvancedExportDialog(Gtk.Dialog):
|
||||
__gtype_name__ = "AdvancedExportDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated AdvancedExportDialog object.
|
||||
"""
|
||||
builder = get_builder('UberwriterAdvancedExportDialog')
|
||||
new_object = builder.get_object("uberwriter_advanced_export_dialog")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initalizing should be called after parsing the ui definition
|
||||
and creating a AdvancedExportDialog object with it in order
|
||||
to finish initializing the start of the new AboutUberwriterDialog
|
||||
instance.
|
||||
|
||||
Put your initialization code in here and leave __init__ undefined.
|
||||
"""
|
||||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self)
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
'''Enhances builder connections, provides object to access glade objects'''
|
||||
|
||||
from gi.repository import GObject, Gtk # pylint: disable=E0611
|
||||
|
||||
import inspect
|
||||
import functools
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter_lib')
|
||||
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
# this module is big so uses some conventional prefixes and postfixes
|
||||
# *s list, except self.widgets is a dictionary
|
||||
# *_dict dictionary
|
||||
# *name string
|
||||
# ele_* element in a ElementTree
|
||||
|
||||
|
||||
# pylint: disable=R0904
|
||||
# the many public methods is a feature of Gtk.Builder
|
||||
class Builder(Gtk.Builder):
|
||||
''' extra features
|
||||
connects glade defined handler to default_handler if necessary
|
||||
auto connects widget to handler with matching name or alias
|
||||
auto connects several widgets to a handler via multiple aliases
|
||||
allow handlers to lookup widget name
|
||||
logs every connection made, and any on_* not made
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
Gtk.Builder.__init__(self)
|
||||
self.widgets = {}
|
||||
self.glade_handler_dict = {}
|
||||
self.connections = []
|
||||
self._reverse_widget_dict = {}
|
||||
|
||||
# pylint: disable=R0201
|
||||
# this is a method so that a subclass of Builder can redefine it
|
||||
def default_handler(self,
|
||||
handler_name, filename, *args, **kwargs):
|
||||
'''helps the apprentice guru
|
||||
|
||||
glade defined handlers that do not exist come here instead.
|
||||
An apprentice guru might wonder which signal does what he wants,
|
||||
now he can define any likely candidates in glade and notice which
|
||||
ones get triggered when he plays with the project.
|
||||
this method does not appear in Gtk.Builder'''
|
||||
logger.debug('''tried to call non-existent function:%s()
|
||||
expected in %s
|
||||
args:%s
|
||||
kwargs:%s''', handler_name, filename, args, kwargs)
|
||||
# pylint: enable=R0201
|
||||
|
||||
def get_name(self, widget):
|
||||
''' allows a handler to get the name (id) of a widget
|
||||
|
||||
this method does not appear in Gtk.Builder'''
|
||||
return self._reverse_widget_dict.get(widget)
|
||||
|
||||
def add_from_file(self, filename):
|
||||
'''parses xml file and stores wanted details'''
|
||||
Gtk.Builder.add_from_file(self, filename)
|
||||
|
||||
# extract data for the extra interfaces
|
||||
tree = ElementTree()
|
||||
tree.parse(filename)
|
||||
|
||||
ele_widgets = tree.getiterator("object")
|
||||
for ele_widget in ele_widgets:
|
||||
name = ele_widget.attrib['id']
|
||||
widget = self.get_object(name)
|
||||
|
||||
# populate indexes - a dictionary of widgets
|
||||
self.widgets[name] = widget
|
||||
|
||||
# populate a reversed dictionary
|
||||
self._reverse_widget_dict[widget] = name
|
||||
|
||||
# populate connections list
|
||||
ele_signals = ele_widget.findall("signal")
|
||||
|
||||
connections = [
|
||||
(name,
|
||||
ele_signal.attrib['name'],
|
||||
ele_signal.attrib['handler']) for ele_signal in ele_signals]
|
||||
|
||||
if connections:
|
||||
self.connections.extend(connections)
|
||||
|
||||
ele_signals = tree.getiterator("signal")
|
||||
for ele_signal in ele_signals:
|
||||
self.glade_handler_dict.update(
|
||||
{ele_signal.attrib["handler"]: None})
|
||||
|
||||
def connect_signals(self, callback_obj):
|
||||
'''connect the handlers defined in glade
|
||||
|
||||
reports successful and failed connections
|
||||
and logs call to missing handlers'''
|
||||
filename = inspect.getfile(callback_obj.__class__)
|
||||
callback_handler_dict = dict_from_callback_obj(callback_obj)
|
||||
connection_dict = {}
|
||||
connection_dict.update(self.glade_handler_dict)
|
||||
connection_dict.update(callback_handler_dict)
|
||||
for item in connection_dict.items():
|
||||
if item[1] is None:
|
||||
# the handler is missing so reroute to default_handler
|
||||
handler = functools.partial(
|
||||
self.default_handler, item[0], filename)
|
||||
|
||||
connection_dict[item[0]] = handler
|
||||
|
||||
# replace the run time warning
|
||||
logger.warn("expected handler '%s' in %s",
|
||||
item[0], filename)
|
||||
|
||||
# connect glade define handlers
|
||||
Gtk.Builder.connect_signals(self, connection_dict)
|
||||
|
||||
# let's tell the user how we applied the glade design
|
||||
for connection in self.connections:
|
||||
widget_name, signal_name, handler_name = connection
|
||||
logger.debug("connect builder by design '%s', '%s', '%s'",
|
||||
widget_name, signal_name, handler_name)
|
||||
|
||||
def get_ui(self, callback_obj=None, by_name=True):
|
||||
'''Creates the ui object with widgets as attributes
|
||||
|
||||
connects signals by 2 methods
|
||||
this method does not appear in Gtk.Builder'''
|
||||
|
||||
result = UiFactory(self.widgets)
|
||||
|
||||
# Hook up any signals the user defined in glade
|
||||
if callback_obj is not None:
|
||||
# connect glade define handlers
|
||||
self.connect_signals(callback_obj)
|
||||
|
||||
if by_name:
|
||||
auto_connect_by_name(callback_obj, self)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# pylint: disable=R0903
|
||||
# this class deliberately does not provide any public interfaces
|
||||
# apart from the glade widgets
|
||||
class UiFactory():
|
||||
''' provides an object with attributes as glade widgets'''
|
||||
def __init__(self, widget_dict):
|
||||
self._widget_dict = widget_dict
|
||||
for (widget_name, widget) in widget_dict.items():
|
||||
setattr(self, widget_name, widget)
|
||||
|
||||
# Mangle any non-usable names (like with spaces or dashes)
|
||||
# into pythonic ones
|
||||
cannot_message = """cannot bind ui.%s, name already exists
|
||||
consider using a pythonic name instead of design name '%s'"""
|
||||
consider_message = """consider using a pythonic name instead of design name '%s'"""
|
||||
|
||||
for (widget_name, widget) in widget_dict.items():
|
||||
pyname = make_pyname(widget_name)
|
||||
if pyname != widget_name:
|
||||
if hasattr(self, pyname):
|
||||
logger.debug(cannot_message, pyname, widget_name)
|
||||
else:
|
||||
logger.debug(consider_message, widget_name)
|
||||
setattr(self, pyname, widget)
|
||||
|
||||
def iterator():
|
||||
'''Support 'for o in self' '''
|
||||
return iter(widget_dict.values())
|
||||
setattr(self, '__iter__', iterator)
|
||||
|
||||
def __getitem__(self, name):
|
||||
'access as dictionary where name might be non-pythonic'
|
||||
return self._widget_dict[name]
|
||||
# pylint: enable=R0903
|
||||
|
||||
|
||||
def make_pyname(name):
|
||||
''' mangles non-pythonic names into pythonic ones'''
|
||||
pyname = ''
|
||||
for character in name:
|
||||
if (character.isalpha() or character == '_' or
|
||||
(pyname and character.isdigit())):
|
||||
pyname += character
|
||||
else:
|
||||
pyname += '_'
|
||||
return pyname
|
||||
|
||||
|
||||
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
|
||||
# need to reimplement inspect.getmembers. GObject introspection doesn't
|
||||
# play nice with it.
|
||||
def getmembers(obj, check):
|
||||
members = []
|
||||
for k in dir(obj):
|
||||
try:
|
||||
attr = getattr(obj, k)
|
||||
except:
|
||||
continue
|
||||
if check(attr):
|
||||
members.append((k, attr))
|
||||
members.sort()
|
||||
return members
|
||||
|
||||
|
||||
def dict_from_callback_obj(callback_obj):
|
||||
'''a dictionary interface to callback_obj'''
|
||||
methods = getmembers(callback_obj, inspect.ismethod)
|
||||
|
||||
aliased_methods = [x[1] for x in methods if hasattr(x[1], 'aliases')]
|
||||
|
||||
# a method may have several aliases
|
||||
#~ @alias('on_btn_foo_clicked')
|
||||
#~ @alias('on_tool_foo_activate')
|
||||
#~ on_menu_foo_activate():
|
||||
#~ pass
|
||||
alias_groups = [(x.aliases, x) for x in aliased_methods]
|
||||
|
||||
aliases = []
|
||||
for item in alias_groups:
|
||||
for alias in item[0]:
|
||||
aliases.append((alias, item[1]))
|
||||
|
||||
dict_methods = dict(methods)
|
||||
dict_aliases = dict(aliases)
|
||||
|
||||
results = {}
|
||||
results.update(dict_methods)
|
||||
results.update(dict_aliases)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def auto_connect_by_name(callback_obj, builder):
|
||||
'''finds handlers like on_<widget_name>_<signal> and connects them
|
||||
|
||||
i.e. find widget,signal pair in builder and call
|
||||
widget.connect(signal, on_<widget_name>_<signal>)'''
|
||||
|
||||
callback_handler_dict = dict_from_callback_obj(callback_obj)
|
||||
|
||||
for item in builder.widgets.items():
|
||||
(widget_name, widget) = item
|
||||
signal_ids = []
|
||||
try:
|
||||
widget_type = type(widget)
|
||||
while widget_type:
|
||||
signal_ids.extend(GObject.signal_list_ids(widget_type))
|
||||
widget_type = GObject.type_parent(widget_type)
|
||||
except RuntimeError: # pylint wants a specific error
|
||||
pass
|
||||
signal_names = [GObject.signal_name(sid) for sid in signal_ids]
|
||||
|
||||
# Now, automatically find any the user didn't specify in glade
|
||||
for sig in signal_names:
|
||||
# using convention suggested by glade
|
||||
sig = sig.replace("-", "_")
|
||||
handler_names = ["on_%s_%s" % (widget_name, sig)]
|
||||
|
||||
# Using the convention that the top level window is not
|
||||
# specified in the handler name. That is use
|
||||
# on_destroy() instead of on_windowname_destroy()
|
||||
if widget is callback_obj:
|
||||
handler_names.append("on_%s" % sig)
|
||||
|
||||
do_connect(item, sig, handler_names,
|
||||
callback_handler_dict, builder.connections)
|
||||
|
||||
log_unconnected_functions(callback_handler_dict, builder.connections)
|
||||
|
||||
|
||||
def do_connect(item, signal_name, handler_names,
|
||||
callback_handler_dict, connections):
|
||||
'''connect this signal to an unused handler'''
|
||||
widget_name, widget = item
|
||||
|
||||
for handler_name in handler_names:
|
||||
target = handler_name in callback_handler_dict.keys()
|
||||
connection = (widget_name, signal_name, handler_name)
|
||||
duplicate = connection in connections
|
||||
if target and not duplicate:
|
||||
widget.connect(signal_name, callback_handler_dict[handler_name])
|
||||
connections.append(connection)
|
||||
|
||||
logger.debug("connect builder by name '%s','%s', '%s'",
|
||||
widget_name, signal_name, handler_name)
|
||||
|
||||
|
||||
def log_unconnected_functions(callback_handler_dict, connections):
|
||||
'''log functions like on_* that we could not connect'''
|
||||
|
||||
connected_functions = [x[2] for x in connections]
|
||||
|
||||
handler_names = callback_handler_dict.keys()
|
||||
unconnected = [x for x in handler_names if x.startswith('on_')]
|
||||
|
||||
for handler_name in connected_functions:
|
||||
try:
|
||||
unconnected.remove(handler_name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for handler_name in unconnected:
|
||||
logger.debug("Not connected to builder '%s'", handler_name)
|
|
@ -1,108 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
"""
|
||||
Based on latex2png.py from Stuart Rackham
|
||||
|
||||
AUTHOR
|
||||
Written by Stuart Rackham, <srackham@gmail.com>
|
||||
The code was inspired by Kjell Magne Fauske's code:
|
||||
http://fauskes.net/nb/htmleqII/
|
||||
|
||||
See also:
|
||||
http://www.amk.ca/python/code/mt-math
|
||||
http://code.google.com/p/latexmath2png/
|
||||
|
||||
COPYING
|
||||
Copyright (C) 2010 Stuart Rackham. Free use of this software is
|
||||
granted under the terms of the MIT License.
|
||||
"""
|
||||
|
||||
import os, sys, tempfile, subprocess
|
||||
|
||||
class LatexToPNG():
|
||||
|
||||
TEX_HEADER = r'''\documentclass{article}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amsthm}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{bm}
|
||||
\newcommand{\mx}[1]{\mathbf{\bm{#1}}} % Matrix command
|
||||
\newcommand{\vc}[1]{\mathbf{\bm{#1}}} % Vector command
|
||||
\newcommand{\T}{\text{T}} % Transpose
|
||||
\pagestyle{empty}
|
||||
\begin{document}'''
|
||||
|
||||
TEX_FOOTER = r'''\end{document}'''
|
||||
|
||||
def __init__(self):
|
||||
self.temp_result = tempfile.NamedTemporaryFile(suffix='.png')
|
||||
|
||||
def latex2png(self, tex, outfile, dpi, modified):
|
||||
'''Convert LaTeX input file infile to PNG file named outfile.'''
|
||||
outfile = os.path.abspath(outfile)
|
||||
outdir = os.path.dirname(outfile)
|
||||
texfile = tempfile.mktemp(suffix='.tex', dir=os.path.dirname(outfile))
|
||||
basefile = os.path.splitext(texfile)[0]
|
||||
dvifile = basefile + '.dvi'
|
||||
temps = [basefile + ext for ext in ('.tex','.dvi', '.aux', '.log')]
|
||||
skip = False
|
||||
|
||||
tex = '%s\n%s\n%s\n' % (self.TEX_HEADER, tex.strip(), self.TEX_FOOTER)
|
||||
|
||||
open(texfile, 'w').write(tex)
|
||||
saved_pwd = os.getcwd()
|
||||
|
||||
os.chdir(outdir)
|
||||
|
||||
args = ['latex', '-halt-on-error', texfile]
|
||||
p = subprocess.Popen(args,
|
||||
stderr=subprocess.STDOUT,
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
output = p.stdout
|
||||
output_lines = output.readlines()
|
||||
if os.path.isfile(dvifile): # DVI File exists
|
||||
# Convert DVI file to PNG.
|
||||
args = ['dvipng',
|
||||
'-D', str(dpi),
|
||||
'-T', 'tight',
|
||||
'-x', '1000',
|
||||
'-z', '9',
|
||||
'-bg', 'Transparent',
|
||||
'-o', outfile,
|
||||
dvifile]
|
||||
|
||||
p = subprocess.Popen(args)
|
||||
p.communicate()
|
||||
|
||||
else:
|
||||
self.clean_up(temps)
|
||||
'''
|
||||
Errors in Latex output start with "! "
|
||||
Stripping exclamation marks and superflous newlines
|
||||
and telling the user what he's done wrong.
|
||||
'''
|
||||
i = []
|
||||
error = ""
|
||||
for line in output_lines:
|
||||
line = line.decode('utf-8')
|
||||
if line.startswith("!"):
|
||||
error += line[2:] # removing "! "
|
||||
if error.endswith("\n"):
|
||||
error = error[:-1]
|
||||
raise Exception(error)
|
||||
|
||||
def generatepng(self, formula):
|
||||
try:
|
||||
self.temp_result = tempfile.NamedTemporaryFile(suffix='.png')
|
||||
formula = "$" + formula + "$"
|
||||
self.latex2png(formula, self.temp_result.name, 300, False)
|
||||
return (True, self.temp_result.name)
|
||||
|
||||
except Exception as e:
|
||||
self.temp_result.close()
|
||||
return (False, e.args[0])
|
||||
|
||||
def clean_up(self, files):
|
||||
for f in files:
|
||||
if os.path.isfile(f):
|
||||
os.remove(f)
|
|
@ -1,64 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
"""this dialog adjusts values in gsettings
|
||||
"""
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter_lib')
|
||||
|
||||
from . helpers import get_builder, show_uri, get_help_uri
|
||||
|
||||
class PreferencesDialog(Gtk.Dialog):
|
||||
__gtype_name__ = "PreferencesDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated PreferencesDialog object.
|
||||
"""
|
||||
builder = get_builder('PreferencesUberwriterDialog')
|
||||
new_object = builder.get_object("preferences_uberwriter_dialog")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initalizing should be called after parsing the ui definition
|
||||
and creating a PreferencesDialog object with it in order to
|
||||
finish initializing the start of the new PerferencesUberwriterDialog
|
||||
instance.
|
||||
|
||||
Put your initialization code in here and leave __init__ undefined.
|
||||
"""
|
||||
|
||||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self, True)
|
||||
|
||||
# code for other initialization actions should be added here
|
||||
|
||||
def on_btn_close_clicked(self, widget, data=None):
|
||||
self.destroy()
|
||||
|
||||
def on_btn_help_clicked(self, widget, data=None):
|
||||
show_uri(self, "ghelp:%s" % get_help_uri('preferences'))
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
from gi.repository import Gio, Gtk # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter_lib')
|
||||
|
||||
from . helpers import get_builder, show_uri, get_help_uri
|
||||
|
||||
# This class is meant to be subclassed by UberwriterWindow. It provides
|
||||
# common functions and some boilerplate.
|
||||
class Window(Gtk.Window):
|
||||
__gtype_name__ = "Window"
|
||||
|
||||
# To construct a new instance of this method, the following notable
|
||||
# methods are called in this order:
|
||||
# __new__(cls)
|
||||
# __init__(self)
|
||||
# finish_initializing(self, builder)
|
||||
# __init__(self)
|
||||
#
|
||||
# For this reason, it's recommended you leave __init__ empty and put
|
||||
# your initialization code in finish_initializing
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated BaseUberwriterWindow object.
|
||||
"""
|
||||
builder = get_builder('UberwriterWindow')
|
||||
new_object = builder.get_object("uberwriter_window")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initializing should be called after parsing the UI definition
|
||||
and creating a UberwriterWindow object with it in order to finish
|
||||
initializing the start of the new UberwriterWindow instance.
|
||||
"""
|
||||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self, True)
|
||||
self.PreferencesDialog = None # class
|
||||
self.preferences_dialog = None # instance
|
||||
self.AboutDialog = None # class
|
||||
|
||||
self.settings = Gio.Settings("net.launchpad.uberwriter")
|
||||
self.settings.connect('changed', self.on_preferences_changed)
|
||||
|
||||
# Optional application indicator support
|
||||
# Run 'quickly add indicator' to get started.
|
||||
# More information:
|
||||
# http://owaislone.org/quickly-add-indicator/
|
||||
# https://wiki.ubuntu.com/DesktopExperienceTeam/ApplicationIndicators
|
||||
try:
|
||||
from uberwriter import indicator
|
||||
# self is passed so methods of this class can be called from indicator.py
|
||||
# Comment this next line out to disable appindicator
|
||||
self.indicator = indicator.new_application_indicator(self)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def on_mnu_contents_activate(self, widget, data=None):
|
||||
show_uri(self, "ghelp:%s" % get_help_uri())
|
||||
|
||||
def on_mnu_about_activate(self, widget, data=None):
|
||||
"""Display the about box for uberwriter."""
|
||||
if self.AboutDialog is not None:
|
||||
about = self.AboutDialog() # pylint: disable=E1102
|
||||
response = about.run()
|
||||
about.destroy()
|
||||
|
||||
def on_mnu_preferences_activate(self, widget, data=None):
|
||||
"""Display the preferences window for uberwriter."""
|
||||
|
||||
""" From the PyGTK Reference manual
|
||||
Say for example the preferences dialog is currently open,
|
||||
and the user chooses Preferences from the menu a second time;
|
||||
use the present() method to move the already-open dialog
|
||||
where the user can see it."""
|
||||
if self.preferences_dialog is not None:
|
||||
logger.debug('show existing preferences_dialog')
|
||||
self.preferences_dialog.present()
|
||||
elif self.PreferencesDialog is not None:
|
||||
logger.debug('create new preferences_dialog')
|
||||
self.preferences_dialog = self.PreferencesDialog() # pylint: disable=E1102
|
||||
self.preferences_dialog.connect('destroy', self.on_preferences_dialog_destroyed)
|
||||
self.preferences_dialog.show()
|
||||
# destroy command moved into dialog to allow for a help button
|
||||
|
||||
def on_mnu_close_activate(self, widget, data=None):
|
||||
"""Signal handler for closing the UberwriterWindow."""
|
||||
self.destroy()
|
||||
|
||||
def on_destroy(self, widget, data=None):
|
||||
"""Called when the UberwriterWindow is closed."""
|
||||
# Clean up code for saving application state should be added here.
|
||||
Gtk.main_quit()
|
||||
|
||||
def on_preferences_changed(self, settings, key, data=None):
|
||||
logger.debug('preference changed: %s = %s' % (key, str(settings.get_value(key))))
|
||||
|
||||
def on_preferences_dialog_destroyed(self, widget, data=None):
|
||||
'''only affects gui
|
||||
|
||||
logically there is no difference between the user closing,
|
||||
minimising or ignoring the preferences dialog'''
|
||||
logger.debug('on_preferences_dialog_destroyed')
|
||||
# to determine whether to create or present preferences_dialog
|
||||
self.preferences_dialog = None
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
'''facade - makes uberwriter_lib package easy to refactor
|
||||
|
||||
while keeping its api constant'''
|
||||
from . helpers import set_up_logging
|
||||
from . Window import Window
|
||||
from . uberwriterconfig import get_version
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Python 2/3 unicode
|
||||
import sys
|
||||
if sys.version_info.major == 3:
|
||||
u = lambda x: x
|
||||
else:
|
||||
u = lambda x: x.decode('utf-8')
|
||||
|
||||
# Metadata
|
||||
__version__ = '3.0'
|
||||
__project__ = 'Python GTK Spellcheck'
|
||||
__short_name__ = 'pygtkspellcheck'
|
||||
__authors__ = u('Maximilian Köhl & Carlos Jenkins')
|
||||
__emails__ = u('linuxmaxi@googlemail.com & carlos@jenkins.co.cr')
|
||||
__website__ = 'http://koehlma.github.com/projects/pygtkspellcheck.html'
|
||||
__download_url__ = 'https://github.com/koehlma/pygtkspellcheck/tarball/master'
|
||||
__source__ = 'https://github.com/koehlma/pygtkspellcheck/'
|
||||
__vcs__ = 'git://github.com/koehlma/pygtkspellcheck.git'
|
||||
__copyright__ = u('2012, Maximilian Köhl & Carlos Jenkins')
|
||||
__desc_short__ = 'A simple but quite powerful Python spell checking library for GtkTextViews based on Enchant.'
|
||||
__desc_long__ = ('It supports both Gtk\'s Python bindings, PyGObject and'
|
||||
'PyGtk, and for both Python 2 and 3 with automatic switching'
|
||||
'and binding autodetection. For automatic translation of the'
|
||||
'user interface it can use GEdit\'s translation files.')
|
||||
|
||||
__metadata__ = {'__version__' : __version__,
|
||||
'__project__' : __project__,
|
||||
'__short_name__' : __short_name__,
|
||||
'__authors__' : __authors__,
|
||||
'__emails__' : __emails__,
|
||||
'__website__' : __website__,
|
||||
'__download_url__' : __download_url__,
|
||||
'__source__' : __source__,
|
||||
'__vcs__' : __vcs__,
|
||||
'__copyright__' : __copyright__,
|
||||
'__desc_short__' : __desc_short__,
|
||||
'__desc_long__' : __desc_long__}
|
||||
|
||||
# import SpellChecker class
|
||||
from . spellcheck import SpellChecker
|
|
@ -1,638 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
A simple but quite powerful spellchecking library written in pure Python for Gtk
|
||||
based on Enchant. It supports PyGObject as well as PyGtk for Python 2 and 3 with
|
||||
automatic switching and binding detection. For automatic translation of the user
|
||||
interface it can use Gedit’s translation files.
|
||||
"""
|
||||
|
||||
import enchant
|
||||
import gettext
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ..pylocales import code_to_name
|
||||
|
||||
# public objects
|
||||
__all__ = ['SpellChecker', 'NoDictionariesFound', 'NoGtkBindingFound']
|
||||
|
||||
# logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NoDictionariesFound(Exception):
|
||||
"""
|
||||
There aren't any dictionaries installed on the current system so
|
||||
spellchecking could not work in any way.
|
||||
"""
|
||||
|
||||
class NoGtkBindingFound(Exception):
|
||||
"""
|
||||
Could not find any loaded Gtk binding.
|
||||
"""
|
||||
|
||||
# find any loaded gtk binding
|
||||
if 'gi.repository.Gtk' in sys.modules:
|
||||
gtk = sys.modules['gi.repository.Gtk']
|
||||
_pygobject = True
|
||||
elif 'gtk' in sys.modules:
|
||||
gtk = sys.modules['gtk']
|
||||
_pygobject = False
|
||||
else:
|
||||
raise NoGtkBindingFound('could not find any loaded Gtk binding')
|
||||
|
||||
# select base list class
|
||||
try:
|
||||
from collections import UserList
|
||||
_list = UserList
|
||||
except ImportError:
|
||||
_list = list
|
||||
|
||||
if sys.version_info.major == 3:
|
||||
_py3k = True
|
||||
else:
|
||||
_py3k = False
|
||||
|
||||
# select base string
|
||||
if _py3k:
|
||||
basestring = str
|
||||
|
||||
# map between Gedit's translation and PyGtkSpellcheck's
|
||||
_GEDIT_MAP = {'Languages' : 'Languages',
|
||||
'Ignore All' : 'Ignore _All',
|
||||
'Suggestions' : 'Suggestions',
|
||||
'(no suggestions)' : '(no suggested words)',
|
||||
'Add "{}" to Dictionary' : 'Add w_ord'}
|
||||
|
||||
# translation
|
||||
if gettext.find('gedit'):
|
||||
_gedit = gettext.translation('gedit', fallback=True).gettext
|
||||
_ = lambda message: _gedit(_GEDIT_MAP[message]).replace('_', '')
|
||||
else:
|
||||
locale_name = 'py{}gtkspellcheck'.format(sys.version_info.major)
|
||||
_ = gettext.translation(locale_name, fallback=True).gettext
|
||||
|
||||
class SpellChecker(object):
|
||||
"""
|
||||
Main spellchecking class, everything important happens here.
|
||||
|
||||
:param view: GtkTextView the SpellChecker should be attached to.
|
||||
:param language: The language which should be used for spellchecking.
|
||||
Use a combination of two letter lower-case ISO 639 language code with a
|
||||
two letter upper-case ISO 3166 country code, for example en_US or de_DE.
|
||||
:param prefix: A prefix for some internal GtkTextMarks.
|
||||
:param collapse: Enclose suggestions in its own menu.
|
||||
:param params: Dictionary with Enchant broker parameters that should be set
|
||||
e.g. `enchant.myspell.dictionary.path`.
|
||||
|
||||
.. attribute:: languages
|
||||
|
||||
A list of supported languages.
|
||||
|
||||
.. function:: exists(language)
|
||||
|
||||
Checks if a language exists.
|
||||
|
||||
:param language: language to check
|
||||
"""
|
||||
FILTER_WORD = 'word'
|
||||
FILTER_LINE = 'line'
|
||||
FILTER_TEXT = 'text'
|
||||
|
||||
DEFAULT_FILTERS = {FILTER_WORD : [r'[0-9.,]+'],
|
||||
FILTER_LINE : [(r'(https?|ftp|file):((//)|(\\\\))+[\w\d:'
|
||||
r'#@%/;$()~_?+-=\\.&]+'),
|
||||
r'[\w\d]+@[\w\d.]+'],
|
||||
FILTER_TEXT : []}
|
||||
|
||||
class _LanguageList(_list):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if sys.version_info.major == 3:
|
||||
super().__init__(*args, **kwargs)
|
||||
else:
|
||||
_list.__init__(self, *args, **kwargs)
|
||||
self.mapping = dict(self)
|
||||
|
||||
@classmethod
|
||||
def from_broker(cls, broker):
|
||||
return cls(sorted([(language, code_to_name(language))
|
||||
for language in broker.list_languages()],
|
||||
key=lambda language: language[1]))
|
||||
|
||||
def exists(self, language):
|
||||
return language in self.mapping
|
||||
|
||||
class _Mark():
|
||||
def __init__(self, buffer, name, start):
|
||||
self._buffer = buffer
|
||||
self._name = name
|
||||
self._mark = self._buffer.create_mark(self._name, start, True)
|
||||
|
||||
@property
|
||||
def iter(self):
|
||||
return self._buffer.get_iter_at_mark(self._mark)
|
||||
|
||||
@property
|
||||
def inside_word(self):
|
||||
return self.iter.inside_word()
|
||||
|
||||
@property
|
||||
def word(self):
|
||||
start = self.iter
|
||||
if not start.starts_word():
|
||||
start.backward_word_start()
|
||||
end = self.iter
|
||||
if end.inside_word():
|
||||
end.forward_word_end()
|
||||
return start, end
|
||||
|
||||
def move(self, location):
|
||||
self._buffer.move_mark(self._mark, location)
|
||||
|
||||
def __init__(self, view, language='en', prefix='gtkspellchecker',
|
||||
collapse=True, params={}):
|
||||
self._view = view
|
||||
self.collapse = collapse
|
||||
self._view.connect('populate-popup',
|
||||
lambda entry, menu:self._extend_menu(menu))
|
||||
self._view.connect('popup-menu', self._click_move_popup)
|
||||
self._view.connect('button-press-event', self._click_move_button)
|
||||
self._prefix = prefix
|
||||
if _pygobject:
|
||||
self._misspelled = gtk.TextTag.new('{}-misspelled'\
|
||||
.format(self._prefix))
|
||||
else:
|
||||
self._misspelled = gtk.TextTag('{}-misspelled'.format(self._prefix))
|
||||
self._misspelled.set_property('underline', 4)
|
||||
self._broker = enchant.Broker()
|
||||
for param, value in params.items(): self._broker.set_param(param, value)
|
||||
self.languages = SpellChecker._LanguageList.from_broker(self._broker)
|
||||
if self.languages.exists(language):
|
||||
self._language = language
|
||||
elif self.languages.exists('en'):
|
||||
logger.warning(('no installed dictionary for language "{}", '
|
||||
'fallback to english'.format(language)))
|
||||
self._language = 'en'
|
||||
else:
|
||||
if self.languages:
|
||||
self._language = self.languages[0][0]
|
||||
logger.warning(('no installed dictionary for language "{}" '
|
||||
'and english, fallback to first language in'
|
||||
'language list ("{}")').format(language,
|
||||
self._language))
|
||||
else:
|
||||
logger.critical('no dictionaries found')
|
||||
raise NoDictionariesFound()
|
||||
self._dictionary = self._broker.request_dict(self._language)
|
||||
self._deferred_check = False
|
||||
self._filters = dict(SpellChecker.DEFAULT_FILTERS)
|
||||
self._regexes = {SpellChecker.FILTER_WORD : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_WORD])),
|
||||
SpellChecker.FILTER_LINE : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_LINE])),
|
||||
SpellChecker.FILTER_TEXT : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_TEXT]),
|
||||
re.MULTILINE)}
|
||||
self._enabled = True
|
||||
self.buffer_initialize()
|
||||
|
||||
@property
|
||||
def language(self):
|
||||
"""
|
||||
The language used for spellchecking.
|
||||
"""
|
||||
return self._language
|
||||
|
||||
@language.setter
|
||||
def language(self, language):
|
||||
if language != self._language and self.languages.exists(language):
|
||||
self._language = language
|
||||
self._dictionary = self._broker.request_dict(language)
|
||||
self.recheck()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
"""
|
||||
Enable or disable spellchecking.
|
||||
"""
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
if enabled and not self._enabled:
|
||||
self.enable()
|
||||
elif not enabled and self._enabled:
|
||||
self.disable()
|
||||
|
||||
def buffer_initialize(self):
|
||||
"""
|
||||
Initialize the GtkTextBuffer associated with the GtkTextView. If you
|
||||
have associated a new GtkTextBuffer with the GtkTextView call this
|
||||
method.
|
||||
"""
|
||||
self._buffer = self._view.get_buffer()
|
||||
self._buffer.connect('insert-text', self._before_text_insert)
|
||||
self._buffer.connect_after('insert-text', self._after_text_insert)
|
||||
self._buffer.connect_after('delete-range', self._range_delete)
|
||||
self._buffer.connect_after('mark-set', self._mark_set)
|
||||
start = self._buffer.get_bounds()[0]
|
||||
self._marks = {'insert-start' : SpellChecker._Mark(self._buffer,
|
||||
'{}-insert-start'.format(self._prefix), start),
|
||||
'insert-end' : SpellChecker._Mark(self._buffer,
|
||||
'{}-insert-end'.format(self._prefix), start),
|
||||
'click' : SpellChecker._Mark(self._buffer,
|
||||
'{}-click'.format(self._prefix), start)}
|
||||
self._table = self._buffer.get_tag_table()
|
||||
self._table.add(self._misspelled)
|
||||
self.ignored_tags = []
|
||||
def tag_added(tag, *args):
|
||||
if hasattr(tag, 'spell_check') and not getattr(tag, 'spell_check'):
|
||||
self.ignored_tags.append(tag)
|
||||
def tag_removed(tag, *args):
|
||||
if tag in self.ignored_tags:
|
||||
self.ignored_tags.remove(tag)
|
||||
self._table.connect('tag-added', tag_added)
|
||||
self._table.connect('tag-removed', tag_removed)
|
||||
self._table.foreach(tag_added, None)
|
||||
self.no_spell_check = self._table.lookup('no-spell-check')
|
||||
if not self.no_spell_check:
|
||||
if _pygobject:
|
||||
self.no_spell_check = gtk.TextTag.new('no-spell-check')
|
||||
else:
|
||||
self.no_spell_check = gtk.TextTag('no-spell-check')
|
||||
self._table.add(self.no_spell_check)
|
||||
self.recheck()
|
||||
|
||||
def recheck(self):
|
||||
"""
|
||||
Rechecks the spelling of the whole text.
|
||||
"""
|
||||
start, end = self._buffer.get_bounds()
|
||||
self.check_range(start, end, True)
|
||||
|
||||
def disable(self):
|
||||
"""
|
||||
Disable spellchecking.
|
||||
"""
|
||||
self._enabled = False
|
||||
start, end = self._buffer.get_bounds()
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
|
||||
def enable(self):
|
||||
"""
|
||||
Enable spellchecking.
|
||||
"""
|
||||
self._enabled = True
|
||||
self.recheck()
|
||||
|
||||
def append_filter(self, regex, filter_type):
|
||||
"""
|
||||
Append a new filter to the filter list. Filters are useful to ignore
|
||||
some misspelled words based on regular expressions.
|
||||
|
||||
:param regex: The regex used for filtering.
|
||||
:param filter_type: The type of the filter.
|
||||
|
||||
Filter Types:
|
||||
|
||||
:const:`SpellChecker.FILTER_WORD`: The regex must match the whole word
|
||||
you want to filter. The word separation is done by Pango's word
|
||||
separation algorithm so, for example, urls won't work here because
|
||||
they are split in many words.
|
||||
|
||||
:const:`SpellChecker.FILTER_LINE`: If the expression you want to match
|
||||
is a single line expression use this type. It should not be an open
|
||||
end expression because then the rest of the line with the text you
|
||||
want to filter will become correct.
|
||||
|
||||
:const:`SpellChecker.FILTER_TEXT`: Use this if you want to filter
|
||||
multiline expressions. The regex will be compiled with the
|
||||
`re.MULTILINE` flag. Same with open end expressions apply here.
|
||||
"""
|
||||
self._filters[filter_type].append(regex)
|
||||
if filter_type == SpellChecker.FILTER_TEXT:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]), re.MULTILINE)
|
||||
else:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]))
|
||||
|
||||
def remove_filter(self, regex, filter_type):
|
||||
"""
|
||||
Remove a filter from the filter list.
|
||||
|
||||
:param regex: The regex which used for filtering.
|
||||
:param filter_type: The type of the filter.
|
||||
"""
|
||||
self._filters[filter_type].remove(regex)
|
||||
if filter_type == SpellChecker.FILTER_TEXT:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]), re.MULTILINE)
|
||||
else:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]))
|
||||
|
||||
def append_ignore_tag(self, tag):
|
||||
"""
|
||||
Appends a tag to the list of ignored tags. A string will be automatic
|
||||
resolved into a tag object.
|
||||
|
||||
:param tag: Tag object or tag name.
|
||||
"""
|
||||
if isinstance(tag, basestring):
|
||||
tag = self._table.lookup(tag)
|
||||
self.ignored_tags.append(tag)
|
||||
|
||||
def remove_ignore_tag(self, tag):
|
||||
"""
|
||||
Removes a tag from the list of ignored tags. A string will be automatic
|
||||
resolved into a tag object.
|
||||
|
||||
:param tag: Tag object or tag name.
|
||||
"""
|
||||
if isinstance(tag, basestring):
|
||||
tag = self._table.lookup(tag)
|
||||
self.ignored_tags.remove(tag)
|
||||
|
||||
def add_to_dictionary(self, word):
|
||||
"""
|
||||
Adds a word to user's dictionary.
|
||||
|
||||
:param word: The word to add.
|
||||
"""
|
||||
self._dictionary.add_to_pwl(word)
|
||||
self.recheck()
|
||||
|
||||
def ignore_all(self, word):
|
||||
"""
|
||||
Ignores a word for the current session.
|
||||
|
||||
:param word: The word to ignore.
|
||||
"""
|
||||
self._dictionary.add_to_session(word)
|
||||
self.recheck()
|
||||
|
||||
def check_range(self, start, end, force_all=False):
|
||||
"""
|
||||
Checks a specified range between two GtkTextIters.
|
||||
|
||||
:param start: Start iter - checking starts here.
|
||||
:param end: End iter - checking ends here.
|
||||
"""
|
||||
if not self._enabled:
|
||||
return
|
||||
if end.inside_word(): end.forward_word_end()
|
||||
if not start.starts_word() and (start.inside_word() or
|
||||
start.ends_word()):
|
||||
start.backward_word_start()
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
cursor = self._buffer.get_iter_at_mark(self._buffer.get_insert())
|
||||
precursor = cursor.copy()
|
||||
precursor.backward_char()
|
||||
highlight = (cursor.has_tag(self._misspelled) or
|
||||
precursor.has_tag(self._misspelled))
|
||||
if not start.get_offset():
|
||||
start.forward_word_end()
|
||||
start.backward_word_start()
|
||||
word_start = start.copy()
|
||||
while word_start.compare(end) < 0:
|
||||
word_end = word_start.copy()
|
||||
word_end.forward_word_end()
|
||||
in_word = ((word_start.compare(cursor) < 0) and
|
||||
(cursor.compare(word_end) <= 0))
|
||||
if in_word and not force_all:
|
||||
if highlight:
|
||||
self._check_word(word_start, word_end)
|
||||
else:
|
||||
self._deferred_check = True
|
||||
else:
|
||||
self._check_word(word_start, word_end)
|
||||
self._deferred_check = False
|
||||
word_end.forward_word_end()
|
||||
word_end.backward_word_start()
|
||||
if word_start.equal(word_end):
|
||||
break
|
||||
word_start = word_end.copy()
|
||||
|
||||
def _languages_menu(self):
|
||||
def _set_language(item, code):
|
||||
self.language = code
|
||||
if _pygobject:
|
||||
menu = gtk.Menu.new()
|
||||
group = []
|
||||
else:
|
||||
menu = gtk.Menu()
|
||||
group = gtk.RadioMenuItem()
|
||||
connect = []
|
||||
for code, name in self.languages:
|
||||
if _pygobject:
|
||||
item = gtk.RadioMenuItem.new_with_label(group, name)
|
||||
group.append(item)
|
||||
else:
|
||||
item = gtk.RadioMenuItem(group, name)
|
||||
if code == self.language:
|
||||
item.set_active(True)
|
||||
connect.append((item, code))
|
||||
menu.append(item)
|
||||
for item, code in connect:
|
||||
item.connect('activate', _set_language, code)
|
||||
return menu
|
||||
|
||||
def _suggestion_menu(self, word):
|
||||
menu = []
|
||||
suggestions = self._dictionary.suggest(word)
|
||||
if not suggestions:
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new()
|
||||
label = gtk.Label.new('')
|
||||
else:
|
||||
item = gtk.MenuItem()
|
||||
label = gtk.Label()
|
||||
try:
|
||||
label.set_halign(gtk.Align.LEFT)
|
||||
except AttributeError:
|
||||
label.set_alignment(0.0, 0.5)
|
||||
label.set_markup('<i>{text}</i>'.format(text=_('(no suggestions)')))
|
||||
item.add(label)
|
||||
menu.append(item)
|
||||
else:
|
||||
for suggestion in suggestions:
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new()
|
||||
label = gtk.Label.new('')
|
||||
else:
|
||||
item = gtk.MenuItem()
|
||||
label = gtk.Label()
|
||||
label.set_markup('<b>{text}</b>'.format(text=suggestion))
|
||||
try:
|
||||
label.set_halign(gtk.Align.LEFT)
|
||||
except AttributeError:
|
||||
label.set_alignment(0.0, 0.5)
|
||||
item.add(label)
|
||||
item.connect('activate', self._replace_word, word, suggestion)
|
||||
menu.append(item)
|
||||
if _pygobject:
|
||||
menu.append(gtk.SeparatorMenuItem.new())
|
||||
item = gtk.MenuItem.new_with_label(
|
||||
_('Add "{}" to Dictionary').format(word))
|
||||
else:
|
||||
menu.append(gtk.SeparatorMenuItem())
|
||||
item = gtk.MenuItem(_('Add "{}" to Dictionary').format(word))
|
||||
item.connect('activate', lambda *args: self.add_to_dictionary(word))
|
||||
menu.append(item)
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new_with_label(_('Ignore All'))
|
||||
else:
|
||||
item = gtk.MenuItem(_('Ignore All'))
|
||||
item.connect('activate', lambda *args: self.ignore_all(word))
|
||||
menu.append(item)
|
||||
return menu
|
||||
|
||||
def _extend_menu(self, menu):
|
||||
if not self._enabled:
|
||||
return
|
||||
if _pygobject:
|
||||
separator = gtk.SeparatorMenuItem.new()
|
||||
else:
|
||||
separator = gtk.SeparatorMenuItem()
|
||||
separator.show()
|
||||
menu.prepend(separator)
|
||||
if _pygobject:
|
||||
languages = gtk.MenuItem.new_with_label(_('Languages'))
|
||||
else:
|
||||
languages = gtk.MenuItem(_('Languages'))
|
||||
languages.set_submenu(self._languages_menu())
|
||||
languages.show_all()
|
||||
menu.prepend(languages)
|
||||
if self._marks['click'].inside_word:
|
||||
start, end = self._marks['click'].word
|
||||
if start.has_tag(self._misspelled):
|
||||
if _py3k:
|
||||
word = self._buffer.get_text(start, end, False)
|
||||
else:
|
||||
word = self._buffer.get_text(start, end,
|
||||
False).decode('utf-8')
|
||||
items = self._suggestion_menu(word)
|
||||
if self.collapse:
|
||||
if _pygobject:
|
||||
suggestions = gtk.MenuItem.new_with_label(
|
||||
_('Suggestions'))
|
||||
submenu = gtk.Menu.new()
|
||||
else:
|
||||
suggestions = gtk.MenuItem(_('Suggestions'))
|
||||
submenu = gtk.Menu()
|
||||
for item in items:
|
||||
submenu.append(item)
|
||||
suggestions.set_submenu(submenu)
|
||||
suggestions.show_all()
|
||||
menu.prepend(suggestions)
|
||||
else:
|
||||
items.reverse()
|
||||
for item in items:
|
||||
menu.prepend(item)
|
||||
menu.show_all()
|
||||
|
||||
def _click_move_popup(self, *args):
|
||||
self._marks['click'].move(self._buffer.get_iter_at_mark(
|
||||
self._buffer.get_insert()))
|
||||
return False
|
||||
|
||||
def _click_move_button(self, widget, event):
|
||||
if event.button == 3:
|
||||
if self._deferred_check: self._check_deferred_range(True)
|
||||
x, y = self._view.window_to_buffer_coords(2, int(event.x),
|
||||
int(event.y))
|
||||
self._marks['click'].move(self._view.get_iter_at_location(x, y))
|
||||
return False
|
||||
|
||||
def _before_text_insert(self, textbuffer, location, text, length):
|
||||
self._marks['insert-start'].move(location)
|
||||
|
||||
def _after_text_insert(self, textbuffer, location, text, length):
|
||||
start = self._marks['insert-start'].iter
|
||||
self.check_range(start, location)
|
||||
self._marks['insert-end'].move(location)
|
||||
|
||||
def _range_delete(self, textbuffer, start, end):
|
||||
self.check_range(start, end)
|
||||
|
||||
def _mark_set(self, textbuffer, location, mark):
|
||||
if mark == self._buffer.get_insert() and self._deferred_check:
|
||||
self._check_deferred_range(False)
|
||||
|
||||
def _replace_word(self, item, old_word, new_word):
|
||||
start, end = self._marks['click'].word
|
||||
offset = start.get_offset()
|
||||
self._buffer.begin_user_action()
|
||||
self._buffer.delete(start, end)
|
||||
self._buffer.insert(self._buffer.get_iter_at_offset(offset), new_word)
|
||||
self._buffer.end_user_action()
|
||||
self._dictionary.store_replacement(old_word, new_word)
|
||||
|
||||
def _check_deferred_range(self, force_all):
|
||||
start = self._marks['insert-start'].iter
|
||||
end = self._marks['insert-end'].iter
|
||||
self.check_range(start, end, force_all)
|
||||
|
||||
def _check_word(self, start, end):
|
||||
if start.has_tag(self.no_spell_check):
|
||||
return
|
||||
for tag in self.ignored_tags:
|
||||
if start.has_tag(tag):
|
||||
return
|
||||
if _py3k:
|
||||
word = self._buffer.get_text(start, end, False).strip()
|
||||
else:
|
||||
word = self._buffer.get_text(start, end, False).decode('utf-8').strip()
|
||||
if len(self._filters[SpellChecker.FILTER_WORD]):
|
||||
if self._regexes[SpellChecker.FILTER_WORD].match(word):
|
||||
return
|
||||
if len(self._filters[SpellChecker.FILTER_LINE]):
|
||||
line_start = self._buffer.get_iter_at_line(start.get_line())
|
||||
line_end = end.copy()
|
||||
line_end.forward_to_line_end()
|
||||
if _py3k:
|
||||
line = self._buffer.get_text(line_start, line_end, False)
|
||||
else:
|
||||
line = self._buffer.get_text(line_start, line_end,
|
||||
False).decode('utf-8')
|
||||
for match in self._regexes[SpellChecker.FILTER_LINE].finditer(line):
|
||||
if match.start() <= start.get_line_offset() <= match.end():
|
||||
start = self._buffer.get_iter_at_line_offset(
|
||||
start.get_line(), match.start())
|
||||
end = self._buffer.get_iter_at_line_offset(start.get_line(),
|
||||
match.end())
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
return
|
||||
if len(self._filters[SpellChecker.FILTER_TEXT]):
|
||||
text_start, text_end = self._buffer.get_bounds()
|
||||
if _py3k:
|
||||
text = self._buffer.get_text(text_start, text_end, False)
|
||||
else:
|
||||
text = self._buffer.get_text(text_start, text_end,
|
||||
False).decode('utf-8')
|
||||
for match in self._regexes[SpellChecker.FILTER_TEXT].finditer(text):
|
||||
if match.start() <= start.get_offset() <= match.end():
|
||||
start = self._buffer.get_iter_at_offset(match.start())
|
||||
end = self._buffer.get_iter_at_offset(match.end())
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
return
|
||||
if not self._dictionary.check(word):
|
||||
self._buffer.apply_tag(self._misspelled, start, end)
|
|
@ -1,117 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
"""Helpers for an Ubuntu application."""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from . uberwriterconfig import get_data_file
|
||||
from . Builder import Builder
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
def get_builder(builder_file_name):
|
||||
"""Return a fully-instantiated Gtk.Builder instance from specified ui
|
||||
file
|
||||
|
||||
:param builder_file_name: The name of the builder file, without extension.
|
||||
Assumed to be in the 'ui' directory under the data path.
|
||||
"""
|
||||
# Look for the ui file that describes the user interface.
|
||||
ui_filename = get_data_file('ui', '%s.ui' % (builder_file_name,))
|
||||
if not os.path.exists(ui_filename):
|
||||
ui_filename = None
|
||||
|
||||
builder = Builder()
|
||||
builder.set_translation_domain('uberwriter')
|
||||
builder.add_from_file(ui_filename)
|
||||
return builder
|
||||
|
||||
|
||||
# Owais Lone : To get quick access to icons and stuff.
|
||||
def get_media_file(media_file_name):
|
||||
media_filename = get_data_file('media', '%s' % (media_file_name,))
|
||||
if not os.path.exists(media_filename):
|
||||
media_filename = None
|
||||
|
||||
return "file:///"+media_filename
|
||||
|
||||
def get_media_path(media_file_name):
|
||||
media_filename = get_data_file('media', '%s' % (media_file_name,))
|
||||
if not os.path.exists(media_filename):
|
||||
media_filename = None
|
||||
return "/"+media_filename
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def set_up_logging(opts):
|
||||
# add a handler to prevent basicConfig
|
||||
root = logging.getLogger()
|
||||
null_handler = NullHandler()
|
||||
root.addHandler(null_handler)
|
||||
|
||||
formatter = logging.Formatter("%(levelname)s:%(name)s: %(funcName)s() '%(message)s'")
|
||||
|
||||
logger = logging.getLogger('uberwriter')
|
||||
logger_sh = logging.StreamHandler()
|
||||
logger_sh.setFormatter(formatter)
|
||||
logger.addHandler(logger_sh)
|
||||
|
||||
lib_logger = logging.getLogger('uberwriter_lib')
|
||||
lib_logger_sh = logging.StreamHandler()
|
||||
lib_logger_sh.setFormatter(formatter)
|
||||
lib_logger.addHandler(lib_logger_sh)
|
||||
|
||||
# Set the logging level to show debug messages.
|
||||
if opts.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.debug('logging enabled')
|
||||
if opts.verbose and opts.verbose > 1:
|
||||
lib_logger.setLevel(logging.DEBUG)
|
||||
|
||||
def get_help_uri(page=None):
|
||||
# help_uri from source tree - default language
|
||||
here = os.path.dirname(__file__)
|
||||
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
|
||||
|
||||
if not os.path.exists(help_uri):
|
||||
# installed so use gnome help tree - user's language
|
||||
help_uri = 'uberwriter'
|
||||
|
||||
# unspecified page is the index.page
|
||||
if page is not None:
|
||||
help_uri = '%s#%s' % (help_uri, page)
|
||||
|
||||
return help_uri
|
||||
|
||||
def show_uri(parent, link):
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
screen = parent.get_screen()
|
||||
Gtk.show_uri(screen, link, Gtk.get_current_event_time())
|
||||
|
||||
def alias(alternative_function_name):
|
||||
'''see http://www.drdobbs.com/web-development/184406073#l9'''
|
||||
def decorator(function):
|
||||
'''attach alternative_function_name(s) to function'''
|
||||
if not hasattr(function, 'aliases'):
|
||||
function.aliases = []
|
||||
function.aliases.append(alternative_function_name)
|
||||
return function
|
||||
return decorator
|
|
@ -1,55 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Python 2/3 unicode
|
||||
import sys
|
||||
if sys.version_info.major == 3:
|
||||
u = lambda x: x
|
||||
else:
|
||||
u = lambda x: x.decode('utf-8')
|
||||
|
||||
# Metadata
|
||||
__version__ = '1.1'
|
||||
__project__ = 'PyLocales'
|
||||
__short_name__ = 'pylocales'
|
||||
__authors__ = u('Maximilian Köhl & Carlos Jenkins')
|
||||
__emails__ = u('linuxmaxi@googlemail.com & carlos@jenkins.co.cr')
|
||||
__website__ = 'http://pygtkspellcheck.readthedocs.org/'
|
||||
__source__ = 'https://github.com/koehlma/pygtkspellcheck/'
|
||||
__vcs__ = 'git://github.com/koehlma/pygtkspellcheck.git'
|
||||
__copyright__ = u('2012, Maximilian Köhl & Carlos Jenkins')
|
||||
__desc_short__ = 'Query the ISO 639/3166 database about a country or a language.'
|
||||
__desc_long__ = ('Query the ISO 639/3166 database about a country or a'
|
||||
'language. The locales database contains ISO 639 languages'
|
||||
'definitions and ISO 3166 countries definitions. This package'
|
||||
'provides translation for countries and languages names if'
|
||||
'iso-codes package is installed (Ubuntu/Debian).')
|
||||
|
||||
__metadata__ = {'__version__' : __version__,
|
||||
'__project__' : __project__,
|
||||
'__short_name__' : __short_name__,
|
||||
'__authors__' : __authors__,
|
||||
'__emails__' : __emails__,
|
||||
'__website__' : __website__,
|
||||
'__source__' : __source__,
|
||||
'__vcs__' : __vcs__,
|
||||
'__copyright__' : __copyright__,
|
||||
'__desc_short__' : __desc_short__,
|
||||
'__desc_long__' : __desc_long__}
|
||||
|
||||
# Should only import Public Objects
|
||||
from .locales import *
|
|
@ -1,138 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Query the ISO 639/3166 database about a country or a language. The locales
|
||||
database contains ISO 639 languages definitions and ISO 3166 countries
|
||||
definitions. This package provides translation for countries and languages names
|
||||
if iso-codes package is installed (Ubuntu/Debian).
|
||||
|
||||
@see utils/locales/build.py to know the database tables and structure.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
# Public Objects
|
||||
__all__ = ['Country', 'Language', 'LanguageNotFound',
|
||||
'CountryNotFound', 'code_to_name']
|
||||
|
||||
# Translation
|
||||
_translator_language = gettext.translation('iso_639', fallback=True).gettext
|
||||
_translator_country = gettext.translation('iso_3166', fallback=True).gettext
|
||||
|
||||
# Decides where the database is located. If an application provides an
|
||||
# os.path.get_module_path monkey patch to determine the path where the module
|
||||
# is located it uses this. If not it searches in the directory of this source
|
||||
# code file.
|
||||
__path__ = None
|
||||
if hasattr(os.path, 'get_module_path'):
|
||||
__path__ = os.path.get_module_path(__file__)
|
||||
if not os.path.isfile(os.path.join(__path__, 'locales.db')):
|
||||
__path__ = None
|
||||
if __path__ is None:
|
||||
__path__ = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
||||
|
||||
# Loading the Database
|
||||
_database = sqlite3.connect(os.path.join(__path__, 'locales.db'))
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Exceptions
|
||||
class LanguageNotFound(Exception):
|
||||
"""
|
||||
The specified language wasn't found in the database.
|
||||
"""
|
||||
|
||||
class CountryNotFound(Exception):
|
||||
"""
|
||||
The specified country wasn't found in the database.
|
||||
"""
|
||||
|
||||
class Country(object):
|
||||
def __init__(self, rowid):
|
||||
country = _database.execute('SELECT * FROM countries WHERE rowid == ?', (rowid,)).fetchone()
|
||||
self.name = country[0]
|
||||
self.official_name = country[1]
|
||||
self.alpha_2 = country[2]
|
||||
self.alpha_3 = country[3]
|
||||
self.numeric = country[4]
|
||||
self.translation = _translator_country(self.name)
|
||||
|
||||
@classmethod
|
||||
def get_country(cls, code, codec):
|
||||
country = _database.execute('SELECT rowid FROM countries WHERE %s == ?' % (codec), (code,)).fetchone()
|
||||
if country:
|
||||
return cls(country[0])
|
||||
raise CountryNotFound('code: %s, codec: %s' % (code, codec))
|
||||
|
||||
@classmethod
|
||||
def by_alpha_2(cls, code):
|
||||
return Country.get_country(code, 'alpha_2')
|
||||
|
||||
@classmethod
|
||||
def by_alpha_3(cls, code):
|
||||
return Country.get_country(code, 'alpha_3')
|
||||
|
||||
@classmethod
|
||||
def by_numeric(cls, code):
|
||||
return Country.get_country(code, 'numeric')
|
||||
|
||||
class Language(object):
|
||||
def __init__(self, rowid):
|
||||
language = _database.execute('SELECT * FROM languages WHERE rowid == ?', (rowid,)).fetchone()
|
||||
self.name = language[0]
|
||||
self.iso_639_2B = language[1]
|
||||
self.iso_639_2T = language[2]
|
||||
self.iso_639_1 = language[3]
|
||||
self.translation = _translator_language(self.name)
|
||||
|
||||
@classmethod
|
||||
def get_language(cls, code, codec):
|
||||
language = _database.execute('SELECT rowid FROM languages WHERE %s == ?' % (codec), (code,)).fetchone()
|
||||
if language:
|
||||
return cls(language[0])
|
||||
raise LanguageNotFound('code: %s, codec: %s' % (code, codec))
|
||||
|
||||
@classmethod
|
||||
def by_iso_639_2B(cls, code):
|
||||
return Language.get_language(code, 'iso_639_2B')
|
||||
|
||||
@classmethod
|
||||
def by_iso_639_2T(cls, code):
|
||||
return Language.get_language(code, 'iso_639_2T')
|
||||
|
||||
@classmethod
|
||||
def by_iso_639_1(cls, code):
|
||||
return Language.get_language(code, 'iso_639_1')
|
||||
|
||||
|
||||
def code_to_name(code, separator='_'):
|
||||
"""
|
||||
Get the natural name of a language based on it's code.
|
||||
"""
|
||||
logger.debug('requesting name for code "{}"'.format(code))
|
||||
code = code.split(separator)
|
||||
if len(code) > 1:
|
||||
lang = Language.by_iso_639_1(code[0]).translation
|
||||
country = Country.by_alpha_2(code[1]).translation
|
||||
return '{lang} ({country})'.format(lang=lang, country=country)
|
||||
else:
|
||||
return Language.by_iso_639_1(code[0]).translation
|
|
@ -1,209 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
import re
|
||||
import vim
|
||||
|
||||
|
||||
def cjk_width(text):
|
||||
import sys
|
||||
if sys.version_info[0] < 3:
|
||||
if not isinstance(text, unicode):
|
||||
text = text.decode("utf-8")
|
||||
from unicodedata import east_asian_width
|
||||
return sum(1+(east_asian_width(c) in "WF") for c in text)
|
||||
|
||||
|
||||
def create_separarator(widths, char):
|
||||
"""Genera una linea para separar filas de una tabla.
|
||||
|
||||
El parametro `widths` es un lista indicando el ancho de cada
|
||||
columna. En cambio el argumento `char` es el caracter que se
|
||||
tiene que utilizar para imprimir.
|
||||
|
||||
El valor retornado es un string.
|
||||
|
||||
Por ejemplo::
|
||||
|
||||
>>> create_separarator([2, 4], '-')
|
||||
'+----+------+'
|
||||
"""
|
||||
|
||||
line = []
|
||||
|
||||
for w in widths:
|
||||
line.append("+" + char * (w + 2))
|
||||
|
||||
line.append("+")
|
||||
return ''.join(line)
|
||||
|
||||
|
||||
def create_line(columns, widths):
|
||||
"""Crea una fila de la tabla separando los campos con un '|'.
|
||||
|
||||
El argumento `columns` es una lista con las celdas que se
|
||||
quieren imprimir y el argumento `widths` tiene el ancho
|
||||
de cada columna. Si la columna es mas ancha que el texto
|
||||
a imprimir se agregan espacios vacíos.
|
||||
|
||||
Por ejemplo::
|
||||
|
||||
>>> create_line(['nombre', 'apellido'], [7, 10])
|
||||
'| nombre | apellido |'
|
||||
"""
|
||||
|
||||
line = zip(columns, widths)
|
||||
result = []
|
||||
|
||||
for text, width in line:
|
||||
spaces = " " * (width - cjk_width(text))
|
||||
text = "| " + text + spaces + " "
|
||||
result.append(text)
|
||||
|
||||
result.append("|")
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
def create_table(content):
|
||||
"""Imprime una tabla en formato restructuredText.
|
||||
|
||||
El argumento `content` tiene que ser una lista
|
||||
de celdas.
|
||||
|
||||
Por ejemplo::
|
||||
|
||||
>>> print create_table([['software', 'vesion'], ['python', '3.2'], ['vim', '7.3']])
|
||||
+----------+--------+
|
||||
| software | vesion |
|
||||
+==========+========+
|
||||
| python | 3.2 |
|
||||
+----------+--------+
|
||||
| vim | 7.3 |
|
||||
+----------+--------+
|
||||
"""
|
||||
|
||||
# obtiene las columnas de toda la tabla.
|
||||
columns = zip(*content)
|
||||
# calcula el tamano maximo que debe tener cada columna.
|
||||
widths = [max([cjk_width(x) for x in i]) for i in columns]
|
||||
|
||||
result = []
|
||||
|
||||
result.append(create_separarator(widths, '-'))
|
||||
result.append(create_line(content[0], widths))
|
||||
result.append(create_separarator(widths, '='))
|
||||
|
||||
for line in content[1:]:
|
||||
result.append(create_line(line, widths))
|
||||
result.append(create_separarator(widths, '-'))
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
def are_in_a_table(current_line):
|
||||
"Indica si el cursor se encuentra dentro de una tabla."
|
||||
return "|" in current_line or "+" in current_line
|
||||
|
||||
|
||||
def are_in_a_paragraph(current_line):
|
||||
"Indica si la linea actual es parte de algun parrafo"
|
||||
return len(current_line.strip()) >= 1
|
||||
|
||||
|
||||
def get_table_bounds(current_row_index, are_in_callback):
|
||||
"""Obtiene el numero de fila donde comienza y termina la tabla.
|
||||
|
||||
el argumento `are_in_callback` tiene que ser una función
|
||||
que indique si una determinada linea pertenece o no
|
||||
a la tabla que se quiere medir (o crear).
|
||||
|
||||
Retorna ambos valores como una tupla.
|
||||
"""
|
||||
|
||||
top = 0
|
||||
buffer = vim.current.buffer
|
||||
max = len(buffer)
|
||||
bottom = max - 1
|
||||
|
||||
for a in range(current_row_index, top, -1):
|
||||
if not are_in_callback(buffer[a]):
|
||||
top = a + 1
|
||||
break
|
||||
|
||||
for b in range(current_row_index, max):
|
||||
if not are_in_callback(buffer[b]):
|
||||
bottom = b - 1
|
||||
break
|
||||
|
||||
return top, bottom
|
||||
|
||||
|
||||
def remove_spaces(string):
|
||||
"Elimina los espacios innecesarios de una fila de tabla."
|
||||
return re.sub("\s\s+", " ", string)
|
||||
|
||||
|
||||
def create_separators_removing_spaces(string):
|
||||
return re.sub("\s\s+", "|", string)
|
||||
|
||||
|
||||
def extract_cells_as_list(string):
|
||||
"Extrae el texto de una fila de tabla y lo retorna como una lista."
|
||||
string = remove_spaces(string)
|
||||
return [item.strip() for item in string.split('|') if item]
|
||||
|
||||
|
||||
def extract_table(buffer, top, bottom):
|
||||
full_table_text = buffer[top:bottom]
|
||||
# selecciona solamente las lineas que tienen celdas con texto.
|
||||
only_text_lines = [x for x in full_table_text if '|' in x]
|
||||
# extrae las celdas y descarta los separadores innecesarios.
|
||||
return [extract_cells_as_list(x) for x in only_text_lines]
|
||||
|
||||
|
||||
def extract_words_as_lists(buffer, top, bottom):
|
||||
"Genera una lista de palabras para crear una lista."
|
||||
|
||||
lines = buffer[top:bottom + 1]
|
||||
return [create_separators_removing_spaces(line).split('|') for line in lines]
|
||||
|
||||
|
||||
def copy_to_buffer(buffer, string, index):
|
||||
lines = string.split('\n')
|
||||
for line in lines:
|
||||
buffer[index] = line
|
||||
index += 1
|
||||
|
||||
|
||||
def fix_table(current_row_index):
|
||||
"""Arregla una tabla para que todas las columnas tengan el mismo ancho.
|
||||
|
||||
`initial_row` es un numero entero que indica en que
|
||||
linea se encuenta el cursor."""
|
||||
|
||||
# obtiene el indice donde comienza y termina la tabla.
|
||||
r1, r2 = get_table_bounds(current_row_index, are_in_a_table)
|
||||
|
||||
# extrae de la tabla solo las celdas de texto
|
||||
table_as_list = extract_table(vim.current.buffer, r1, r2)
|
||||
|
||||
# genera una nueva tabla tipo restructured text y la dibuja en el buffer.
|
||||
table_content = create_table(table_as_list)
|
||||
copy_to_buffer(vim.current.buffer, table_content, r1)
|
||||
|
||||
|
||||
def FixTable():
|
||||
(row, col) = vim.current.window.cursor
|
||||
line = vim.current.buffer[row - 1]
|
||||
|
||||
if are_in_a_table(line):
|
||||
fix_table(row - 1)
|
||||
else:
|
||||
print("No estoy en una tabla. Terminando...")
|
||||
|
||||
|
||||
def CreateTable():
|
||||
(row, col) = vim.current.window.cursor
|
||||
|
||||
top, bottom = get_table_bounds(row - 1, are_in_a_paragraph)
|
||||
lines = extract_words_as_lists(vim.current.buffer, top, bottom)
|
||||
table_content = create_table(lines)
|
||||
vim.current.buffer[top:bottom + 1] = table_content.split('\n')
|
|
@ -1,69 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
__all__ = [
|
||||
'project_path_not_found',
|
||||
'get_data_file',
|
||||
'get_data_path',
|
||||
]
|
||||
|
||||
# Where your project will look for your data (for instance, images and ui
|
||||
# files). By default, this is ../data, relative your trunk layout
|
||||
__uberwriter_data_directory__ = '../data/'
|
||||
__license__ = 'GPL-3'
|
||||
__version__ = 'VERSION'
|
||||
|
||||
import os
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
class project_path_not_found(Exception):
|
||||
"""Raised when we can't find the project directory."""
|
||||
|
||||
|
||||
def get_data_file(*path_segments):
|
||||
"""Get the full path to a data file.
|
||||
|
||||
Returns the path to a file underneath the data directory (as defined by
|
||||
`get_data_path`). Equivalent to os.path.join(get_data_path(),
|
||||
*path_segments).
|
||||
"""
|
||||
return os.path.join(get_data_path(), *path_segments)
|
||||
|
||||
|
||||
def get_data_path():
|
||||
"""Retrieve uberwriter data path
|
||||
|
||||
This path is by default <uberwriter_lib_path>/../data/ in trunk
|
||||
and /usr/share/uberwriter in an installed version but this path
|
||||
is specified at installation time.
|
||||
"""
|
||||
|
||||
# Get pathname absolute or relative.
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), __uberwriter_data_directory__)
|
||||
|
||||
abs_data_path = os.path.abspath(path)
|
||||
if not os.path.exists(abs_data_path):
|
||||
raise project_path_not_found
|
||||
|
||||
return abs_data_path
|
||||
|
||||
|
||||
def get_version():
|
||||
return __version__
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
import locale
|
||||
locale.textdomain('uberwriter')
|
||||
|
||||
# Add project root directory (enable symlink and trunk execution)
|
||||
PROJECT_ROOT_DIRECTORY = os.path.abspath(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
|
||||
|
||||
python_path = []
|
||||
if os.path.abspath(__file__).startswith('/opt'):
|
||||
locale.bindtextdomain('uberwriter', '/opt/extras.ubuntu.com/uberwriter/share/locale')
|
||||
syspath = sys.path[:] # copy to avoid infinite loop in pending objects
|
||||
for path in syspath:
|
||||
opt_path = path.replace('/usr', '/opt/extras.ubuntu.com/uberwriter')
|
||||
python_path.insert(0, opt_path)
|
||||
sys.path.insert(0, opt_path)
|
||||
os.putenv("XDG_DATA_DIRS", "%s:%s" % ("/opt/extras.ubuntu.com/uberwriter/share/", os.getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")))
|
||||
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'uberwriter'))
|
||||
and PROJECT_ROOT_DIRECTORY not in sys.path):
|
||||
python_path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
if python_path:
|
||||
os.putenv('PYTHONPATH', "%s:%s" % (os.getenv('PYTHONPATH', ''), ':'.join(python_path))) # for subprocesses
|
||||
|
||||
import uberwriter
|
||||
uberwriter.main()
|
Loading…
Reference in New Issue