From 04c3badee5334100f3547f10011ed5e15e5c9a58 Mon Sep 17 00:00:00 2001 From: Robert Griesel Date: Wed, 16 Feb 2022 16:16:15 +0100 Subject: [PATCH] search function for symbols sidebar --- data/resources/style_gtk.css | 35 +- setzer/workspace/sidebar/sidebar.py | 260 +------------- setzer/workspace/sidebar/sidebar_viewgtk.py | 114 +----- .../sidebar/symbols_page/__init__.py | 0 .../sidebar/symbols_page/symbols_page.py | 340 ++++++++++++++++++ .../symbols_page/symbols_page_viewgtk.py | 156 ++++++++ 6 files changed, 524 insertions(+), 381 deletions(-) create mode 100644 setzer/workspace/sidebar/symbols_page/__init__.py create mode 100644 setzer/workspace/sidebar/symbols_page/symbols_page.py create mode 100644 setzer/workspace/sidebar/symbols_page/symbols_page_viewgtk.py diff --git a/data/resources/style_gtk.css b/data/resources/style_gtk.css index 02008306..4b63d32b 100644 --- a/data/resources/style_gtk.css +++ b/data/resources/style_gtk.css @@ -235,57 +235,57 @@ box.buildlog > box button { ** sidebar */ -overlay.sidebar { +box.sidebar-symbols { background-color: @theme_base_color; } -overlay.sidebar toolbar { +box.sidebar-symbols toolbar { padding: 0px; margin: 0px; background-color: @theme_base_color; } -overlay.sidebar toolbar toolbutton { +box.sidebar-symbols toolbar toolbutton { padding: 0px; margin: 0px; } -overlay.sidebar toolbar button { +box.sidebar-symbols toolbar button { margin: 0px; padding: 6px; border-width: 0px; border-radius: 0px; box-shadow: none; } -overlay.sidebar toolbar button:checked { +box.sidebar-symbols toolbar button:checked { background: mix(@theme_base_color, @borders, 0.45); } -overlay.sidebar toolbar button:active { +box.sidebar-symbols toolbar button:active { background: mix(@theme_base_color, @borders, 0.45); } -overlay.sidebar label { +box.sidebar-symbols label { padding: 9px 8px 8px 9px; background-color: @theme_base_color; border-bottom: 1px solid mix(@theme_base_color, @borders, 0.5); } -overlay.sidebar label.overlay { +box.sidebar-symbols label.overlay { border-bottom-width: 0px; } -overlay.sidebar box.tabs-box { +box.sidebar-symbols box.tabs-box { background-color: @theme_base_color; border-bottom: 1px solid mix(@theme_base_color, @borders, 0.5); padding-bottom: 0px; } -overlay.sidebar box.tabs-box.no-border { +box.sidebar-symbols box.tabs-box.no-border { border-bottom-width: 0px; padding-bottom: 1px; } -overlay.sidebar flowbox { +box.sidebar-symbols flowbox { background-color: mix(@theme_base_color, @theme_bg_color, 0.7); padding-bottom: 36px; border-bottom: 1px solid mix(@theme_base_color, @borders, 0.5); } -overlay.sidebar flowbox flowboxchild { +box.sidebar-symbols flowbox flowboxchild { padding: 0px; } -overlay.sidebar flowbox flowboxchild image { +box.sidebar-symbols flowbox flowboxchild image { color: @theme_fg_color; background-color: mix(@theme_base_color, @theme_bg_color, 0.7); border-bottom: 1px solid mix(@theme_base_color, @borders, 0.5); @@ -293,9 +293,16 @@ overlay.sidebar flowbox flowboxchild image { margin: 0px; padding: 5px; } -overlay.sidebar flowbox flowboxchild image.no_right_border { +box.sidebar-symbols flowbox flowboxchild image.no_right_border { border-right: 1px solid mix(@theme_base_color, @theme_bg_color, 0.7); } +box.sidebar-symbols box.search_bar { + padding-left: 6px; + padding-right: 6px; +} +box.sidebar-symbols box.search_bar button { + margin-left: 6px; +} /* ** shortcuts bar diff --git a/setzer/workspace/sidebar/sidebar.py b/setzer/workspace/sidebar/sidebar.py index 7a1d43c3..11872914 100644 --- a/setzer/workspace/sidebar/sidebar.py +++ b/setzer/workspace/sidebar/sidebar.py @@ -18,19 +18,10 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import Gio -from gi.repository import GLib -from gi.repository import GObject -import setzer.workspace.sidebar.sidebar_viewgtk as sidebar_view +import setzer.workspace.sidebar.symbols_page.symbols_page as symbols_page from setzer.app.service_locator import ServiceLocator -import math -import time -import xml.etree.ElementTree as ET -import os - class Sidebar(object): ''' Init and controll sidebar ''' @@ -39,252 +30,7 @@ def __init__(self, workspace): self.view = ServiceLocator.get_main_window().sidebar self.workspace = workspace - self.scroll_to = None - - # tabbed pages: name, icon name, tooltip, widget - self.pages = list() - self.pages.append(['greek_letters', 'own-symbols-greek-letters-symbolic', _('Greek Letters'), - 'sidebar_view.SidebarPageSymbolsList("greek_letters", 25)']) - self.pages.append(['arrows', 'own-symbols-arrows-symbolic', _('Arrows'), - 'sidebar_view.SidebarPageSymbolsList("arrows", 48)']) - self.pages.append(['relations', 'own-symbols-relations-symbolic', _('Relations'), - 'sidebar_view.SidebarPageSymbolsList("relations", 39)']) - self.pages.append(['operators', 'own-symbols-operators-symbolic', _('Operators'), - 'sidebar_view.SidebarPageSymbolsList("operators", 47)']) - self.pages.append(['misc_math', 'own-symbols-misc-math-symbolic', _('Misc. Math'), - 'sidebar_view.SidebarPageSymbolsList("misc_math", 42)']) - self.pages.append(['misc_text', 'own-symbols-misc-text-symbolic', _('Misc. Symbols'), - 'sidebar_view.SidebarPageSymbolsList("misc_text", 38)']) - self.init_pages() - - self.recent = ServiceLocator.get_settings().get_value('app_recent_symbols', 'symbols') - self.recent_details = list() - self.recent_page_size = None - self.update_recent_widget() - - self.view.show_all() - - self.view.vbox.connect('size-allocate', self.on_scroll_or_resize) - self.view.scrolled_window.get_vadjustment().connect('value-changed', self.on_scroll_or_resize) - self.view.next_button.connect('clicked', self.on_next_button_clicked) - self.view.prev_button.connect('clicked', self.on_prev_button_clicked) - - def init_pages(self): - for page in reversed(self.pages): - page_view = eval(page[3]) - label = Gtk.Label(page[2]) - label.set_xalign(0) - label.set_halign(Gtk.Align.START) - label.set_valign(Gtk.Align.START) - label.set_size_request(108, -1) - label.get_style_context().add_class('overlay') - self.view.add_overlay(label) - self.view.reorder_overlay(label, 1) - self.view.set_overlay_pass_through(label, True) - self.view.labels.append(label) - self.view.page_views.append(page_view) - self.view.vbox.pack_end(page_view, False, False, 0) - placeholder = Gtk.Label(page[2]) - placeholder.set_xalign(0) - self.view.vbox.pack_end(placeholder, False, False, 0) - page_view.connect('size-allocate', self.on_page_size_allocate) - page_view.connect('button-press-event', self.on_flowbox_clicked, page_view.symbols) - self.init_symbols_page(page_view) - - def update_recent_widget(self): - for item in [item for item in self.recent]: - self.add_recent_symbol_to_flowbox(item) - self.view.page_view_recent.connect('button-press-event', self.on_recent_widget_clicked) - self.view.page_view_recent.connect('size-allocate', self.on_recent_page_size_allocate) - - def on_recent_widget_clicked(self, flowbox, event): - if event.button != 1: return - - child = flowbox.get_child_at_pos(event.x, event.y) - - if child != None and self.workspace.active_document != None: - self.workspace.get_active_document().content.insert_text_at_cursor(self.recent_details[- child.get_index() - 1][1]) - self.workspace.get_active_document().content.scroll_cursor_onscreen() - self.add_recent_symbol(self.recent[- child.get_index() - 1]) - - return True - - def remove_recent_symbol(self, item): - self.recent.remove(item) - for symbol in [symbol for symbol in self.recent_details]: - if item[1] == symbol[1]: - self.view.page_view_recent.remove(symbol[5]) - self.recent_details.remove(symbol) - - def add_recent_symbol(self, new_item): - for item in [item for item in self.recent]: - if item[1] == new_item[1]: - self.remove_recent_symbol(item) - if len(self.recent) >= 20: - self.remove_recent_symbol(self.recent[0]) - - self.recent.append(new_item) - self.add_recent_symbol_to_flowbox(new_item) - - def add_recent_symbol_to_flowbox(self, item): - (category, command) = item - xml_tree = ET.parse(os.path.join(ServiceLocator.get_resources_path(), 'symbols', category + '.xml')) - xml_root = xml_tree.getroot() - elements = xml_root.findall('./symbol[@command=\'' + command + '\']') - if len(elements) == 0: - self.remove_recent_symbol(item) - else: - attrib = elements[0].attrib - symbol = [attrib['file'].rsplit('.')[0], attrib['command'], attrib.get('package', None), int(attrib.get('original_width', 10)), int(attrib.get('original_height', 10))] - size = max(symbol[3], symbol[4]) - - image = Gtk.Image.new_from_icon_name('sidebar-' + symbol[0] + '-symbolic', 0) - image.set_pixel_size(int(size * 1.5)) - tooltip_text = symbol[1] - if symbol[2] != None: - tooltip_text += ' (' + _('Package') + ': ' + symbol[2] + ')' - image.set_tooltip_text(tooltip_text) - image.show_all() - symbol.append(image) - self.recent_details.append(symbol) - - self.view.page_view_recent.insert(image, 0) - self.view.queue_draw() - - def init_symbols_page(self, page_view): - for symbol in page_view.symbols: - image = symbol[5] - image.set_size_request(page_view.symbol_width + 11, -1) - - def on_scroll_or_resize(self, *args): - scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() - if scrolling_offset == 0: - self.view.prev_button.set_sensitive(False) - else: - self.view.prev_button.set_sensitive(True) - final_label_offset = self.view.vbox.get_allocated_height() - self.view.page_views[0].get_allocated_height() - if scrolling_offset >= final_label_offset: - self.view.next_button.set_sensitive(False) - else: - self.view.next_button.set_sensitive(True) - self.update_labels() - - def update_labels(self): - offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 - scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() - self.view.tabs_box.get_style_context().remove_class('no-border') - for key, page in enumerate(reversed(self.view.page_views)): - label = self.view.labels[len(self.view.page_views) - key - 1] - margin_top = max(0, offset - int(scrolling_offset)) - label.set_margin_top(margin_top) - if margin_top > 0 and margin_top <= label.get_allocated_height(): - self.view.tabs_box.get_style_context().add_class('no-border') - offset += page.get_allocated_height() + label.get_allocated_height() + 1 - - def on_next_button_clicked(self, button): - offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 - scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() - if scrolling_offset < offset: - self.scroll_view(offset) - return - - for key, page in enumerate(reversed(self.view.page_views)): - label = self.view.labels[len(self.view.page_views) - key - 1] - if offset >= scrolling_offset - (page.get_allocated_height() + label.get_allocated_height()): - new_offset = offset + (page.get_allocated_height() + label.get_allocated_height()) + 1 - if key < len(self.view.page_views) - 1: - self.scroll_view(new_offset) - break - offset += page.get_allocated_height() + label.get_allocated_height() + 1 - - def on_prev_button_clicked(self, button): - offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 - old_offset = offset - scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() - - if scrolling_offset <= offset: - self.scroll_view(0) - return - - for key, page in enumerate(reversed(self.view.page_views)): - label = self.view.labels[len(self.view.page_views) - key - 1] - if offset >= scrolling_offset - (page.get_allocated_height() + label.get_allocated_height()): - if offset == int(scrolling_offset): - new_offset = old_offset - else: - new_offset = offset - self.scroll_view(new_offset) - break - old_offset = offset - offset += page.get_allocated_height() + label.get_allocated_height() + 1 - - def on_flowbox_clicked(self, flowbox, event, symbols_list): - if event.button != 1: return - - child = flowbox.get_child_at_pos(event.x, event.y) - - if child != None and self.workspace.active_document != None: - self.workspace.get_active_document().content.insert_text_at_cursor(symbols_list[child.get_index()][1]) - self.workspace.get_active_document().content.scroll_cursor_onscreen() - self.add_recent_symbol((flowbox.symbol_folder, symbols_list[child.get_index()][1])) - - return True - - ''' - *** manage borders of images - ''' - - def on_page_size_allocate(self, symbol_page, allocation, data=None): - if symbol_page.size != (allocation.width, allocation.height): - symbol_page.size = (allocation.width, allocation.height) - - width_with_border = symbol_page.symbol_width + 11 - width_avail = allocation.width - symbols_per_line = int((width_avail) / width_with_border) - - for number, image in enumerate(symbol_page.symbols): - if (number % symbols_per_line) == symbols_per_line - 1: - image[5].get_style_context().add_class('no_right_border') - else: - image[5].get_style_context().remove_class('no_right_border') - - def on_recent_page_size_allocate(self, page, allocation, data=None): - if self.recent_page_size != (allocation.width, allocation.height): - self.recent_page_size = (allocation.width, allocation.height) - - for number, recent_symbol in enumerate(self.recent_details): - image = recent_symbol[5] - image.get_style_context().remove_class('no_right_border') - - for offset in range(max(0, allocation.height) // 20 + 1): - widget = page.get_child_at_pos(allocation.width - 1, offset * 20) - if widget != None: - widget.get_child().get_style_context().add_class('no_right_border') - - def scroll_view(self, position, duration=0.2): - adjustment = self.view.scrolled_window.get_vadjustment() - self.scroll_to = {'position_start': adjustment.get_value(), 'position_end': position, 'time_start': time.time(), 'duration': duration} - self.view.scrolled_window.set_kinetic_scrolling(False) - GObject.timeout_add(15, self.do_scroll) - - def do_scroll(self): - if self.scroll_to != None: - adjustment = self.view.scrolled_window.get_vadjustment() - time_elapsed = time.time() - self.scroll_to['time_start'] - if self.scroll_to['duration'] == 0: - time_elapsed_percent = 1 - else: - time_elapsed_percent = time_elapsed / self.scroll_to['duration'] - if time_elapsed_percent >= 1: - adjustment.set_value(self.scroll_to['position_end']) - self.scroll_to = None - self.view.scrolled_window.set_kinetic_scrolling(True) - return False - else: - adjustment.set_value(self.scroll_to['position_start'] * (1 - self.ease(time_elapsed_percent)) + self.scroll_to['position_end'] * self.ease(time_elapsed_percent)) - return True - return False - - def ease(self, time): return (time - 1)**3 + 1 + self.symbols_page = symbols_page.SymbolsPage(workspace) + self.view.append_page(self.symbols_page.view, Gtk.Label(_('Symbols'))) diff --git a/setzer/workspace/sidebar/sidebar_viewgtk.py b/setzer/workspace/sidebar/sidebar_viewgtk.py index ccc1686e..9b494502 100644 --- a/setzer/workspace/sidebar/sidebar_viewgtk.py +++ b/setzer/workspace/sidebar/sidebar_viewgtk.py @@ -17,121 +17,15 @@ import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gdk from gi.repository import Gtk -from setzer.app.service_locator import ServiceLocator -import xml.etree.ElementTree as ET -import os - - -class Sidebar(Gtk.Overlay): +class Sidebar(Gtk.Notebook): def __init__(self): - Gtk.Overlay.__init__(self) - - self.get_style_context().add_class('sidebar') - - self.vbox_top = Gtk.VBox() - self.scrolled_window = Gtk.ScrolledWindow() - self.vbox = Gtk.VBox() - self.scrolled_window.add(self.vbox) - - self.tabs_box = Gtk.HBox() - self.tabs_box.set_valign(Gtk.Align.START) - self.tabs_box.set_halign(Gtk.Align.FILL) - self.tabs_box.get_style_context().add_class('tabs-box') - - self.tabs = Gtk.Toolbar() - self.tabs.set_style(Gtk.ToolbarStyle.ICONS) - self.tabs.set_orientation(Gtk.Orientation.HORIZONTAL) - self.tabs.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) - - self.prev_button = Gtk.ToolButton() - self.prev_button.set_icon_name('go-up-symbolic') - self.prev_button.set_focus_on_click(False) - self.prev_button.set_tooltip_text(_('Back')) - self.tabs.insert(self.prev_button, -1) - - self.next_button = Gtk.ToolButton() - self.next_button.set_icon_name('go-down-symbolic') - self.next_button.set_focus_on_click(False) - self.next_button.set_tooltip_text(_('Forward')) - self.tabs.insert(self.next_button, -1) - - self.tabs_box.pack_end(self.tabs, False, False, 0) - self.vbox_top.pack_start(self.tabs_box, False, False, 0) - self.vbox_top.pack_start(self.scrolled_window, True, True, 0) - self.add(self.vbox_top) - - self.labels = list() - self.page_views = list() - - self.label_recent = Gtk.Label(_('Recent')) - self.label_recent.set_xalign(0) - self.label_recent.set_halign(Gtk.Align.START) - self.label_recent.set_valign(Gtk.Align.START) - self.label_recent.set_size_request(108, -1) - self.label_recent.get_style_context().add_class('overlay') - self.add_overlay(self.label_recent) - self.reorder_overlay(self.label_recent, 0) - self.set_overlay_pass_through(self.label_recent, True) - - self.page_view_recent = Gtk.FlowBox() - self.page_view_recent.set_homogeneous(False) - self.page_view_recent.set_valign(Gtk.Align.START) - self.page_view_recent.set_max_children_per_line(20) - self.vbox.pack_start(self.page_view_recent, False, False, 0) + Gtk.Notebook.__init__(self) - def do_get_request_mode(self): - return Gtk.SizeRequestMode.CONSTANT_SIZE - - def do_get_preferred_width(self): - return 216, 300 - - -class SidebarPage(Gtk.FlowBox): - - def __init__(self): - Gtk.FlowBox.__init__(self) - - -class SidebarPageSymbolsList(SidebarPage): - - def __init__(self, symbol_folder, symbol_width): - SidebarPage.__init__(self) - - self.symbol_folder = symbol_folder - self.symbol_width = symbol_width - - self.size = None - - # symbols: icon name, latex code - self.symbols = list() - - self.set_homogeneous(False) - self.set_valign(Gtk.Align.START) - self.set_max_children_per_line(20) - - xml_tree = ET.parse(os.path.join(ServiceLocator.get_resources_path(), 'symbols', symbol_folder + '.xml')) - xml_root = xml_tree.getroot() - for symbol_tag in xml_root: - self.symbols.append([symbol_tag.attrib['file'].rsplit('.')[0], symbol_tag.attrib['command'], symbol_tag.attrib.get('package', None), int(symbol_tag.attrib.get('original_width', 10)), int(symbol_tag.attrib.get('original_height', 10))]) - - self.init_symbols_list() - - def init_symbols_list(self): - for symbol in self.symbols: - size = max(symbol[3], symbol[4]) - - image = Gtk.Image.new_from_icon_name('sidebar-' + symbol[0] + '-symbolic', 0) - image.set_pixel_size(int(size * 1.5)) - tooltip_text = symbol[1] - if symbol[2] != None: - tooltip_text += ' (' + _('Package') + ': ' + symbol[2] + ')' - image.set_tooltip_text(tooltip_text) - symbol.append(image) - self.insert(image, -1) + self.get_style_context().add_class('sidebar') + self.set_show_tabs(False) diff --git a/setzer/workspace/sidebar/symbols_page/__init__.py b/setzer/workspace/sidebar/symbols_page/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/setzer/workspace/sidebar/symbols_page/symbols_page.py b/setzer/workspace/sidebar/symbols_page/symbols_page.py new file mode 100644 index 00000000..ee123470 --- /dev/null +++ b/setzer/workspace/sidebar/symbols_page/symbols_page.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +# Copyright (C) 2017, 2018 Robert Griesel +# 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 + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Gio +from gi.repository import GLib +from gi.repository import GObject + +import setzer.workspace.sidebar.symbols_page.symbols_page_viewgtk as symbols_page_view +from setzer.app.service_locator import ServiceLocator +import setzer.helpers.timer as timer + +import math +import time +import xml.etree.ElementTree as ET +import os + + +class SymbolsPage(object): + + def __init__(self, workspace): + self.view = symbols_page_view.SymbolsPageView() + self.workspace = workspace + + self.scroll_to = None + + # tabbed pages: name, icon name, tooltip, widget + self.pages = list() + self.pages.append(['greek_letters', 'own-symbols-greek-letters-symbolic', _('Greek Letters'), + 'symbols_page_view.SidebarPageSymbolsList("greek_letters", 25)']) + self.pages.append(['arrows', 'own-symbols-arrows-symbolic', _('Arrows'), + 'symbols_page_view.SidebarPageSymbolsList("arrows", 48)']) + self.pages.append(['relations', 'own-symbols-relations-symbolic', _('Relations'), + 'symbols_page_view.SidebarPageSymbolsList("relations", 39)']) + self.pages.append(['operators', 'own-symbols-operators-symbolic', _('Operators'), + 'symbols_page_view.SidebarPageSymbolsList("operators", 47)']) + self.pages.append(['misc_math', 'own-symbols-misc-math-symbolic', _('Misc. Math'), + 'symbols_page_view.SidebarPageSymbolsList("misc_math", 42)']) + self.pages.append(['misc_text', 'own-symbols-misc-text-symbolic', _('Misc. Symbols'), + 'symbols_page_view.SidebarPageSymbolsList("misc_text", 38)']) + self.init_pages() + + self.recent = ServiceLocator.get_settings().get_value('app_recent_symbols', 'symbols') + self.recent_details = list() + self.recent_page_size = None + self.update_recent_widget() + + self.view.show_all() + + self.view.vbox.connect('size-allocate', self.on_scroll_or_resize) + self.view.scrolled_window.get_vadjustment().connect('value-changed', self.on_scroll_or_resize) + self.view.next_button.connect('clicked', self.on_next_button_clicked) + self.view.prev_button.connect('clicked', self.on_prev_button_clicked) + self.view.search_button.connect('toggled', self.on_search_button_toggled) + self.view.search_close_button.connect('clicked', self.on_search_close_button_clicked) + self.view.search_entry.connect('stop-search', self.on_search_stopped) + self.view.search_entry.connect('search-changed', self.on_search_changed) + + self.update_symbols() + + def init_pages(self): + for page in reversed(self.pages): + page_view = eval(page[3]) + label = Gtk.Label(page[2]) + label.set_xalign(0) + label.set_halign(Gtk.Align.START) + label.set_valign(Gtk.Align.START) + label.set_size_request(108, -1) + label.get_style_context().add_class('overlay') + self.view.overlay.add_overlay(label) + self.view.overlay.reorder_overlay(label, 1) + self.view.overlay.set_overlay_pass_through(label, True) + self.view.labels.append(label) + self.view.page_views.append(page_view) + self.view.vbox.pack_end(page_view, False, False, 0) + placeholder = Gtk.Label(page[2]) + placeholder.set_xalign(0) + self.view.vbox.pack_end(placeholder, False, False, 0) + page_view.connect('size-allocate', self.on_page_size_allocate) + page_view.connect('button-press-event', self.on_flowbox_clicked, page_view) + self.init_symbols_page(page_view) + + def update_recent_widget(self): + for item in [item for item in self.recent]: + self.add_recent_symbol_to_flowbox(item) + self.view.page_view_recent.connect('button-press-event', self.on_recent_widget_clicked) + self.view.page_view_recent.connect('size-allocate', self.on_recent_page_size_allocate) + + def on_recent_widget_clicked(self, flowbox, event): + if event.button != 1: return + + child = flowbox.get_child_at_pos(event.x, event.y) + + if child != None and self.workspace.active_document != None: + self.workspace.get_active_document().content.insert_text_at_cursor(self.recent_details[- child.get_index() - 1][1]) + self.workspace.get_active_document().content.scroll_cursor_onscreen() + self.add_recent_symbol(self.recent[- child.get_index() - 1]) + + return True + + def remove_recent_symbol(self, item): + self.recent.remove(item) + for symbol in [symbol for symbol in self.recent_details]: + if item[1] == symbol[1]: + self.view.page_view_recent.remove(symbol[5]) + self.recent_details.remove(symbol) + + def add_recent_symbol(self, new_item): + for item in [item for item in self.recent]: + if item[1] == new_item[1]: + self.remove_recent_symbol(item) + if len(self.recent) >= 20: + self.remove_recent_symbol(self.recent[0]) + + self.recent.append(new_item) + self.add_recent_symbol_to_flowbox(new_item) + + def add_recent_symbol_to_flowbox(self, item): + (category, command) = item + xml_tree = ET.parse(os.path.join(ServiceLocator.get_resources_path(), 'symbols', category + '.xml')) + xml_root = xml_tree.getroot() + elements = xml_root.findall('./symbol[@command=\'' + command + '\']') + if len(elements) == 0: + self.remove_recent_symbol(item) + else: + attrib = elements[0].attrib + symbol = [attrib['file'].rsplit('.')[0], attrib['command'], attrib.get('package', None), int(attrib.get('original_width', 10)), int(attrib.get('original_height', 10))] + size = max(symbol[3], symbol[4]) + + image = Gtk.Image.new_from_icon_name('sidebar-' + symbol[0] + '-symbolic', 0) + image.set_pixel_size(int(size * 1.5)) + tooltip_text = symbol[1] + if symbol[2] != None: + tooltip_text += ' (' + _('Package') + ': ' + symbol[2] + ')' + image.set_tooltip_text(tooltip_text) + image.show_all() + symbol.append(image) + self.recent_details.append(symbol) + + self.view.page_view_recent.insert(image, 0) + self.view.queue_draw() + + def init_symbols_page(self, page_view): + for symbol in page_view.symbols: + image = symbol[5] + image.set_size_request(page_view.symbol_width + 11, -1) + + def on_scroll_or_resize(self, *args): + scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() + if scrolling_offset == 0: + self.view.prev_button.set_sensitive(False) + else: + self.view.prev_button.set_sensitive(True) + final_label_offset = self.view.vbox.get_allocated_height() - self.view.page_views[0].get_allocated_height() + if scrolling_offset >= final_label_offset: + self.view.next_button.set_sensitive(False) + else: + self.view.next_button.set_sensitive(True) + self.update_labels() + + def update_labels(self): + offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 + scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() + self.view.tabs_box.get_style_context().remove_class('no-border') + for key, page in enumerate(reversed(self.view.page_views)): + label = self.view.labels[len(self.view.page_views) - key - 1] + margin_top = max(0, offset - int(scrolling_offset)) + label.set_margin_top(margin_top) + if margin_top > 0 and margin_top <= label.get_allocated_height(): + self.view.tabs_box.get_style_context().add_class('no-border') + offset += page.get_allocated_height() + label.get_allocated_height() + 1 + + def on_next_button_clicked(self, button): + offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 + scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() + if scrolling_offset < offset: + self.scroll_view(offset) + return + + for key, page in enumerate(reversed(self.view.page_views)): + label = self.view.labels[len(self.view.page_views) - key - 1] + if offset >= scrolling_offset - (page.get_allocated_height() + label.get_allocated_height()): + new_offset = offset + (page.get_allocated_height() + label.get_allocated_height()) + 1 + if key < len(self.view.page_views) - 1: + self.scroll_view(new_offset) + break + offset += page.get_allocated_height() + label.get_allocated_height() + 1 + + def on_prev_button_clicked(self, button): + offset = self.view.page_view_recent.get_allocated_height() + self.view.label_recent.get_allocated_height() + 1 + old_offset = offset + scrolling_offset = self.view.scrolled_window.get_vadjustment().get_value() + + if scrolling_offset <= offset: + self.scroll_view(0) + return + + for key, page in enumerate(reversed(self.view.page_views)): + label = self.view.labels[len(self.view.page_views) - key - 1] + if offset >= scrolling_offset - (page.get_allocated_height() + label.get_allocated_height()): + if offset == int(scrolling_offset): + new_offset = old_offset + else: + new_offset = offset + self.scroll_view(new_offset) + break + old_offset = offset + offset += page.get_allocated_height() + label.get_allocated_height() + 1 + + def on_search_button_toggled(self, button): + if button.get_active(): + self.view.search_entry.set_text('') + self.view.search_revealer.set_reveal_child(True) + self.view.search_entry.grab_focus() + else: + self.view.search_entry.set_text('') + self.view.search_revealer.set_reveal_child(False) + document = self.workspace.get_active_document() + if document != None: + document.content.source_view.grab_focus() + + def on_search_close_button_clicked(self, button): + self.view.search_button.set_active(False) + + def on_search_stopped(self, entry): + self.view.search_button.set_active(False) + + def on_search_changed(self, entry): + self.update_symbols() + + def update_symbols(self): + search_words = self.view.search_entry.get_text().split() + for page in reversed(self.view.page_views): + for symbol in page.visible_symbols: + page.remove(symbol[5]) + page.visible_symbols = [] + + for symbol in page.symbols: + image = symbol[5] + symbol_found = True + for word in search_words: + if symbol[0].find(word) == -1: + symbol_found = False + if symbol_found: + page.visible_symbols.append(symbol) + page.insert(image, -1) + page.show_all() + self.update_borders(page, page.get_allocated_width()) + + def on_flowbox_clicked(self, flowbox, event, page): + if event.button != 1: return + + child = flowbox.get_child_at_pos(event.x, event.y) + + if child != None and self.workspace.active_document != None: + self.workspace.get_active_document().content.insert_text_at_cursor(page.visible_symbols[child.get_index()][1]) + self.workspace.get_active_document().content.scroll_cursor_onscreen() + self.add_recent_symbol((flowbox.symbol_folder, page.visible_symbols[child.get_index()][1])) + + return True + + ''' + *** manage borders of images + ''' + + def on_page_size_allocate(self, symbol_page, allocation, data=None): + if symbol_page.size != (allocation.width, allocation.height): + symbol_page.size = (allocation.width, allocation.height) + self.update_borders(symbol_page, allocation.width) + + def update_borders(self, page, width_available): + width_with_border = page.symbol_width + 11 + symbols_per_line = int((width_available) / width_with_border) + + if symbols_per_line == 0 or len(page.visible_symbols) == 0: + return + + for number, image in enumerate(page.visible_symbols): + if (number % symbols_per_line) == symbols_per_line - 1: + image[5].get_style_context().add_class('no_right_border') + else: + image[5].get_style_context().remove_class('no_right_border') + + def on_recent_page_size_allocate(self, page, allocation, data=None): + if self.recent_page_size != (allocation.width, allocation.height): + self.recent_page_size = (allocation.width, allocation.height) + + for number, recent_symbol in enumerate(self.recent_details): + image = recent_symbol[5] + image.get_style_context().remove_class('no_right_border') + + for offset in range(max(0, allocation.height) // 20 + 1): + widget = page.get_child_at_pos(allocation.width - 1, offset * 20) + if widget != None: + widget.get_child().get_style_context().add_class('no_right_border') + + def scroll_view(self, position, duration=0.2): + adjustment = self.view.scrolled_window.get_vadjustment() + self.scroll_to = {'position_start': adjustment.get_value(), 'position_end': position, 'time_start': time.time(), 'duration': duration} + self.view.scrolled_window.set_kinetic_scrolling(False) + GObject.timeout_add(15, self.do_scroll) + + def do_scroll(self): + if self.scroll_to != None: + adjustment = self.view.scrolled_window.get_vadjustment() + time_elapsed = time.time() - self.scroll_to['time_start'] + if self.scroll_to['duration'] == 0: + time_elapsed_percent = 1 + else: + time_elapsed_percent = time_elapsed / self.scroll_to['duration'] + if time_elapsed_percent >= 1: + adjustment.set_value(self.scroll_to['position_end']) + self.scroll_to = None + self.view.scrolled_window.set_kinetic_scrolling(True) + return False + else: + adjustment.set_value(self.scroll_to['position_start'] * (1 - self.ease(time_elapsed_percent)) + self.scroll_to['position_end'] * self.ease(time_elapsed_percent)) + return True + return False + + def ease(self, time): return (time - 1)**3 + 1 + + diff --git a/setzer/workspace/sidebar/symbols_page/symbols_page_viewgtk.py b/setzer/workspace/sidebar/symbols_page/symbols_page_viewgtk.py new file mode 100644 index 00000000..4760110c --- /dev/null +++ b/setzer/workspace/sidebar/symbols_page/symbols_page_viewgtk.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +# Copyright (C) 2017, 2018 Robert Griesel +# 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 + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gdk +from gi.repository import Gtk + +from setzer.app.service_locator import ServiceLocator + +import xml.etree.ElementTree as ET +import os + + +class SymbolsPageView(Gtk.VBox): + + def __init__(self): + Gtk.VBox.__init__(self) + + self.get_style_context().add_class('sidebar-symbols') + + self.overlay = Gtk.Overlay() + self.vbox_top = Gtk.VBox() + self.scrolled_window = Gtk.ScrolledWindow() + self.vbox = Gtk.VBox() + self.scrolled_window.add(self.vbox) + + self.tabs_box = Gtk.HBox() + self.tabs_box.set_valign(Gtk.Align.START) + self.tabs_box.set_halign(Gtk.Align.FILL) + self.tabs_box.get_style_context().add_class('tabs-box') + + self.tabs = Gtk.Toolbar() + self.tabs.set_style(Gtk.ToolbarStyle.ICONS) + self.tabs.set_orientation(Gtk.Orientation.HORIZONTAL) + self.tabs.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) + + self.prev_button = Gtk.ToolButton() + self.prev_button.set_icon_name('go-up-symbolic') + self.prev_button.set_focus_on_click(False) + self.prev_button.set_tooltip_text(_('Back')) + self.tabs.insert(self.prev_button, -1) + + self.next_button = Gtk.ToolButton() + self.next_button.set_icon_name('go-down-symbolic') + self.next_button.set_focus_on_click(False) + self.next_button.set_tooltip_text(_('Forward')) + self.tabs.insert(self.next_button, -1) + + self.search_button = Gtk.ToggleToolButton() + self.search_button.set_icon_name('edit-find-symbolic') + self.search_button.set_focus_on_click(False) + self.search_button.set_tooltip_text(_('Find')) + self.tabs.insert(self.search_button, -1) + + self.search_revealer = Gtk.Revealer() + self.search_box = Gtk.HBox() + self.search_box.get_style_context().add_class('search_bar') + + self.search_entry = Gtk.SearchEntry() + self.search_box.pack_start(self.search_entry, True, True, 0) + + self.search_close_button = Gtk.Button.new_from_icon_name('window-close-symbolic', Gtk.IconSize.MENU) + self.search_close_button.get_style_context().add_class('flat') + self.search_close_button.set_can_focus(False) + self.search_box.pack_start(self.search_close_button, False, False, 0) + + self.search_revealer.add(self.search_box) + + self.tabs_box.pack_end(self.tabs, False, False, 0) + self.vbox_top.pack_start(self.tabs_box, False, False, 0) + self.vbox_top.pack_start(self.scrolled_window, True, True, 0) + self.overlay.add(self.vbox_top) + self.pack_start(self.overlay, True, True, 0) + self.pack_start(self.search_revealer, False, False, 0) + + self.labels = list() + self.page_views = list() + + self.label_recent = Gtk.Label(_('Recent')) + self.label_recent.set_xalign(0) + self.label_recent.set_halign(Gtk.Align.START) + self.label_recent.set_valign(Gtk.Align.START) + self.label_recent.set_size_request(108, -1) + self.label_recent.get_style_context().add_class('overlay') + self.overlay.add_overlay(self.label_recent) + self.overlay.reorder_overlay(self.label_recent, 0) + self.overlay.set_overlay_pass_through(self.label_recent, True) + + self.page_view_recent = Gtk.FlowBox() + self.page_view_recent.set_homogeneous(False) + self.page_view_recent.set_valign(Gtk.Align.START) + self.page_view_recent.set_max_children_per_line(20) + self.vbox.pack_start(self.page_view_recent, False, False, 0) + + def do_get_request_mode(self): + return Gtk.SizeRequestMode.CONSTANT_SIZE + + def do_get_preferred_width(self): + return 234, 300 + + +class SidebarPageSymbolsList(Gtk.FlowBox): + + def __init__(self, symbol_folder, symbol_width): + Gtk.FlowBox.__init__(self) + + self.symbol_folder = symbol_folder + self.symbol_width = symbol_width + + self.size = None + + # symbols: icon name, latex code + self.symbols = list() + self.visible_symbols = list() + + self.set_homogeneous(False) + self.set_valign(Gtk.Align.START) + self.set_max_children_per_line(20) + + xml_tree = ET.parse(os.path.join(ServiceLocator.get_resources_path(), 'symbols', symbol_folder + '.xml')) + xml_root = xml_tree.getroot() + for symbol_tag in xml_root: + self.symbols.append([symbol_tag.attrib['file'].rsplit('.')[0], symbol_tag.attrib['command'], symbol_tag.attrib.get('package', None), int(symbol_tag.attrib.get('original_width', 10)), int(symbol_tag.attrib.get('original_height', 10))]) + + self.init_symbols_list() + + def init_symbols_list(self): + for symbol in self.symbols: + size = max(symbol[3], symbol[4]) + + image = Gtk.Image.new_from_icon_name('sidebar-' + symbol[0] + '-symbolic', 0) + image.set_pixel_size(int(size * 1.5)) + tooltip_text = symbol[1] + if symbol[2] != None: + tooltip_text += ' (' + _('Package') + ': ' + symbol[2] + ')' + image.set_tooltip_text(tooltip_text) + symbol.append(image) + self.visible_symbols.append(symbol) + self.insert(image, -1) + +