Пример #1
0
    def __init__(self, ui, plugin, pageview):
        Dialog.__init__(
            self,
            ui,
            _('Insert Symbol'),  # T: Dialog title
            button=(_('_Insert'), 'gtk-ok'),  # T: Button label
            defaultwindowsize=(350, 400))
        self.plugin = plugin
        self.pageview = pageview
        if not plugin.symbols:
            plugin.load_file()

        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False)

        # TODO make this iconview single-click
        model = gtk.ListStore(str, str)  # text, shortcut
        self.iconview = gtk.IconView(model)
        self.iconview.set_text_column(0)
        self.iconview.set_column_spacing(0)
        self.iconview.set_row_spacing(0)
        if gtk.gtk_version >= (2, 12, 0):
            self.iconview.set_property('has-tooltip', True)
            self.iconview.connect('query-tooltip', self.on_query_tooltip)
        self.iconview.connect('item-activated', self.on_activated)

        self.vbox.add(ScrolledWindow(self.iconview))

        button = gtk.Button(stock=gtk.STOCK_EDIT)
        button.connect('clicked', self.on_edit)
        self.action_area.add(button)
        self.action_area.reorder_child(button, 0)

        self.load_symbols()
Пример #2
0
    def __init__(self, pageview, preferences):
        """Initialize the Input Box with options."""
        Dialog.__init__(
            self,
            pageview,
            _('Search in Zotero'),  # T: Dialog title
            button=_('_GO'),  # T: Button label
            defaultwindowsize=(350, 200))

        self.pageview = pageview
        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False, True, 0)
        self.preferences = preferences
        first = None
        options = [
            "Search in Title, Author and Date",
            "Search in All Fields and Tags", "Search Everywhere"
        ]
        for text in options:
            self.radio = Gtk.RadioButton.new_with_mnemonic_from_widget(
                first, text)
            if not first:
                first = self.radio
            self.vbox.pack_start(self.radio, False, True, 0)
            self.radio.show()
Пример #3
0
    def __init__(self, tasksview, opener, properties, uistate):
        GObject.GObject.__init__(self)
        self.uistate = uistate
        self.uistate.setdefault('only_show_act', False)
        self.uistate.setdefault('show_flatlist', False)

        self.task_list = TaskListTreeView(
            tasksview,
            opener,
            _parse_task_labels(properties['labels']),
            nonactionable_tags=_parse_task_labels(
                properties['nonactionable_tags']),
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=properties['tag_by_page'],
            use_workweek=properties['use_workweek'],
            compact=True,
            flatlist=self.uistate['show_flatlist'],
        )
        self.task_list.connect('populate-popup', self.on_populate_popup)
        self.task_list.set_headers_visible(True)

        self.connectto(properties, 'changed', self.on_properties_changed)

        self.filter_entry = InputEntry(placeholder_text=_(
            'Filter'))  # T: label for filtering/searching tasks
        self.filter_entry.set_icon_to_clear()
        filter_cb = DelayedCallback(
            500,
            lambda o: self.task_list.set_filter(self.filter_entry.get_text()))
        self.filter_entry.connect('changed', filter_cb)

        self.pack_start(ScrolledWindow(self.task_list), True, True, 0)
        self.pack_end(self.filter_entry, False, True, 0)
Пример #4
0
    def __init__(self, parent, file):
        title = _('Rename file')  # T: dialog title
        Dialog.__init__(self, parent, title)

        assert isinstance(file,
                          LocalFile) and not isinstance(file, LocalFolder)
        self.old_file = file
        self.new_file = file

        # Add field for entering new filename.
        self.txt_filename = InputEntry()
        self.txt_filename.set_text(self.old_file.basename)
        self.txt_filename.set_activates_default(
            True)  # Make ENTER key press trigger the OK button.
        self.txt_filename.connect("changed", self.do_validate, parent)

        # Add field for showing hints when an error occurs (e.g. filename already exists).
        self.txt_error = Gtk.Label()
        self.txt_error.set_visible(False)

        # Add ok button.
        self.btn_ok = self.get_widget_for_response(
            response_id=Gtk.ResponseType.OK)
        self.btn_ok.set_can_default(True)
        self.btn_ok.grab_default()
        self.btn_ok.set_sensitive(False)

        # Configure dialog.
        self.set_modal(True)
        self.set_default_size(380, 100)
        self.vbox.pack_start(self.txt_filename, False, True, 0)
        self.vbox.pack_start(self.txt_error, False, True, 0)

        # Set focus to search field
        self.txt_filename.grab_focus()
Пример #5
0
    def __init__(self, tasksview, opener, preferences, uistate):
        gtk.VBox.__init__(self)
        self.uistate = uistate
        self.uistate.setdefault('only_show_act', False)
        self.uistate.setdefault('show_flatlist', False)

        column_layout=TaskListTreeView.COMPACT_COLUMN_LAYOUT_WITH_DUE \
         if preferences['with_due'] else TaskListTreeView.COMPACT_COLUMN_LAYOUT
        self.task_list = TaskListTreeView(
            tasksview,
            opener,
            _parse_task_labels(preferences['labels']),
            nonactionable_tags=_parse_task_labels(
                preferences['nonactionable_tags']),
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=preferences['tag_by_page'],
            use_workweek=preferences['use_workweek'],
            column_layout=column_layout,
            flatlist=self.uistate['show_flatlist'],
        )
        self.task_list.connect('populate-popup', self.on_populate_popup)
        self.task_list.set_headers_visible(True)

        self.filter_entry = InputEntry(placeholder_text=_(
            'Filter'))  # T: label for filtering/searching tasks
        self.filter_entry.set_icon_to_clear()
        filter_cb = DelayedCallback(
            500,
            lambda o: self.task_list.set_filter(self.filter_entry.get_text()))
        self.filter_entry.connect('changed', filter_cb)

        self.pack_start(ScrolledWindow(self.task_list))
        self.pack_end(self.filter_entry, False)
Пример #6
0
    def __init__(self, parent, plugin, pageview):
        Dialog.__init__(
            self,
            parent,
            _('Insert Symbol'),  # T: Dialog title
            button=_('_Insert'),  # T: Button label
            defaultwindowsize=(350, 400))
        self.plugin = plugin
        self.pageview = pageview
        if not plugin.symbols:
            plugin.load_file()

        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False, True, 0)

        model = Gtk.ListStore(str, str)  # text, shortcut
        self.iconview = Gtk.IconView(model)
        self.iconview.set_text_column(0)
        self.iconview.set_column_spacing(0)
        self.iconview.set_row_spacing(0)
        self.iconview.set_property('has-tooltip', True)
        self.iconview.set_property('activate-on-single-click', True)
        self.iconview.connect('query-tooltip', self.on_query_tooltip)
        self.iconview.connect('item-activated', self.on_activated)

        swindow = ScrolledWindow(self.iconview)
        self.vbox.pack_start(swindow, True, True, 0)

        button = Gtk.Button.new_with_mnemonic(_('_Edit'))  # T: Button label
        button.connect('clicked', self.on_edit)
        self.action_area.add(button)
        self.action_area.reorder_child(button, 0)

        self.load_symbols()
Пример #7
0
    def __init__(self, window):
        Dialog.__init__(
            self,
            window,
            _('Search'),  # T: Dialog title
            buttons=gtk.BUTTONS_CLOSE,
            help='Help:Searching',
            defaultwindowsize=(400, 300))
        self.app_window = window

        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        hbox.pack_start(gtk.Label(_('Search') + ': '), False)  # T: input label
        self.query_entry = InputEntry()
        hbox.add(self.query_entry)
        self.search_button = gtk.Button(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False)

        if gtk.gtk_version >= (2, 20) \
        and gtk.pygtk_version >= (2, 22): # update in pygtk was later
            self.spinner = gtk.Spinner()
            hbox.pack_start(self.spinner, False)
        else:
            self.spinner = None

        self.cancel_button = gtk.Button(stock=gtk.STOCK_STOP)
        hbox.pack_start(self.cancel_button, False)
        self._set_state(self.READY)

        help_text = _('For advanced search you can use operators like\n'
                      'AND, OR and NOT. See the help page for more details.'
                      )  # T: help text for the search dialog
        if gtk.gtk_version >= (2, 12) \
        and gtk.pygtk_version >= (2, 12):
            self.query_entry.set_tooltip_text(help_text)
        else:
            tooltips = gtk.Tooltips()
            tooltips.set_tip(self.query_entry, help_text)

        self.namespacecheckbox = gtk.CheckButton(
            _('Limit search to the current page and sub-pages'))
        # T: checkbox option in search dialog
        self.vbox.pack_start(self.namespacecheckbox, False)

        # TODO advanced query editor
        # TODO checkbox _('Match c_ase')
        # TODO checkbox _('Whole _word')

        self.results_treeview = SearchResultsTreeView(self.app_window)
        self.vbox.add(ScrolledWindow(self.results_treeview))

        self.search_button.connect_object('clicked', self.__class__._search,
                                          self)
        self.cancel_button.connect_object('clicked', self.__class__._cancel,
                                          self)
        self.query_entry.connect_object('activate', self.__class__._search,
                                        self)
    def __init__(self, widget, notebook, page, navigation):
        Dialog.__init__(
            self,
            widget,
            _('Search'),  # T: Dialog title
            buttons=Gtk.ButtonsType.CLOSE,
            help='Help:Searching',
            defaultwindowsize=(400, 300))
        self.page = page

        hbox = Gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False, True, 0)
        search_label = Gtk.Label.new_with_mnemonic(_('_Search') + ': ')
        hbox.pack_start(search_label, False, True, 0)  # T: input label
        self.query_entry = InputEntry()
        hbox.add(self.query_entry)
        search_label.set_mnemonic_widget(self.query_entry)
        self.search_button = Gtk.Button.new_with_mnemonic(
            _('_Find'))  # T: Button label
        hbox.pack_start(self.search_button, False, True, 0)

        self.spinner = Gtk.Spinner()
        hbox.pack_start(self.spinner, False, True, 0)

        self.cancel_button = Gtk.Button.new_with_mnemonic(
            _('_Cancel'))  # T: Button label
        hbox.pack_start(self.cancel_button, False, True, 0)
        self._set_state(self.READY)

        help_text = _('For advanced search you can use operators like\n'
                      'AND, OR and NOT. See the help page for more details.'
                      )  # T: help text for the search dialog
        self.query_entry.set_tooltip_text(help_text)

        self.namespacecheckbox = Gtk.CheckButton.new_with_mnemonic(
            _('_Limit search to the current page and sub-pages'))
        # T: checkbox option in search dialog
        if page is not None:
            self.vbox.pack_start(self.namespacecheckbox, False, True, 0)

        # TODO advanced query editor
        # TODO checkbox _('Match c_ase')
        # TODO checkbox _('Whole _word')

        self.results_treeview = SearchResultsTreeView(notebook, navigation)
        self.vbox.pack_start(ScrolledWindow(self.results_treeview), True, True,
                             0)

        self.search_button.connect_object('clicked', self.__class__._search,
                                          self)
        self.cancel_button.connect_object('clicked', self.__class__._cancel,
                                          self)
        self.query_entry.connect_object('activate', self.__class__._search,
                                        self)
Пример #9
0
	def __init__(self, parent):
		Dialog.__init__(self, parent, _('Insert Code Block')) # T: dialog title
		self.result = (None, None, None)
		self.uistate.define(id=String(None))
		self.uistate.define(lang=String(None))
		self.uistate.define(line_numbers=Boolean(True))

		grid = Gtk.Grid()
		grid.set_column_spacing(5)
		grid.set_row_spacing(5)

		label = Gtk.Label(_('Syntax') + ':') # T: input label
		grid.add(label)

		self.combobox = Gtk.ComboBox.new_with_model_and_entry(self.init_combobox_model())
		self.combobox.set_entry_text_column(0)
		entry = self.combobox.get_child()
		entry.set_activates_default(True)  # Pressing enter will activate the default button (here: ok-button)

		completion = Gtk.EntryCompletion()
		completion.set_model(self.init_autocomplete_model())
		completion.set_text_column(0)
		completion.set_minimum_key_length(0)
		entry.set_completion(completion)

		defaultlang = self.init_default_language()
		if defaultlang:
			entry.set_text(defaultlang)

		self.combobox.connect("changed", self.on_combobox_changed)

		grid.attach(self.combobox, 1, 0, 1, 1)

		label = Gtk.Label(_('Id') + ':') # T: input label for object ID
		grid.attach(label, 0, 1, 1, 1)
		self.entry = InputEntry()
		grid.attach(self.entry, 1, 1, 1, 1)

		self.checkbox = Gtk.CheckButton(_('Display line numbers')) # T: input checkbox
		self.checkbox.set_active(self.uistate['line_numbers'])
		grid.attach(self.checkbox, 1, 2, 1, 1)

		self.vbox.add(grid)

		# Set ok button as default.
		self.btn_ok = self.get_widget_for_response(response_id=Gtk.ResponseType.OK)
		self.btn_ok.set_can_default(True)
		self.btn_ok.grab_default()
		self.btn_ok.set_sensitive(defaultlang is not None)
Пример #10
0
class TaskListWidget(Gtk.VBox, TaskListWidgetMixin, WindowSidePaneWidget):

    title = _('Tas_ks')  # T: tab label for side pane

    def __init__(self, tasksview, opener, properties, with_due, uistate):
        GObject.GObject.__init__(self)
        self.uistate = uistate
        self.uistate.setdefault('only_show_act', False)
        self.uistate.setdefault('show_flatlist', False)

        column_layout=TaskListTreeView.COMPACT_COLUMN_LAYOUT_WITH_DUE \
         if with_due else TaskListTreeView.COMPACT_COLUMN_LAYOUT
        self.task_list = TaskListTreeView(
            tasksview,
            opener,
            _parse_task_labels(properties['labels']),
            nonactionable_tags=_parse_task_labels(
                properties['nonactionable_tags']),
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=properties['tag_by_page'],
            use_workweek=properties['use_workweek'],
            column_layout=column_layout,
            flatlist=self.uistate['show_flatlist'],
        )
        self.task_list.connect('populate-popup', self.on_populate_popup)
        self.task_list.set_headers_visible(True)

        self.connectto(properties, 'changed', self.on_properties_changed)

        self.filter_entry = InputEntry(placeholder_text=_(
            'Filter'))  # T: label for filtering/searching tasks
        self.filter_entry.set_icon_to_clear()
        filter_cb = DelayedCallback(
            500,
            lambda o: self.task_list.set_filter(self.filter_entry.get_text()))
        self.filter_entry.connect('changed', filter_cb)

        self.pack_start(ScrolledWindow(self.task_list), True, True, 0)
        self.pack_end(self.filter_entry, False, True, 0)

    def on_properties_changed(self, properties):
        self.task_list.update_properties(
            task_labels=_parse_task_labels(properties['labels']),
            nonactionable_tags=_parse_task_labels(
                properties['nonactionable_tags']),
            tag_by_page=properties['tag_by_page'],
            use_workweek=properties['use_workweek'],
        )
Пример #11
0
    def __init__(self, ui, plugin):
        Dialog.__init__(self, ui, _('Insert Symbol'), # T: Dialog title
            button=(_('_Insert'), 'gtk-ok'),  # T: Button label
            defaultwindowsize=(350, 400) )
        self.plugin = plugin

        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False)

        # TODO make this iconview single-click
        model = gtk.ListStore(str, str) # text, shortcut
        self.iconview = gtk.IconView(model)
        self.iconview.set_text_column(0)
        self.iconview.set_column_spacing(0)
        self.iconview.set_row_spacing(0)
        if gtk.gtk_version >= (2, 12, 0):
            self.iconview.set_property('has-tooltip', True)
            self.iconview.connect('query-tooltip', self.on_query_tooltip)
        self.iconview.connect('item-activated', self.on_activated)

        self.vbox.add(ScrolledWindow(self.iconview))

        button = gtk.Button(stock=gtk.STOCK_EDIT)
        button.connect('clicked', self.on_edit)
        self.action_area.add(button)
        self.action_area.reorder_child(button, 0)

        self.load_symbols()
Пример #12
0
    def __init__(self, plugin):
        if ui_environment['platform'] == 'maemo':
            defaultsize = (800, 480)
        else:
            defaultsize = (900, 500)

        Dialog.__init__(self, plugin.ui, _('Qda Codes'),  # T: dialog title
            buttons=gtk.BUTTONS_CLOSE, help=':Plugins:Qda Codes',
            defaultwindowsize=defaultsize)

        self.plugin = plugin
        if ui_environment['platform'] == 'maemo':
            self.resize(800, 480)
            # Force maximum dialog size under maemo, otherwise
            # we'll end with a too small dialog and no way to resize it
            
        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        self.hpane = HPaned()
        self.uistate.setdefault('hpane_pos', 75)
        self.hpane.set_position(self.uistate['hpane_pos'])
        self.vbox.add(self.hpane)

        # Code list
        self.qda_codes = QdaCodesTreeView(self.ui, plugin)
        self.qda_codes.set_headers_visible(True)  # Fix for maemo
        self.hpane.add2(ScrolledWindow(self.qda_codes))

        # Tag list
        self.tag_list = TagListTreeView(self.qda_codes)
        self.hpane.add1(ScrolledWindow(self.tag_list))

        # Filter input
        hbox.pack_start(gtk.Label(_('Filter') + ': '), False)  # T: Input label
        filter_entry = InputEntry()
        filter_entry.set_icon_to_clear()
        hbox.pack_start(filter_entry, False)
        filter_cb = DelayedCallback(500, lambda o: self.qda_codes.set_filter(filter_entry.get_text()))
        filter_entry.connect('changed', filter_cb)

        # Statistics label
        # self.statistics_label = gtk.Label()
        # hbox.pack_end(self.statistics_label, False)

        export_button = gtk.Button(_('_Export') )
        export_button.connect_object('clicked', self.qda_codes.get_data_as_page, self.qda_codes )
        hbox.pack_end(export_button, False )


        def on_qdacodes_changed(o):
            self.qda_codes.refresh()
            self.tag_list.refresh(self.qda_codes)
            # set_statistics()

        callback = DelayedCallback(10, on_qdacodes_changed)
            # Don't really care about the delay, but want to
            # make it less blocking - should be async preferably
            # now it is at least on idle

        self.connectto(plugin, 'qdacodes-changed', callback)
Пример #13
0
	def __init__(self, ui):
		Dialog.__init__(self, ui, _('Search'), # T: Dialog title
			buttons=gtk.BUTTONS_CLOSE, help='Help:Searching',
			defaultwindowsize=(400, 300)
		)

		hbox = gtk.HBox(spacing=5)
		self.vbox.pack_start(hbox, False)
		hbox.pack_start(gtk.Label(_('Search')+': '), False) # T: input label
		self.query_entry = InputEntry()
		hbox.add(self.query_entry)
		self.search_button = gtk.Button(stock=gtk.STOCK_FIND)
		hbox.pack_start(self.search_button, False)

		if gtk.gtk_version >= (2, 20) \
		and gtk.pygtk_version >= (2, 22): # update in pygtk was later
			self.spinner = gtk.Spinner()
			hbox.pack_start(self.spinner, False)
		else:
			self.spinner = None

		self.cancel_button = gtk.Button(stock=gtk.STOCK_STOP)
		hbox.pack_start(self.cancel_button, False)
		self._set_state(self.READY)

		help_text = _(
			'For advanced search you can use operators like\n'
			'AND, OR and NOT. See the help page for more details.'
		) # T: help text for the search dialog
		if gtk.gtk_version >= (2, 12) \
		and gtk.pygtk_version >= (2, 12):
			self.query_entry.set_tooltip_text(help_text)
		else:
			tooltips = gtk.Tooltips()
			tooltips.set_tip(self.query_entry, help_text)

		self.namespacecheckbox = gtk.CheckButton(_('Limit search to the current page and sub-pages'))
			# T: checkbox option in search dialog
		self.vbox.pack_start(self.namespacecheckbox, False)

		# TODO advanced query editor
		# TODO checkbox _('Match c_ase')
		# TODO checkbox _('Whole _word')

		self.results_treeview = SearchResultsTreeView(self.ui)
		self.vbox.add(ScrolledWindow(self.results_treeview))

		self.search_button.connect_object('clicked', self.__class__._search, self)
		self.cancel_button.connect_object('clicked', self.__class__._cancel, self)
		self.query_entry.connect_object('activate', self.__class__._search, self)
Пример #14
0
    def __init__(self, window, index_ext, preferences):
        Dialog.__init__(
            self,
            window,
            _('Task List'),  # T: dialog title
            buttons=gtk.BUTTONS_CLOSE,
            help=':Plugins:Task List',
            defaultwindowsize=(550, 400))
        self.preferences = preferences
        self.index_ext = index_ext

        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        self.hpane = HPaned()
        self.uistate.setdefault('hpane_pos', 75)
        self.hpane.set_position(self.uistate['hpane_pos'])
        self.vbox.add(self.hpane)

        # Task list
        self.uistate.setdefault('only_show_act', False)
        opener = window.get_resource_opener()
        self.task_list = TaskListTreeView(
            self.index_ext,
            opener,
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=preferences['tag_by_page'],
            use_workweek=preferences['use_workweek'])
        self.task_list.set_headers_visible(True)  # Fix for maemo
        self.hpane.add2(ScrolledWindow(self.task_list))

        # Tag list
        self.tag_list = TagListTreeView(self.index_ext, self.task_list)
        self.hpane.add1(ScrolledWindow(self.tag_list))

        # Filter input
        hbox.pack_start(gtk.Label(_('Filter') + ': '), False)  # T: Input label
        filter_entry = InputEntry()
        filter_entry.set_icon_to_clear()
        hbox.pack_start(filter_entry, False)
        filter_cb = DelayedCallback(
            500, lambda o: self.task_list.set_filter(filter_entry.get_text()))
        filter_entry.connect('changed', filter_cb)

        # Dropdown with options - TODO
        #~ menu = gtk.Menu()
        #~ showtree = gtk.CheckMenuItem(_('Show _Tree')) # T: menu item in options menu
        #~ menu.append(showtree)
        #~ menu.append(gtk.SeparatorMenuItem())
        #~ showall = gtk.RadioMenuItem(None, _('Show _All Items')) # T: menu item in options menu
        #~ showopen = gtk.RadioMenuItem(showall, _('Show _Open Items')) # T: menu item in options menu
        #~ menu.append(showall)
        #~ menu.append(showopen)
        #~ menubutton = MenuButton(_('_Options'), menu) # T: Button label
        #~ hbox.pack_start(menubutton, False)

        self.act_toggle = gtk.CheckButton(_('Only Show Actionable Tasks'))
        # T: Checkbox in task list
        self.act_toggle.set_active(self.uistate['only_show_act'])
        self.act_toggle.connect(
            'toggled',
            lambda o: self.task_list.set_filter_actionable(o.get_active()))
        hbox.pack_start(self.act_toggle, False)

        # Statistics label
        self.statistics_label = gtk.Label()
        hbox.pack_end(self.statistics_label, False)

        def set_statistics():
            total, stats = self.task_list.get_statistics()
            text = ngettext('%i open item', '%i open items', total) % total
            # T: Label for statistics in Task List, %i is the number of tasks
            text += ' (' + '/'.join(map(str, stats)) + ')'
            self.statistics_label.set_text(text)

        set_statistics()

        def on_tasklist_changed(o):
            self.task_list.refresh()
            self.tag_list.refresh(self.task_list)
            set_statistics()

        callback = DelayedCallback(10, on_tasklist_changed)
        # Don't really care about the delay, but want to
        # make it less blocking - should be async preferably
        # now it is at least on idle
        self.connectto(index_ext, 'tasklist-changed', callback)
Пример #15
0
class InsertCodeBlockDialog(Dialog):

	def __init__(self, parent):
		Dialog.__init__(self, parent, _('Insert Code Block')) # T: dialog title
		self.result = (None, None, None)
		self.uistate.define(id=String(None))
		self.uistate.define(lang=String(None))
		self.uistate.define(line_numbers=Boolean(True))

		grid = Gtk.Grid()
		grid.set_column_spacing(5)
		grid.set_row_spacing(5)

		label = Gtk.Label(_('Syntax') + ':') # T: input label
		grid.add(label)

		self.combobox = Gtk.ComboBox.new_with_model_and_entry(self.init_combobox_model())
		self.combobox.set_entry_text_column(0)
		entry = self.combobox.get_child()
		entry.set_activates_default(True)  # Pressing enter will activate the default button (here: ok-button)

		completion = Gtk.EntryCompletion()
		completion.set_model(self.init_autocomplete_model())
		completion.set_text_column(0)
		completion.set_minimum_key_length(0)
		entry.set_completion(completion)

		defaultlang = self.init_default_language()
		if defaultlang:
			entry.set_text(defaultlang)

		self.combobox.connect("changed", self.on_combobox_changed)

		grid.attach(self.combobox, 1, 0, 1, 1)

		label = Gtk.Label(_('Id') + ':') # T: input label for object ID
		grid.attach(label, 0, 1, 1, 1)
		self.entry = InputEntry()
		grid.attach(self.entry, 1, 1, 1, 1)

		self.checkbox = Gtk.CheckButton(_('Display line numbers')) # T: input checkbox
		self.checkbox.set_active(self.uistate['line_numbers'])
		grid.attach(self.checkbox, 1, 2, 1, 1)

		self.vbox.add(grid)

		# Set ok button as default.
		self.btn_ok = self.get_widget_for_response(response_id=Gtk.ResponseType.OK)
		self.btn_ok.set_can_default(True)
		self.btn_ok.grab_default()
		self.btn_ok.set_sensitive(defaultlang is not None)

	def init_default_language(self):
		for lang in sorted(LANGUAGES, key=lambda k: k.lower()):
			if LANGUAGES[lang] == self.uistate['lang']:
				return lang
		return None

	def init_combobox_model(self):
		menu = {}
		for l in sorted(LANGUAGES, key=lambda k: k.lower()):
			key = l[0].upper()
			if not key in menu:
				menu[key] = []
			menu[key].append(l)
		model = Gtk.TreeStore(str)
		for key in sorted(menu):
			iter = model.append(None, [key])
			for lang in menu[key]:
				model.append(iter, [lang])
		return model

	def init_autocomplete_model(self):
		store = Gtk.TreeStore(str)
		for lang in sorted(LANGUAGES, key=lambda k: k.lower()):
			store.append(None, [lang])
		return store

	def on_combobox_changed(self, widget):
		""" Checks whether the text entry in combobox is valid and enables/disables the ok-button. """
		self.btn_ok = self.get_widget_for_response(response_id=Gtk.ResponseType.OK)
		self.btn_ok.set_sensitive(widget.get_child().get_text() in LANGUAGES)

	def do_response_ok(self):
		if self.combobox.get_child().get_text() in LANGUAGES:
			self.uistate['lang'] = LANGUAGES[self.combobox.get_child().get_text()]
			self.uistate['id'] = self.entry.get_text()
			self.uistate['line_numbers'] = self.checkbox.get_active()
			self.result = (self.uistate['id'], self.uistate['lang'], self.uistate['line_numbers'])
			return True
		else:
			return False # no syntax selected
Пример #16
0
    def __init__(self, notebookinfo=None, port=8080, public=True, **opts):
        '''Constructor
		@param notebookinfo: the notebook location
		@param port: the http port to serve on
		@param public: allow connections to the server from other
		computers - if C{False} can only connect from localhost
		@param opts: options for L{WWWInterface.__init__()}
		'''
        GObject.GObject.__init__(self)
        self.set_title('Zim - ' + _('Web Server'))  # T: Window title
        self.set_border_width(10)
        self.interface_opts = opts
        self.httpd = None
        self._source_id = None

        # Widgets
        self.status_label = Gtk.Label()
        self.status_label.set_markup('<i>' + _('Server not started') + '</i>')
        # T: Status in web server gui
        self.start_button = IconButton('gtk-media-play')
        self.start_button.connect('clicked', lambda o: self.start())
        self.stop_button = IconButton('gtk-media-stop')
        self.stop_button.connect('clicked', lambda o: self.stop())
        self.stop_button.set_sensitive(False)

        self.link_button = Gtk.LinkButton('')
        self.link_button.set_sensitive(False)

        self.notebookcombobox = NotebookComboBox(current=notebookinfo)
        self.open_button = IconButton('gtk-index')
        self.open_button.connect('clicked',
                                 lambda *a: NotebookDialog(self).run())

        self.portentry = Gtk.SpinButton()
        self.portentry.set_numeric(True)
        self.portentry.set_range(80, 10000)
        self.portentry.set_increments(1, 80)
        self.portentry.set_value(port)

        self.public_checkbox = Gtk.CheckButton.new_with_mnemonic(
            _('Allow public access'))
        # T: Checkbox in web server gui
        self.public_checkbox.set_active(public)

        self.templatecombobox = Gtk.ComboBoxText.new()
        template_names = [name for name, _ in list_templates('html')]
        for name in template_names:
            self.templatecombobox.append_text(name)
        self.templatecombobox.set_active(template_names.index('Default'))

        self.auth_checkbox = Gtk.CheckButton.new_with_mnemonic(
            _('Require authentication'))
        # T: checkbox in dialog for starting webserver
        self.username_input = InputEntry()
        self.password_input = InputEntry()
        self.password_input.set_visibility(False)

        # Build the interface
        vbox = Gtk.VBox()
        self.add(vbox)

        hbox = Gtk.HBox(spacing=12)
        hbox.pack_start(self.start_button, False, True, 0)
        hbox.pack_start(self.stop_button, False, True, 0)
        hbox.pack_start(self.status_label, False, True, 0)
        vbox.pack_start(hbox, False, False, 0)

        table = input_table_factory((
            (_('Notebook'), self.notebookcombobox, self.open_button),
            # T: Field in web server gui
            (_('Port'), self.portentry),
            # T: Field in web server gui for HTTP port (e.g. port 80)
            (_('Template'), self.templatecombobox),
            # T: Field in web server gui for webpage template
            self.public_checkbox,
            self.auth_checkbox,
            (_('Username'), self.username_input),
            # T: Field in web server gui
            (_('Password'), self.password_input)
            # T: Field in web server gui
        ))
        vbox.pack_start(table, False, False, 0)

        if self.link_button:
            hbox = Gtk.HBox()
            hbox.pack_end(self.link_button, False, True, 0)
            vbox.pack_start(hbox, False, False, 0)
Пример #17
0
class ServerWindow(Gtk.Window):
    def __init__(self, notebookinfo=None, port=8080, public=True, **opts):
        '''Constructor
		@param notebookinfo: the notebook location
		@param port: the http port to serve on
		@param public: allow connections to the server from other
		computers - if C{False} can only connect from localhost
		@param opts: options for L{WWWInterface.__init__()}
		'''
        GObject.GObject.__init__(self)
        self.set_title('Zim - ' + _('Web Server'))  # T: Window title
        self.set_border_width(10)
        self.interface_opts = opts
        self.httpd = None
        self._source_id = None

        # Widgets
        self.status_label = Gtk.Label()
        self.status_label.set_markup('<i>' + _('Server not started') + '</i>')
        # T: Status in web server gui
        self.start_button = IconButton('gtk-media-play')
        self.start_button.connect('clicked', lambda o: self.start())
        self.stop_button = IconButton('gtk-media-stop')
        self.stop_button.connect('clicked', lambda o: self.stop())
        self.stop_button.set_sensitive(False)

        self.link_button = Gtk.LinkButton('')
        self.link_button.set_sensitive(False)

        self.notebookcombobox = NotebookComboBox(current=notebookinfo)
        self.open_button = IconButton('gtk-index')
        self.open_button.connect('clicked',
                                 lambda *a: NotebookDialog(self).run())

        self.portentry = Gtk.SpinButton()
        self.portentry.set_numeric(True)
        self.portentry.set_range(80, 10000)
        self.portentry.set_increments(1, 80)
        self.portentry.set_value(port)

        self.public_checkbox = Gtk.CheckButton.new_with_mnemonic(
            _('Allow public access'))
        # T: Checkbox in web server gui
        self.public_checkbox.set_active(public)

        self.templatecombobox = Gtk.ComboBoxText.new()
        template_names = [name for name, _ in list_templates('html')]
        for name in template_names:
            self.templatecombobox.append_text(name)
        self.templatecombobox.set_active(template_names.index('Default'))

        self.auth_checkbox = Gtk.CheckButton.new_with_mnemonic(
            _('Require authentication'))
        # T: checkbox in dialog for starting webserver
        self.username_input = InputEntry()
        self.password_input = InputEntry()
        self.password_input.set_visibility(False)

        # Build the interface
        vbox = Gtk.VBox()
        self.add(vbox)

        hbox = Gtk.HBox(spacing=12)
        hbox.pack_start(self.start_button, False, True, 0)
        hbox.pack_start(self.stop_button, False, True, 0)
        hbox.pack_start(self.status_label, False, True, 0)
        vbox.pack_start(hbox, False, False, 0)

        table = input_table_factory((
            (_('Notebook'), self.notebookcombobox, self.open_button),
            # T: Field in web server gui
            (_('Port'), self.portentry),
            # T: Field in web server gui for HTTP port (e.g. port 80)
            (_('Template'), self.templatecombobox),
            # T: Field in web server gui for webpage template
            self.public_checkbox,
            self.auth_checkbox,
            (_('Username'), self.username_input),
            # T: Field in web server gui
            (_('Password'), self.password_input)
            # T: Field in web server gui
        ))
        vbox.pack_start(table, False, False, 0)

        if self.link_button:
            hbox = Gtk.HBox()
            hbox.pack_end(self.link_button, False, True, 0)
            vbox.pack_start(hbox, False, False, 0)

    def open_notebook(self, notebook):
        '''Sets the notebook in the combobox

		This method is called by the NotebookDialog when a notebook is opened.
		'''
        self.notebookcombobox.set_notebook(notebook)

    def start(self):
        # Start server
        try:
            uri = self.notebookcombobox.get_notebook()
            if uri:
                notebook, x = build_notebook(NotebookInfo(uri))
                if not notebook:
                    return
            else:
                return

            if not notebook.index.is_uptodate:
                for info in notebook.index.update_iter():
                    #logger.info('Indexing %s', info)
                    pass  # TODO meaningful info for above message

            port = int(self.portentry.get_value())
            public = self.public_checkbox.get_active()
            self.interface_opts[
                'template'] = self.templatecombobox.get_active_text()

            require_auth = self.auth_checkbox.get_active()
            auth_creds = None
            if require_auth:
                auth_creds = (self.username_input.get_text(),
                              self.password_input.get_text())
            self.httpd = make_server(notebook,
                                     port,
                                     public,
                                     auth_creds=auth_creds,
                                     **self.interface_opts)

            if sys.platform == 'win32':
                # GObject io watch conflicts with socket use on windows..
                # idle handler uses a bit to much CPU for my taste,
                # timeout every 0.5 sec is better
                self.httpd.timeout = 0.1  # 100 ms
                self._source_id = GObject.timeout_add(500,
                                                      self.do_serve_on_poll)
            else:
                self.httpd.timeout = 3  # if no response after 3 sec, drop it
                self._source_id = GObject.io_add_watch(
                    self.httpd.fileno(),
                    GObject.IO_IN | GObject.IO_OUT | GObject.IO_ERR
                    | GObject.IO_HUP | GObject.IO_PRI,  # any event..
                    self.do_serve_on_io)
            logger.info("Serving HTTP on %s port %i...",
                        self.httpd.server_name, self.httpd.server_port)
        except Exception as error:
            ErrorDialog(self, error).run()
            return

        # Update UI
        self.notebookcombobox.set_sensitive(False)
        self.portentry.set_sensitive(False)
        self.public_checkbox.set_sensitive(False)
        self.templatecombobox.set_sensitive(False)
        self.open_button.set_sensitive(False)
        self.start_button.set_sensitive(False)
        self.stop_button.set_sensitive(True)
        self.auth_checkbox.set_sensitive(False)
        self.username_input.set_sensitive(False)
        self.password_input.set_sensitive(False)

        self.status_label.set_markup('<i>' + _('Server started') + '</i>')
        # T: Status in web server gui
        #if self.public_checkbox.get_active():
        #	url = 'http://%s:%i' % (self.httpd.server_name, self.httpd.server_port)
        #else:
        #	url = 'http://localhost:%i' % self.httpd.server_port
        url = 'http://localhost:%i' % self.httpd.server_port
        if self.link_button:
            self.link_button.set_uri(url)
            self.link_button.set_label(url)
            self.link_button.set_sensitive(True)

    def do_serve_on_io(self, fd, event):
        try:
            if event & GObject.IO_HUP:
                self.stop()
                raise Exception('Socket disconnected')
            else:
                self.httpd.handle_request()
        except:
            logger.exception('Exception while handling IO request:')

        return True  # keep event running

    def do_serve_on_poll(self):
        self.httpd.handle_request()
        return True  # keep event running

    def stop(self):
        # Stop server
        logger.debug('Stop server')
        if self._source_id is not None:
            GObject.source_remove(self._source_id)
            self._source_id = None

        if self.httpd:
            self.httpd.socket.close()
            # There is also a httpd.server_close(), but undocumented (!?)
            self.httpd = None

        # Update UI
        self.status_label.set_markup('<i>' + _('Server stopped') + '</i>')
        # T: Status in web server gui
        if self.link_button:
            self.link_button.set_sensitive(False)
        self.notebookcombobox.set_sensitive(True)
        self.portentry.set_sensitive(True)
        self.public_checkbox.set_sensitive(True)
        self.templatecombobox.set_sensitive(True)
        self.open_button.set_sensitive(True)
        self.stop_button.set_sensitive(False)
        self.start_button.set_sensitive(True)
        self.auth_checkbox.set_sensitive(True)
        self.username_input.set_sensitive(True)
        self.password_input.set_sensitive(True)
Пример #18
0
    def instantsearch(self):

        # init
        self.cached_titles = []
        # self.menu = defaultdict(_MenuItem)
        self.lastQuery = ""  # previous user input
        self.queryO = None
        self.caret = {'pos': 0, 'altPos': 0, 'text': ""}  # cursor position
        self.originalPage = self.window.page.name  # we return here after escape
        self.selection = None
        if not self.plugin.preferences['isCached']:
            # reset last search results
            State.reset()
        self.menuPage = None
        self.isClosed = False
        self.lastPage = None

        # preferences
        self.title_match_char = self.plugin.preferences['title_match_char']
        self.start_search_length = self.plugin.preferences[
            'start_search_length']
        self.keystroke_delay = self.plugin.preferences['keystroke_delay']
        self.open_when_unique = self.plugin.preferences['open_when_unique']

        # building quick title cache
        def build(start=""):
            if hasattr(self.window.notebook, 'pages'):
                o = self.window.notebook.pages
            else:  # for Zim 0.66-
                o = self.window.notebook.index
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)

        build()

        # Gtk
        self.gui = Dialog(self.window,
                          _('Search'),
                          buttons=None,
                          defaultwindowsize=(300, -1))
        self.gui.resize(300, 100)  # reset size
        self.inputEntry = InputEntry()
        self.inputEntry.connect('key_press_event', self.move)
        self.inputEntry.connect(
            'changed',
            self.change)  # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.inputEntry,
                                 expand=False,
                                 fill=True,
                                 padding=0)
        self.labelObject = Gtk.Label(label=(''))
        self.labelObject.set_size_request(300, -1)
        self.gui.vbox.pack_start(self.labelObject,
                                 expand=False,
                                 fill=True,
                                 padding=0)

        # gui geometry
        px, py = self.window.get_position()
        pw, ph = self.window.get_size()
        x, y = self.gui.get_position()

        if self.plugin.preferences[
                'position'] == InstantsearchPlugin.POSITION_RIGHT:
            self.gui.move((pw - 300), 0)
        elif self.plugin.preferences[
                'position'] == InstantsearchPlugin.POSITION_CENTER:
            self.gui.resize(300, 100)
            self.gui.move(px + (pw / 2) - 150, py + (ph / 2) - 250)
        else:
            raise AttributeError("Instant search: Wrong position preference.")

        self.gui.show_all()

        self.labelVar = ""
        self.timeout = ""
        self.timeoutOpenPage = None
Пример #19
0
    def instantsearch(self):


        #init
        self.cached_titles = []
        #self.menu = defaultdict(_MenuItem)
        self.lastQuery = "" # previous user input
        self.queryO = None
        self.caret = {'pos':0, 'altPos':0, 'text':""}  # cursor position
        self.originalPage = self.window.ui.page.name # we return here after escape
        self.selection = None
        if not self.plugin.preferences['isCached']:
            # reset last search results
            State.reset()
        self.menuPage = None
        self.isClosed = False
        self.lastPage = None

        # preferences
        self.title_match_char = self.plugin.preferences['title_match_char']
        self.start_search_length = self.plugin.preferences['start_search_length']
        self.keystroke_delay = self.plugin.preferences['keystroke_delay']
        self.open_when_unique = self.plugin.preferences['open_when_unique']

        # building quick title cache
        def build(start = ""):
            if hasattr(self.window.ui.notebook, 'pages'):
                o = self.window.ui.notebook.pages
            else: # for Zim 0.66-
                o = self.window.ui.notebook.index
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)
        build()

        # Gtk
        self.gui = Dialog(self.window.ui, _('Search'), buttons=None, defaultwindowsize=(300, -1))
        self.gui.resize(300, 100) # reset size
        self.inputEntry = InputEntry()
        self.inputEntry.connect('key_press_event', self.move)
        self.inputEntry.connect('changed', self.change) # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.inputEntry, False)
        self.labelObject = gtk.Label((''))
        self.labelObject.set_usize(300, -1)
        self.gui.vbox.pack_start(self.labelObject, False)

        #gui geometry
        px, py = self.window.get_position()
        pw, ph = self.window.get_size()
        x, y = self.gui.get_position()

        if self.plugin.preferences['position'] == InstantsearchPlugin.POSITION_RIGHT:
            self.gui.move((pw-300), 0)
        elif self.plugin.preferences['position'] == InstantsearchPlugin.POSITION_CENTER:
            self.gui.resize(300, 100)
            self.gui.move(px + (pw / 2) - 150, py + (ph / 2) - 250)
        else:
            raise AttributeError("Instant search: Wrong position preference.")

        self.gui.show_all()

        self.labelVar = ""
        self.timeout = ""
        self.timeoutOpenPage = None
Пример #20
0
class InstantSearchMainWindowExtension(MainWindowExtension):
    gui: "Dialog"
    state: "State"

    def __init__(self, plugin, window):
        super().__init__(plugin, window)
        self.timeout = None
        self.timeout_open_page = None  # will open page after keystroke delay
        self.timeout_open_page_preview = None  # will open page after keystroke delay
        self.cached_titles = None
        self.last_query = None
        self.query_o = None
        self.caret = None
        self.original_page = None
        self.original_history = None
        self.selection = None
        self.menu_page = None
        self.is_closed = None
        self.last_page = self.last_page_preview = None
        self.label_object = None
        self.input_entry = None
        self.label_preview = None
        self.preview_pane = None
        self._last_update = 0

        # preferences
        State.title_match_char = self.plugin.preferences['title_match_char']
        State.start_search_length = self.plugin.preferences[
            'start_search_length']
        self.keystroke_delay_open = self.plugin.preferences[
            'keystroke_delay_open']
        self.keystroke_delay = self.plugin.preferences['keystroke_delay']

    # noinspection PyArgumentList,PyUnresolvedReferences
    @action(_('_Instant search'), accelerator='<ctrl>e')  # T: menu item
    def instant_search(self):

        # init
        self.cached_titles = []
        self.last_query = ""  # previous user input
        self.query_o = None
        self.caret = SimpleNamespace(pos=0, text="",
                                     stick=False)  # cursor position
        self.original_page = self.window.page.name  # we return here after escape
        self.original_history = list(self.window.history.uistate["list"])
        self.selection = None
        if not self.plugin.preferences['is_cached']:
            # reset last search results
            State.reset()
        self.menu_page = None
        self.is_closed = False
        self.last_page = None

        # building quick title cache
        def build(start=""):
            o = self.window.notebook.pages
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)

        build()

        # Gtk
        self.gui = Dialog(self.window,
                          _('Search'),
                          buttons=None,
                          defaultwindowsize=(300, -1))
        self.gui.resize(300, 100)  # reset size
        self.input_entry = InputEntry()
        self.input_entry.connect('key_press_event', self.move)
        self.input_entry.connect(
            'changed',
            self.change)  # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.input_entry,
                                 expand=False,
                                 fill=True,
                                 padding=0)
        # noinspection PyArgumentList
        self.label_object = Gtk.Label(label='')
        self.label_object.set_size_request(300, -1)
        self.gui.vbox.pack_start(self.label_object,
                                 expand=False,
                                 fill=True,
                                 padding=0)

        # preview pane
        self.label_preview = Gtk.Label(label='...loading...')
        # not sure if this has effect, longer lines without spaces still make window inflate
        self.label_preview.set_line_wrap(True)
        self.label_preview.set_xalign(0)  # align to the left
        self.preview_pane = Gtk.VBox()
        self.preview_pane.pack_start(self.label_preview, False, False, 5)
        self.window.pageview.pack_start(self.preview_pane, False, False, 5)

        # gui geometry
        self.geometry(init=True)

        self.gui.show_all()

    def geometry(self, init=False, repeat=True, force=False):
        if repeat and not init:
            # I do not know how to catch callback when result list's width is final, so we align several times
            [
                GObject.timeout_add(
                    x, lambda: self.geometry(repeat=False, force=force))
                for x in (30, 50, 70, 400)
            ]
            # it is not worthy we continue now because often the Gtk redraw is delayed which would mean
            # the Dialog dimensions change twice in a row
            return

        px, py = self.window.get_position()
        pw, ph = self.window.get_size()
        if init:
            x, y = None, None
            w, h = 300, 100
        else:
            x, y = self.gui.get_position()
            w, h = self.gui.get_allocated_width(
            ), self.gui.get_allocated_height()
        if self.plugin.preferences[
                'position'] == InstantSearchPlugin.POSITION_RIGHT:
            x2, y2 = pw - w, 0
        elif self.plugin.preferences[
                'position'] == InstantSearchPlugin.POSITION_CENTER:
            x2, y2 = px + (pw / 2) - w / 2, py + (ph / 2) - 250

        else:
            raise AttributeError("Instant search: Wrong position preference.")

        if init or x != x2 or force:
            self.gui.resize(300, 100)
            self.gui.move(x2, y2)

    def title(self, title=""):
        self.gui.set_title("Search " + title)

    def change(self, _):  # widget, event,text
        if self.timeout:
            GObject.source_remove(self.timeout)
            self.timeout = None
        q = self.input_entry.get_text()
        if q == self.last_query:
            return
        if q == State.title_match_char:
            return
        if q and q[-1] == "∀":  # easter egg: debug option for zim --standalone
            q = q[:-1]
            import ipdb
            ipdb.set_trace()
        self.state = State.set_current(q)

        if not self.state.is_finished:
            if self.start_search():
                self.process_menu()
        else:  # search completed before
            # update the results if a page has been modified meanwhile
            # (not if something got deleted in the notebook #16 )
            self.start_search()
            self.check_last()
            self.sout_menu()

        self.last_query = q

    def start_search(self):
        """ Search string has certainly changed. We search in indexed titles and/or we start fulltext search.
        :rtype: True if no other search is needed and we may output the menu immediately.
            
        """

        query = self.state.query
        menu = self.state.menu

        if not query:
            return self.process_menu()  # show for now results of title search

        # 'te' matches these page titles: 'test' or 'Journal:test' or 'foo test' or 'foo (test)'
        sub_queries = [
            re.compile(r"(^|:|\s|\()?" + q, re.IGNORECASE)
            for q in query.split(" ")
        ]

        def in_query(txt):
            """ False if any part of the query does not match.
                If the query is longer >3 characters:
                    * +10 for every query part that matches a title part beginning
                        Ex: query 'te' -> +3 for these page titles:
                            'test' or 'Journal:test' or 'foo test' or 'foo (test)'
                    * +1 for every query part
                        Ex: query 'st' -> +1 for those page titles

                If the query is shorter <=3 characters:
                    +10 for every query part that matches a title part beginning 'te' for 'test'
                    False otherwise ('st' for 'test') so that you do not end up messed
                     with page titles, after writing a single letter.
            """
            arr = (q.search(txt) for q in sub_queries)
            try:
                if len(query) <= 3:
                    # raises if subquery m does not match or is not at a page chunk beginning
                    return sum(10 if m.group(1) is not None else None
                               for m in arr)
                else:
                    # raises if subquery m does not match
                    return sum(10 if m.group(1) is not None else 1
                               for m in arr)
            except (AttributeError, TypeError
                    ):  # one of the sub_queries is not part of the page title
                return False

        # we loop either all cached page titles or menu that should be built from previous superset-query menu
        it = ((x, x.lower())
              for x in list(menu)) if menu else self.cached_titles
        for path, path_low in it:  # quick search in titles
            score = in_query(path_low)
            if score:  # 'te' matches 'test' or 'Journal:test' etc
                # "foo" in "foo:bar", but not in "bar"
                # when looping "foo:bar", page "foo" receives +1 for having a subpage
                if all(q in path.lower() for q in query) \
                        and any(q not in path.lower().split(":")[-1] for q in query):
                    menu[":".join(path.split(
                        ":")[:-1])].bonus += 1  # 1 point for having a subpage
                    # Normally, zim search gives 11 points bonus if the search-string appears in the titles.
                    # If we are ignoring sub-pages, the search "foo" will match only page "journal:foo",
                    # but not "journal:foo:subpage"
                    # (and score of the parent page will get slightly higher by 1.)
                    # However, if there are occurrences of the string in the fulltext of the subpage,
                    # subpage remains in the result, but gets bonus only 2 points (not 11).
                    # But internal zim search is now disabled.
                    # menu[path].bonus = -11
                else:
                    # 10 points for title (zim default) (so that it gets displayed before search finishes)
                    menu[
                        path].bonus += score  # will be added to score (score will be reset)
                    # if score > 9, it means this might be priority match, not fulltext header search
                    # ex "te" for "test" is priority, whereas "st" is just fulltext
                    menu[path].in_title = True if score > 9 else False
                    menu[path].path = path
                    # menu[path].sure = True

        if self.state.page_title_only:
            return True
        else:
            if not self.state.previous or len(
                    query) == State.start_search_length:
                # quickly show page title search results before longer fulltext search is ready
                # Either there is no previous state – query might have been copied into input
                # or the query is finally long enough to start fulltext search.
                # It is handy to show out filtered page names before because
                # it is often use case to jump to queries matched in page names.
                self.process_menu(ignore_geometry=True)

            self.title("..")
            self.timeout = GObject.timeout_add(
                self.keystroke_delay,
                self.start_zim_search)  # ideal delay between keystrokes

    def start_zim_search(self):
        """ Starts search for the input. """
        self.title("...")
        if self.timeout:
            GObject.source_remove(self.timeout)
            self.timeout = None
        self.query_o = Query(self.state.query)

        # it should be quicker to find the string, if we provide this subset from last time
        # (in the case we just added a letter, so that the subset gets smaller)
        # last_sel = self.selection if self.is_subset and self.state.previous and self.state.previous.is_finished
        #   else None
        selection = self.selection = SearchSelection(self.window.notebook)
        state = self.state  # this is a thread, so that self.state might change before search finishes

        # internal search disabled - it was way too slower
        # selection.search(self.query_o, selection=last_sel, callback=self._search_callback(state))
        # self._update_results(selection, state, force=True)
        # self.title("....")

        # fulltext external search
        # Loop either all .txt files in the notebook or narrow the search with a previous state
        if state.previous and state.previous.is_finished and state.previous.matching_files is not None:
            paths_set = state.previous.matching_files
            # see below paths_cached_set = (p for p in files_set if p in InstantSearchPlugin.file_cache)
        else:
            paths_set = (f for f in pathlib.Path(
                str(self.window.notebook.folder)).rglob("*.txt")
                         if f.is_file())
            # see below paths_cached_set = (p for p in InstantSearchPlugin.file_cache)
        state.matching_files = []

        # This cached search takes about 60 ms, so I let it commented.
        # However on HDD disks this may boost performance.
        # We may do an option: "empty cache immediately after close (default)",
        #                      "search cache first and then do the fresh search (HDD)"
        #                      "use cache always (empties cache after Zim restart)"
        #                      "empty cache after 5 minutes"
        #                      and then prevent to clear the cache in .close().
        # Or rather we may read file mtime and re-read if only it has been changed since last search.
        # if not InstantSearchPlugin.file_cache_fresh:
        #     # Cache might not be fresh but since it is quick, perform quick non-fresh-cached search
        #     # and then do a fresh search. If we are lucky enough, results will not change.
        #     # using temporary selection so that files will not received double points for both cached and fresh loop
        #     selection_temp = SearchSelection(self.window.notebook)
        #     self.start_external_search(selection_temp, state, paths_cached_set)
        #     InstantSearchPlugin.file_cache_fresh = True
        #     InstantSearchPlugin.file_cache.clear()
        self.start_external_search(selection, state, paths_set)

        state.is_finished = True

        # for item in list(state.menu):  # remove all the items that we didnt encounter during the search
        #     if not state.menu[item].sure:
        #         del state.menu[item]

        if state == self.state:
            self.check_last()

        self.process_menu(state=state)
        self.title()

    def start_external_search(self, selection, state: "State", paths):
        """ Zim internal search is not able to find out text with markup.
                 Ex:
                  'economical' is not recognized as 'economi**cal**' (however highlighting works great),
                                                 as 'economi[[inserted link]]cal'
                                                 as 'any text with [[http://economical.example.com|link]]'

                 This fulltext search loops all .txt files in the notebook directory
                 and tries to recognize the patterns.
                 """

        # divide query to independent words "foo economical" -> "foo", "economical", page has to contain both
        # strip markup: **bold**, //italic//,  __underline__, ''verbatim'', ~~strike through~~
        # matches query "economi**cal**"

        def letter_split(q):
            """ Every letter is divided by a any-formatting-match-group and escaped.
                'foo.' -> 'f[*/'_~]o[*/'_~]o[*/'_~]\\.'
            """
            return r"[*/'_~]*".join((re.escape(c) for c in list(q)))

        sub_queries = state.query.split(" ")

        # regex to identify in all sub_queries present in the text
        queries = [
            re.compile(letter_split(q), re.IGNORECASE) for q in sub_queries
        ]

        # regex to identify the very query is present
        exact_query = re.compile(letter_split(
            state.query), re.IGNORECASE) if len(sub_queries) > 1 else None

        # regex to count the number of the sub_queries present and to optionally add information about header used
        header_queries = [
            re.compile("(\n=+ .*)?" + letter_split(q), re.IGNORECASE)
            for q in sub_queries
        ]

        # regex to identify inner link contents
        link = re.compile(
            r"\[\[(.*?)\]\]",
            re.IGNORECASE)  # matches all links "economi[[inserted link]]cal"

        for p in paths:
            if p not in InstantSearchPlugin.file_cache:
                s = p.read_text()  # strip header
                if s.startswith('Content-Type: text/x-zim-wiki'):
                    # XX will that work on Win?
                    # I should use more general separator IMHO in the whole file rather than '\n'.
                    s = s[s.find("\n\n"):]
                InstantSearchPlugin.file_cache[p] = s
            else:
                s = InstantSearchPlugin.file_cache[p]

            matched_links = []

            def matched_link(match):
                matched_links.append(match.group(1))
                return ""

            # pull out links "economi[[inserted link]]cal" -> "economical" + "inserted link"
            txt_body = link.sub(matched_link, s)
            txt_links = "".join(matched_links)

            if all(
                    query.search(txt_body) or query.search(txt_links)
                    for query in queries):
                path = self.window.notebook.layout.map_file(File(str(p)))[0]

                # score = header order * 3 + body match count * 1
                # if there are '=' equal chars before the query, it is header. The bigger number, the bigger header.
                # Header 5 corresponds to 3 points, Header 1 to 7 points.
                score = sum([
                    len(m.group(1)) * 3 if m.group(1) else 1
                    for q in header_queries for m in q.finditer(txt_body)
                ])
                if exact_query:  # there are sub-queries, we favourize full-match
                    score += 50 * len(exact_query.findall(txt_body))

                # noinspection PyProtectedMember
                # score might be zero because we are not re-checking against txt_links matches
                selection._count_score(path, score or 1)
                state.matching_files.append(p)
        self._update_results(selection, state, force=True)

    def check_last(self):
        """ opens the page if there is only one option in the menu """
        if len(self.state.menu
               ) == 1 and self.plugin.preferences['open_when_unique']:
            self._open_page(Path(list(self.state.menu)[0]),
                            exclude_from_history=False)
            self.close()
        elif not len(self.state.menu):
            self._open_original()

    def _search_callback(self, state):
        def _(results, _path):
            if results is not None:
                # we finish the search even if another search is running.
                # If returned False, the search would be cancelled
                self._update_results(results, state)
            while Gtk.events_pending():
                Gtk.main_iteration()
            return True

        return _

    def _update_results(self, results, state: "State", force=False):
        """
        This method may run many times, due to the _update_results, which are updated many times,
         the results are appearing one by one. However, if called earlier than 0.2 s, ignored.

        Measures:
            If every callback would be counted, it takes 3500 ms to build a result set.
            If callbacks earlier than 0.6 s -> 2300 ms, 0.3 -> 2600 ms, 0.1 -> 2800 ms.
        """
        if not force and time(
        ) < self._last_update + 0.2:  # if update callback called earlier than 200 ms, ignore
            return
        self._last_update = time()

        changed = False

        for option in results.scores:
            if option.name not in state.menu or (
                    state.menu[option.name].bonus < 0
                    and state.menu[option.name].score == 0):
                changed = True
            o: _MenuItem = state.menu[option.name]
            # if not o.sure:
            #     o.sure = True
            #     changed = True
            o.score = results.scores[option]  # includes into options

        if changed:  # we added a page
            self.process_menu(state=state, sort=False)
        else:
            pass

    def process_menu(self, state=None, sort=True, ignore_geometry=False):
        """ Sort menu and generate items and sout menu. """
        if state is None:
            state = self.state

        if sort:
            state.items = sorted(
                state.menu,
                reverse=True,
                key=lambda item:
                (state.menu[item].in_title, state.menu[item].score + state.
                 menu[item].bonus, -item.count(":"), item))
        else:
            # when search results are being updated, it's good when the order doesnt change all the time.
            # So that the first result does not become for a while 10th and then become first back.
            state.items = sorted(
                state.menu,
                reverse=True,
                key=lambda item:
                (state.menu[item].in_title, -state.menu[item].last_order))

        # I do not know why there are items with score 0 if internal Zim search used
        state.items = [
            item for item in state.items
            if (state.menu[item].score + state.menu[item].bonus) > 0
        ]

        if state == self.state:
            self.sout_menu(ignore_geometry=ignore_geometry)

    def sout_menu(self,
                  display_immediately=False,
                  caret_move=None,
                  ignore_geometry=False):
        """ Displays menu and handles caret position. """
        if self.timeout_open_page:
            GObject.source_remove(self.timeout_open_page)
            self.timeout_open_page = None
        if self.timeout_open_page_preview:
            GObject.source_remove(self.timeout_open_page_preview)
            self.timeout_open_page_preview = None

        # caret:
        #   by default stays at position 0
        #   If moved to a page, it keeps the page.
        #   If moved back to position 0, stays there.
        if caret_move is not None:
            if caret_move == 0:
                self.caret.pos = 0
            else:
                self.caret.pos += caret_move
            self.caret.stick = self.caret.pos != 0
        elif self.state.items and self.caret.stick:
            # identify current caret position, depending on the text
            self.caret.pos = next((i for i, item in enumerate(self.state.items)
                                   if item == self.caret.text), 0)
        # treat possible caret deflection
        if self.caret.pos < 0:
            # place the caret to the beginning or the end of list
            self.caret.pos = 0
        elif self.caret.pos >= len(self.state.items):
            self.caret.pos = 0 if caret_move == 1 else len(
                self.state.items) - 1

        text = []
        i = 0
        for item in self.state.items:
            score = self.state.menu[item].score + self.state.menu[item].bonus
            self.state.menu[item].last_order = i
            pieces = item.split(":")
            pieces[-1] = f"<b>{pieces[-1]}</b>"
            s = ":".join(pieces)
            if i == self.caret.pos:
                self.caret.text = item  # caret is at this position
                # text += f'→ {s} ({score}) {"" if self.state.menu[item].sure else "?"}\n'
                text.append(f'→ {s} ({score})')
            else:
                # text += f'{s} ({score}) {"" if self.state.menu[item].sure else "?"}\n'
                text.append(f'{s} ({score})')
            i += 1
        text = "No result" if not text and self.state.is_finished else "\n".join(
            text)

        self.label_object.set_markup(text)
        self.menu_page = Path(
            self.caret.text if len(self.state.items) else self.original_page)

        if not display_immediately:
            if self.plugin.preferences[
                    'preview_mode'] != InstantSearchPlugin.PREVIEW_ONLY:
                self.timeout_open_page = GObject.timeout_add(
                    self.keystroke_delay_open, self._open_page,
                    self.menu_page)  # ideal delay between keystrokes
            if self.plugin.preferences[
                    'preview_mode'] != InstantSearchPlugin.FULL_ONLY:
                self.timeout_open_page_preview = GObject.timeout_add(
                    self.keystroke_delay, self._open_page_preview,
                    self.menu_page)  # ideal delay between keystrokes
        else:
            self._open_page(self.menu_page)
        # we force here geometry to redraw because often we end up with "No result" page that is very tall
        # because of a many records just hidden

        if not ignore_geometry:
            self.geometry(force=True)

    def move(self, widget, event):
        """ Move caret up and down. Enter to confirm, Esc closes search."""
        key_name = Gdk.keyval_name(event.keyval)

        # handle basic caret movement
        moves = {
            "Up": -1,
            "ISO_Left_Tab": -1,
            "Down": 1,
            "Tab": 1,
            "Page_Up": -10,
            "Page_Down": 10
        }
        if key_name in moves:
            self.sout_menu(display_immediately=False,
                           caret_move=moves[key_name])
        elif key_name in ("Home", "End"):
            if event.state & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.SHIFT_MASK:
                # Ctrl/Shift+Home jumps to the query input text start
                return
            if key_name == "Home":  # Home jumps at the result list start
                self.sout_menu(display_immediately=False, caret_move=0)
                widget.emit_stop_by_name("key-press-event")
            else:
                self.sout_menu(display_immediately=False,
                               caret_move=float("inf"))
                widget.emit_stop_by_name("key-press-event")

        # confirm or cancel
        elif key_name == "KP_Enter" or key_name == "Return":
            self._open_page(self.menu_page, exclude_from_history=False)
            self.close()
        elif key_name == "Escape":
            self._open_original()
            self.is_closed = True  # few more timeouts are on the way probably
            self.close()

        return

    def close(self):
        """ Safely (closes gets called when hit Enter) """
        if not self.is_closed:  # if hit Esc, GTK has already emitted close itself
            self.is_closed = True
            self.gui.emit("close")

        # remove preview pane and show current text editor
        self._hide_preview()
        self.preview_pane.destroy()
        InstantSearchPlugin.file_cache.clear(
        )  # until next search, pages might change
        InstantSearchPlugin.file_cache_fresh = False

    def _open_original(self):
        self._open_page(Path(self.original_page))
        # we already have HistoryPath objects in the self.original_history, we cannot add them in the constructor
        # XX I do not know what is that good for
        hl = HistoryList([])
        hl.extend(self.original_history)
        self.window.history.uistate["list"] = hl

    # noinspection PyProtectedMember
    def _open_page(self, page, exclude_from_history=True):
        """ Open page and highlight matches """
        self._hide_preview()
        if self.timeout_open_page:  # no delayed page will be open
            GObject.source_remove(self.timeout_open_page)
            self.timeout_open_page = None
        if self.timeout_open_page_preview:  # no delayed preview page will be open
            GObject.source_remove(self.timeout_open_page_preview)
            self.timeout_open_page_preview = None

        # open page
        if page and page.name and page.name != self.last_page:
            self.last_page = page.name
            self.window.navigation.open_page(page)
            if exclude_from_history and list(
                    self.window.history._history
            )[-1:][0].name != self.original_page:
                # there is no public API, so lets use protected _history instead
                self.window.history._history.pop()
                self.window.history._current = len(
                    self.window.history._history) - 1
        if not exclude_from_history and self.window.history.get_current(
        ).name is not page.name:
            # we insert the page to the history because it was likely to be just visited and excluded
            self.window.history.append(page)

        # Popup find dialog with same query
        if self.query_o:  # and self.query_o.simple_match:
            string = self.state.query
            string = string.strip('*')  # support partial matches
            if self.plugin.preferences['highlight_search']:
                # unfortunately, we can highlight single word only
                self.window.pageview.show_find(string.split(" ")[0],
                                               highlight=True)

    def _hide_preview(self):
        self.preview_pane.hide()
        # noinspection PyProtectedMember
        self.window.pageview._hack_hbox.show()

    def _open_page_preview(self, page):
        """ Open preview which is far faster then loading and
         building big parse trees into text editor buffer when opening page. """
        # note: if the dialog is already closed, we do not want a preview to open, but page still can be open
        # (ex: after hitting Enter the dialog can close before opening the page)

        if self.timeout_open_page_preview:
            # no delayed preview page will be open, however self.timeout_open_page might be still running
            GObject.source_remove(self.timeout_open_page_preview)
            self.timeout_open_page_preview = None

        # it does not pose a problem if we re-load preview on the same page;
        # the query text might got another letter to highlight
        if page and not self.is_closed:
            # show preview pane and hide current text editor
            self.last_page_preview = page.name

            local_file = self.window.notebook.layout.map_page(page)[0]
            path = pathlib.Path(str(local_file))
            if path in InstantSearchPlugin.file_cache:
                s = InstantSearchPlugin.file_cache[path]
            else:
                try:
                    s = InstantSearchPlugin.file_cache[path] = local_file.read(
                    )
                except newfs.base.FileNotFoundError:
                    s = f"page {page} has no content"  # page has not been created yet
            lines = s.splitlines()

            # the file length is very small, prefer to not use preview here
            if self.plugin.preferences[
                    'preview_mode'] != InstantSearchPlugin.PREVIEW_ONLY and len(
                        lines) < 50:
                return self._open_page(page, exclude_from_history=True)
            self.label_preview.set_markup(
                self._get_preview_text(lines, self.state.query))

            # shows GUI (hidden in self._hide_preview()
            self.preview_pane.show_all()
            # noinspection PyProtectedMember
            self.window.pageview._hack_hbox.hide()

    def _get_preview_text(self, lines, query):
        max_lines = 200

        # check if the file is a Zim markup file and if so, skip header
        if lines[0] == 'Content-Type: text/x-zim-wiki':
            for i, line in enumerate(lines):
                if line == "":
                    lines = lines[i + 1:]
                    break

        if query.strip() == "":
            return "\n".join(line for line in lines[:max_lines])

        # searching for "a" cannot match "&a", since markup_escape_text("&") -> "&apos;"
        # Ignoring q == "b", it would interfere with multiple queries:
        # Ex: query "f b", text "foo", matched with "f" -> "<b>f</b>oo", matched with "b" -> "<<b>b</b>>f</<b>b</b>>"
        query_match = (re.compile("(" + re.escape(q) + ")", re.IGNORECASE)
                       for q in query.split(" ") if q != "b")
        # too long lines caused strange Gtk behaviour – monitor brightness set to maximum, without any logged warning
        # so that I decided to put just extract of such long lines in preview
        # This regex matches query chunk in the line, prepends characters before and after.
        # When there should be the same query chunk after the first, it stops.
        # Otherwise, the second chunk might be halved and thus not highlighted.
        # Ex: query "test", text: "lorem ipsum text dolor text text sit amet consectetur" ->
        #   ["ipsum text dolor ", "text ", "text sit amet"] (words "lorem" and "consectetur" are strip)
        line_extract = [
            re.compile(
                "(.{0,80}" + re.escape(q) + "(?:(?!" + re.escape(q) +
                ").){0,80})", re.IGNORECASE) for q in query.split(" ")
            if q != "b"
        ]

        # grep some lines
        keep_all = not self.plugin.preferences["preview_short"] and len(
            lines) < max_lines
        lines_iter = iter(lines)
        chosen = [
            next(lines_iter)
        ]  # always include header as the first line, even if it does not contain the query
        for line in lines_iter:
            if len(
                    chosen
            ) > max_lines:  # file is too long which would result the preview to not be smooth
                break
            elif keep_all or any(q in line.lower() for q in query.split(" ")):
                # keep this line since it contains a query chunk
                if len(line) > 100:
                    # however, this line is too long to display, try to extract query and its neighbourhood
                    s = "...".join("...".join(q.findall(line))
                                   for q in line_extract).strip(".")
                    if not s:  # no query chunk was find on this line, the keep_all is True for sure
                        chosen.append(line[:100] + "...")
                    else:
                        chosen.append("..." + s + "...")
                else:
                    chosen.append(line)
        if not keep_all or len(chosen) > max_lines:
            # note that query might not been found, ex: query "foo" would not find line with a bold 'o': "f**o**o"
            chosen.append("...")
        txt = markup_escape_text("\n".join(line for line in chosen))

        # bold query chunks in the text
        for q in query_match:
            txt = q.sub(r"<b>\g<1></b>", txt)

        # preserve markup_escape_text entities
        # correct ex: '&a<b>m</b>p;' -> '&amp;' if searching for 'm'
        bold_tag = re.compile("</?b>")
        broken_entity = re.compile("&[a-z]*<b[^;]*;")
        txt = broken_entity.sub(lambda m: bold_tag.sub("", m.group(0)), txt)
        return txt
Пример #21
0
    def __init__(self, window, tasksview, preferences):
        Dialog.__init__(
            self,
            window,
            _('Task List'),  # T: dialog title
            buttons=gtk.BUTTONS_CLOSE,
            help=':Plugins:Task List',
            defaultwindowsize=(550, 400))
        self.preferences = preferences
        self.tasksview = tasksview

        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        self.hpane = HPaned()
        self.uistate.setdefault('hpane_pos', 75)
        self.hpane.set_position(self.uistate['hpane_pos'])
        self.vbox.add(self.hpane)

        # Task list
        self.uistate.setdefault('only_show_act', False)
        self.uistate.setdefault('show_flatlist', False)
        self.uistate.setdefault('sort_column', 0)
        self.uistate.setdefault('sort_order', int(gtk.SORT_DESCENDING))

        opener = window.get_resource_opener()
        task_labels = _parse_task_labels(preferences['labels'])
        nonactionable_tags = _parse_task_labels(
            preferences['nonactionable_tags'])
        self.task_list = TaskListTreeView(
            self.tasksview,
            opener,
            task_labels,
            nonactionable_tags=nonactionable_tags,
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=preferences['tag_by_page'],
            use_workweek=preferences['use_workweek'],
            flatlist=self.uistate['show_flatlist'],
            sort_column=self.uistate['sort_column'],
            sort_order=self.uistate['sort_order'])
        self.task_list.set_headers_visible(True)
        self.task_list.connect('populate-popup', self.on_populate_popup)
        self.hpane.add2(ScrolledWindow(self.task_list))

        # Tag list
        self.tag_list = TagListTreeView(self.task_list, task_labels)
        self.hpane.add1(ScrolledWindow(self.tag_list))

        # Filter input
        hbox.pack_start(gtk.Label(_('Filter') + ': '), False)  # T: Input label
        filter_entry = InputEntry()
        filter_entry.set_icon_to_clear()
        hbox.pack_start(filter_entry, False)
        filter_cb = DelayedCallback(
            500, lambda o: self.task_list.set_filter(filter_entry.get_text()))
        filter_entry.connect('changed', filter_cb)

        # TODO: use menu button here and add same options as in context menu
        #       for filtering the list
        def on_show_active_toggle(o):
            active = self.act_toggle.get_active()
            if self.uistate['only_show_act'] != active:
                self.uistate['only_show_act'] = active
                self.task_list.set_filter_actionable(active)

        self.act_toggle = gtk.CheckButton(_('Only Show Active Tasks'))
        # T: Checkbox in task list - this options hides tasks that are not yet started
        self.act_toggle.set_active(self.uistate['only_show_act'])
        self.act_toggle.connect('toggled', on_show_active_toggle)
        self.uistate.connect(
            'changed', lambda o: self.act_toggle.set_active(self.uistate[
                'only_show_act']))
        hbox.pack_start(self.act_toggle, False)

        # Statistics label
        self.statistics_label = gtk.Label()
        hbox.pack_end(self.statistics_label, False)

        def set_statistics():
            total = self.task_list.get_n_tasks()
            text = ngettext('%i open item', '%i open items', total) % total
            # T: Label for task List, %i is the number of tasks
            self.statistics_label.set_text(text)

        set_statistics()

        def on_tasklist_changed(o):
            self.task_list.refresh()
            self.tag_list.refresh(self.task_list)
            set_statistics()

        callback = DelayedCallback(10, on_tasklist_changed)
        # Don't really care about the delay, but want to
        # make it less blocking - should be async preferably
        # now it is at least on idle

        ### XXX HACK to get dependency to connect to
        ###   -- no access to plugin, so can;t use get_extension()
        ##    -- duplicat of this snippet in MainWindowExtension
        for e in window.ui.notebook.__zim_extension_objects__:
            if hasattr(e, 'indexer'
                       ) and e.indexer.__class__.__name__ == 'TasksIndexer':
                self.connectto(e, 'tasklist-changed', callback)
                break
        else:
            raise AssertionError, 'Could not find tasklist notebook extension'
Пример #22
0
class FileRenameDialog(Dialog):
    """ A dialog for renaming a file. """
    def __init__(self, parent, file):
        title = _('Rename file')  # T: dialog title
        Dialog.__init__(self, parent, title)

        assert isinstance(file,
                          LocalFile) and not isinstance(file, LocalFolder)
        self.old_file = file
        self.new_file = file

        # Add field for entering new filename.
        self.txt_filename = InputEntry()
        self.txt_filename.set_text(self.old_file.basename)
        self.txt_filename.set_activates_default(
            True)  # Make ENTER key press trigger the OK button.
        self.txt_filename.connect("changed", self.do_validate, parent)

        # Add field for showing hints when an error occurs (e.g. filename already exists).
        self.txt_error = Gtk.Label()
        self.txt_error.set_visible(False)

        # Add ok button.
        self.btn_ok = self.get_widget_for_response(
            response_id=Gtk.ResponseType.OK)
        self.btn_ok.set_can_default(True)
        self.btn_ok.grab_default()
        self.btn_ok.set_sensitive(False)

        # Configure dialog.
        self.set_modal(True)
        self.set_default_size(380, 100)
        self.vbox.pack_start(self.txt_filename, False, True, 0)
        self.vbox.pack_start(self.txt_error, False, True, 0)

        # Set focus to search field
        self.txt_filename.grab_focus()

    def do_validate(self, widget, data):
        """ Validating new file name, show error when validation fails and enable/disable ok button. """
        def is_filename(filename):
            """ Returns True when filename does not contain any path declaration. """
            return filename == os.path.basename(filename)

        def does_file_already_exist(filename):
            """ Checks whether the new filename is already taken. """
            return self.old_file.parent().file(filename).exists()

        def show_error(msg):
            """ Displays an error and disables the OK button. """
            self.txt_error.set_text(msg)
            self.txt_error.set_visible(True)
            self.btn_ok.set_sensitive(False)
            return False

        if not widget.get_text():
            return show_error(
                _('File name should not be blank.'))  # T: Error message

        if does_file_already_exist(widget.get_text()):
            return show_error(
                _('A file with that name already exists.'))  # T: Error message

        if not is_filename(widget.get_text()):
            return show_error(
                _('File name should not contain path declaration.')
            )  # T: Error message

        # No errors, hide error label and enable OK button.
        self.txt_error.hide()
        self.btn_ok.set_sensitive(True)
        return True

    def do_response_ok(self):
        self.new_file = self.old_file.parent().file(
            self.txt_filename.get_text())
        self.result = Gtk.ResponseType.OK
        self.close()
Пример #23
0
class SearchDialog(Dialog):

	READY = 0
	SEARCHING = 1
	CANCELLED = 2

	def __init__(self, ui):
		Dialog.__init__(self, ui, _('Search'), # T: Dialog title
			buttons=gtk.BUTTONS_CLOSE, help='Help:Searching',
			defaultwindowsize=(400, 300)
		)

		hbox = gtk.HBox(spacing=5)
		self.vbox.pack_start(hbox, False)
		hbox.pack_start(gtk.Label(_('Search')+': '), False) # T: input label
		self.query_entry = InputEntry()
		hbox.add(self.query_entry)
		self.search_button = gtk.Button(stock=gtk.STOCK_FIND)
		hbox.pack_start(self.search_button, False)

		if gtk.gtk_version >= (2, 20) \
		and gtk.pygtk_version >= (2, 22):
			self.spinner = gtk.Spinner()
			hbox.pack_start(self.spinner, False)
		else:
			self.spinner = None

		self.cancel_button = gtk.Button(stock=gtk.STOCK_STOP)
		hbox.pack_start(self.cancel_button, False)
		self._set_state(self.READY)

		help_text = _(
			'For advanced search you can use operators like\n'
			'AND, OR and NOT. See the help page for more details.'
		) # T: help text for the search dialog
		if gtk.gtk_version >= (2, 12, 0):
			self.query_entry.set_tooltip_text(help_text)
		else:
			tooltips = gtk.Tooltips()
			tooltips.set_tip(self.query_entry, help_text)

		self.namespacecheckbox = gtk.CheckButton(_('Limit search to current namespace'))
			# T: checkbox option in search dialog
		self.vbox.pack_start(self.namespacecheckbox, False)

		# TODO advanced query editor
		# TODO checkbox _('Match c_ase')
		# TODO checkbox _('Whole _word')

		self.results_treeview = SearchResultsTreeView(self.ui)
		self.vbox.add(ScrolledWindow(self.results_treeview))

		self.search_button.connect_object('clicked', self.__class__._search, self)
		self.cancel_button.connect_object('clicked', self.__class__._cancel, self)
		self.query_entry.connect_object('activate', self.__class__._search, self)

	def search(self, query):
		'''Trigger a search to be performed.
		Because search can take a long time to execute it is best to
		call this method after the dialog is shown.

		@param query: the query as string
		'''
		self.query_entry.set_text(query)
		self._search()

	def _search(self):
		string = self.query_entry.get_text()
		if self.namespacecheckbox.get_active():
			string = 'Namespace: "%s" ' % self.ui.page.name + string
		#~ print '!! QUERY: ' + string

		self._set_state(self.SEARCHING)
		try:
			self.results_treeview.search(string)
		except Exception, error:
			ErrorDialog(self, error).run()

		if not self.results_treeview.cancelled:
			self._set_state(self.READY)
		else:
			self._set_state(self.CANCELLED)
Пример #24
0
	def __init__(self, window, index_ext):
		Dialog.__init__(self, window, _('Task List EDS'), # T: dialog title
			buttons=gtk.BUTTONS_CLOSE, help=':Plugins:Task List EDS',
			defaultwindowsize=(550, 400) )
		self.index_ext = index_ext

		hbox = gtk.HBox(spacing=5)
		self.vbox.pack_start(hbox, False)
		self.hpane = HPaned()
		self.uistate.setdefault('hpane_pos', 75)
		self.hpane.set_position(self.uistate['hpane_pos'])
		self.vbox.add(self.hpane)

		# Task list
		opener = window.get_resource_opener()
		self.task_list = TaskListTreeView(
			self.index_ext, opener
		)
		self.task_list.set_headers_visible(True) # Fix for maemo
		self.hpane.add2(ScrolledWindow(self.task_list))

		# Tag list
		self.tag_list = TagListTreeView(self.index_ext, self.task_list)
		self.hpane.add1(ScrolledWindow(self.tag_list))

		# Filter input
		hbox.pack_start(gtk.Label(_('Filter')+': '), False) # T: Input label
		filter_entry = InputEntry()
		filter_entry.set_icon_to_clear()
		hbox.pack_start(filter_entry, False)
		filter_cb = DelayedCallback(500,
			lambda o: self.task_list.set_filter(filter_entry.get_text()))
		filter_entry.connect('changed', filter_cb)

		# Dropdown with options - TODO
		#~ menu = gtk.Menu()
		#~ showtree = gtk.CheckMenuItem(_('Show _Tree')) # T: menu item in options menu
		#~ menu.append(showtree)
		#~ menu.append(gtk.SeparatorMenuItem())
		#~ showall = gtk.RadioMenuItem(None, _('Show _All Items')) # T: menu item in options menu
		#~ showopen = gtk.RadioMenuItem(showall, _('Show _Open Items')) # T: menu item in options menu
		#~ menu.append(showall)
		#~ menu.append(showopen)
		#~ menubutton = MenuButton(_('_Options'), menu) # T: Button label
		#~ hbox.pack_start(menubutton, False)

		# Statistics label
		self.statistics_label = gtk.Label()
		hbox.pack_end(self.statistics_label, False)


		def set_statistics():
			total, stats = self.task_list.get_statistics()
			text = ngettext('%i open item', '%i open items', total) % total
				# T: Label for statistics in Task List, %i is the number of tasks
			text += ' (' + '/'.join(map(str, stats)) + ')'
			self.statistics_label.set_text(text)

		set_statistics()

		def on_tasklist_changed(o):
			self.task_list.refresh()
			self.tag_list.refresh(self.task_list)
			set_statistics()

		callback = DelayedCallback(10, on_tasklist_changed)
			# Don't really care about the delay, but want to
			# make it less blocking - should be async preferably
			# now it is at least on idle
		self.connectto(index_ext, 'tasklist-changed', callback)
Пример #25
0
class SearchDialog(Dialog):

    READY = 0
    SEARCHING = 1
    CANCELLED = 2

    def __init__(self, widget, notebook, page, navigation):
        Dialog.__init__(
            self,
            widget,
            _('Search'),  # T: Dialog title
            buttons=Gtk.ButtonsType.CLOSE,
            help='Help:Searching',
            defaultwindowsize=(400, 300))
        self.page = page

        hbox = Gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False, True, 0)
        hbox.pack_start(Gtk.Label(_('Search') + ': '), False, True,
                        0)  # T: input label
        self.query_entry = InputEntry()
        hbox.add(self.query_entry)
        self.search_button = Gtk.Button.new_with_mnemonic(
            _('_Find'))  # T: Button label
        hbox.pack_start(self.search_button, False, True, 0)

        self.spinner = Gtk.Spinner()
        hbox.pack_start(self.spinner, False, True, 0)

        self.cancel_button = Gtk.Button.new_with_mnemonic(
            _('_Cancel'))  # T: Button label
        hbox.pack_start(self.cancel_button, False, True, 0)
        self._set_state(self.READY)

        help_text = _('For advanced search you can use operators like\n'
                      'AND, OR and NOT. See the help page for more details.'
                      )  # T: help text for the search dialog
        self.query_entry.set_tooltip_text(help_text)

        self.namespacecheckbox = Gtk.CheckButton.new_with_mnemonic(
            _('Limit search to the current page and sub-pages'))
        # T: checkbox option in search dialog
        if page is not None:
            self.vbox.pack_start(self.namespacecheckbox, False, True, 0)

        # TODO advanced query editor
        # TODO checkbox _('Match c_ase')
        # TODO checkbox _('Whole _word')

        self.results_treeview = SearchResultsTreeView(notebook, navigation)
        self.vbox.pack_start(ScrolledWindow(self.results_treeview), True, True,
                             0)

        self.search_button.connect_object('clicked', self.__class__._search,
                                          self)
        self.cancel_button.connect_object('clicked', self.__class__._cancel,
                                          self)
        self.query_entry.connect_object('activate', self.__class__._search,
                                        self)

    def search(self, query):
        '''Trigger a search to be performed.
		Because search can take a long time to execute it is best to
		call this method after the dialog is shown.

		@param query: the query as string
		'''
        self.query_entry.set_text(query)
        self._search()

    def _search(self):
        string = self.query_entry.get_text()
        if self.namespacecheckbox.get_active():
            assert self.page is not None
            string = 'Section: "%s" ' % self.page.name + string
        #~ print('!! QUERY: ' + string)

        self._set_state(self.SEARCHING)
        try:
            self.results_treeview.search(string)
        except Exception as error:
            ErrorDialog(self, error).run()

        if not self.results_treeview.cancelled:
            self._set_state(self.READY)
        else:
            self._set_state(self.CANCELLED)

    def _cancel(self):
        self.results_treeview.cancelled = True

    def _set_state(self, state):
        # TODO set cursor for treeview part
        # TODO set label or something ?
        def hide(button):
            button.hide()
            button.set_no_show_all(True)

        def show(button):
            button.set_no_show_all(False)
            button.show_all()

        if state in (self.READY, self.CANCELLED):
            self.query_entry.set_sensitive(True)
            hide(self.cancel_button)
            if self.spinner:
                self.spinner.stop()
                hide(self.spinner)
            show(self.search_button)
        elif state == self.SEARCHING:
            self.query_entry.set_sensitive(False)
            hide(self.search_button)
            if self.spinner:
                show(self.spinner)
                self.spinner.start()
            show(self.cancel_button)
        else:
            assert False, 'BUG: invalid state'
Пример #26
0
    def init_uistate(self):
        # Initialize all the uistate parameters
        # delayed till show or show_all because all this needs real
        # uistate to be in place and plugins to be loaded
        # Run between loading plugins and actually presenting the window to the user

        if not self._geometry_set:
            # Ignore this if an explicit geometry was specified to the constructor
            if self.uistate['windowpos'] is not None:
                x, y = self.uistate['windowpos']
                self.move(x, y)

            w, h = self.uistate['windowsize']
            self.set_default_size(w, h)

            if self.uistate['windowmaximized']:
                self.maximize()

        # For these two "None" means system default, but we don't know what that default is :(
        self.preferences.setdefault(
            'toolbar_style', None,
            (TOOLBAR_ICONS_ONLY, TOOLBAR_ICONS_AND_TEXT, TOOLBAR_TEXT_ONLY))
        self.preferences.setdefault(
            'toolbar_size', None,
            (TOOLBAR_ICONS_TINY, TOOLBAR_ICONS_SMALL, TOOLBAR_ICONS_LARGE))

        self.toggle_toolbar(self.uistate['show_toolbar'])
        self.toggle_statusbar(self.uistate['show_statusbar'])

        Window.init_uistate(self)  # takes care of sidepane positions etc

        if self.preferences['toolbar_style'] is not None:
            self.set_toolbar_style(self.preferences['toolbar_style'])

        if self.preferences['toolbar_size'] is not None:
            self.set_toolbar_icon_size(self.preferences['toolbar_size'])

        self.toggle_fullscreen(self._set_fullscreen)

        if self.notebook.readonly:
            self.toggle_editable(False)
            action = self.actiongroup.get_action('toggle_editable')
            action.set_sensitive(False)
        else:
            self.toggle_editable(not self.uistate['readonly'])

        # And hook to notebook properties
        self.on_notebook_properties_changed(self.notebook.properties)
        self.notebook.properties.connect('changed',
                                         self.on_notebook_properties_changed)

        # Hook up the statusbar
        self.connect('page-changed', self.do_update_statusbar)
        self.connect('readonly-changed', self.do_update_statusbar)
        self.pageview.connect('modified-changed', self.do_update_statusbar)
        self.notebook.connect_after('stored-page', self.do_update_statusbar)

        # Notify plugins
        self.emit('init-uistate')

        # Update menus etc.
        self.uimanager.ensure_update()
        # Prevent flashing when the toolbar is loaded after showing the window
        # and do this before connecting signal below for accelmap.

        # Add search bar onec toolbar is loaded
        space = Gtk.SeparatorToolItem()
        space.set_draw(False)
        space.set_expand(True)
        self.toolbar.insert(space, -1)

        from zim.gui.widgets import InputEntry
        entry = InputEntry(placeholder_text=_('Search'))
        entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY,
                                  Gtk.STOCK_FIND)
        entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY,
                                    _('Search Pages...'))
        # T: label in search entry
        inline_search = lambda e, *a: self._uiactions.show_search(
            query=e.get_text() or None)
        entry.connect('activate', inline_search)
        entry.connect('icon-release', inline_search)
        entry.show()
        item = Gtk.ToolItem()
        item.add(entry)
        self.toolbar.insert(item, -1)

        # Load accelmap config and setup saving it
        # TODO - this probably belongs in the application class, not here
        accelmap = ConfigManager.get_config_file('accelmap').file
        logger.debug('Accelmap: %s', accelmap.path)
        if accelmap.exists():
            Gtk.AccelMap.load(accelmap.path)

        def on_accel_map_changed(o, path, key, mod):
            logger.info('Accelerator changed for %s', path)
            Gtk.AccelMap.save(accelmap.path)

        Gtk.AccelMap.get().connect('changed', on_accel_map_changed)

        self.do_update_statusbar()
Пример #27
0
class ZoteroDialog(Dialog):
    """The Zotero specific Input Dialog."""
    def __init__(self, pageview, preferences):
        """Initialize the Input Box with options."""
        Dialog.__init__(
            self,
            pageview,
            _('Search in Zotero'),  # T: Dialog title
            button=_('_GO'),  # T: Button label
            defaultwindowsize=(350, 200))

        self.pageview = pageview
        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False, True, 0)
        self.preferences = preferences
        first = None
        options = [
            "Search in Title, Author and Date",
            "Search in All Fields and Tags", "Search Everywhere"
        ]
        for text in options:
            self.radio = Gtk.RadioButton.new_with_mnemonic_from_widget(
                first, text)
            if not first:
                first = self.radio
            self.vbox.pack_start(self.radio, False, True, 0)
            self.radio.show()

    def run(self):
        """Call the widget.dialog.run method."""
        Dialog.run(self)

    def do_response_ok(self):
        """Call to insert citation when pressing ok."""
        text = self.textentry.get_text()
        buffer = self.pageview.textview.get_buffer()
        active = [r for r in self.radio.get_group() if r.get_active()]  # @+
        radiotext = active[0].get_label()  # @+
        self.insert_citation(text, radiotext, buffer)
        return True

    def insert_citation(self, text, radiotext, buffer):
        """Will insert the whole bibliography text."""
        root = "127.0.0.1:23119/zotxt"
        method = ''  # Method defaults to titleCreatorYear
        if "Tags" in radiotext:
            method = '&method=fields'
        elif "Everywhere" in radiotext:
            method = '&method=everything'
        link_format = self.preferences['link_format']
        format = '&format=' + link_format
        url = 'http://' + root + '/search?q=' + text + format + method
        try:
            resp = json.loads(urlopen(url).read())
            if link_format == 'bibliography':
                for i in resp:
                    key = i['key']
                    try:
                        zotlink = 'zotero://' + root + '/select?key=' + key
                        bibtext = i['text']
                        buffer.insert_link_at_cursor(bibtext, href=zotlink)
                        buffer.insert_at_cursor("\n")
                    except:
                        pass
            elif link_format == 'betterbibtexkey':
                for key in resp:
                    try:
                        zotlink = ('zotero://' + root +
                                   '/select?betterbibtexkey=' + key)
                        buffer.insert_link_at_cursor(key, href=zotlink)
                        buffer.insert_at_cursor("\n")
                    except:
                        pass
            elif link_format == 'easykey':
                for key in resp:
                    try:
                        zotlink = ('zotero://' + root + '/select?easykey=' +
                                   key)
                        buffer.insert_link_at_cursor(key, href=zotlink)
                        buffer.insert_at_cursor("\n")
                    except:
                        pass
            elif link_format == 'key':
                for key in resp:
                    try:
                        zotlink = ('zotero://' + root + '/select?key=' + key)
                        buffer.insert_link_at_cursor(key, href=zotlink)
                        buffer.insert_at_cursor("\n")
                    except:
                        pass
            else:
                buffer.insert_at_cursor('link format unknown: ' + link_format +
                                        "\n")
        except:
            pass
Пример #28
0
    def instant_search(self):

        # init
        self.cached_titles = []
        self.last_query = ""  # previous user input
        self.query_o = None
        self.caret = SimpleNamespace(pos=0, text="",
                                     stick=False)  # cursor position
        self.original_page = self.window.page.name  # we return here after escape
        self.original_history = list(self.window.history.uistate["list"])
        self.selection = None
        if not self.plugin.preferences['is_cached']:
            # reset last search results
            State.reset()
        self.menu_page = None
        self.is_closed = False
        self.last_page = None

        # building quick title cache
        def build(start=""):
            o = self.window.notebook.pages
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)

        build()

        # Gtk
        self.gui = Dialog(self.window,
                          _('Search'),
                          buttons=None,
                          defaultwindowsize=(300, -1))
        self.gui.resize(300, 100)  # reset size
        self.input_entry = InputEntry()
        self.input_entry.connect('key_press_event', self.move)
        self.input_entry.connect(
            'changed',
            self.change)  # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.input_entry,
                                 expand=False,
                                 fill=True,
                                 padding=0)
        # noinspection PyArgumentList
        self.label_object = Gtk.Label(label='')
        self.label_object.set_size_request(300, -1)
        self.gui.vbox.pack_start(self.label_object,
                                 expand=False,
                                 fill=True,
                                 padding=0)

        # preview pane
        self.label_preview = Gtk.Label(label='...loading...')
        # not sure if this has effect, longer lines without spaces still make window inflate
        self.label_preview.set_line_wrap(True)
        self.label_preview.set_xalign(0)  # align to the left
        self.preview_pane = Gtk.VBox()
        self.preview_pane.pack_start(self.label_preview, False, False, 5)
        self.window.pageview.pack_start(self.preview_pane, False, False, 5)

        # gui geometry
        self.geometry(init=True)

        self.gui.show_all()
Пример #29
0
class InsertSymbolDialog(Dialog):
    def __init__(self, parent, plugin, pageview):
        Dialog.__init__(
            self,
            parent,
            _('Insert Symbol'),  # T: Dialog title
            button=_('_Insert'),  # T: Button label
            defaultwindowsize=(350, 400))
        self.plugin = plugin
        self.pageview = pageview
        if not plugin.symbols:
            plugin.load_file()

        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False, True, 0)

        model = Gtk.ListStore(str, str)  # text, shortcut
        self.iconview = Gtk.IconView(model)
        self.iconview.set_text_column(0)
        self.iconview.set_column_spacing(0)
        self.iconview.set_row_spacing(0)
        self.iconview.set_property('has-tooltip', True)
        self.iconview.set_property('activate-on-single-click', True)
        self.iconview.connect('query-tooltip', self.on_query_tooltip)
        self.iconview.connect('item-activated', self.on_activated)

        swindow = ScrolledWindow(self.iconview)
        self.vbox.pack_start(swindow, True, True, 0)

        button = Gtk.Button.new_with_mnemonic(_('_Edit'))  # T: Button label
        button.connect('clicked', self.on_edit)
        self.action_area.add(button)
        self.action_area.reorder_child(button, 0)

        self.load_symbols()

    def load_symbols(self):
        model = self.iconview.get_model()
        model.clear()
        for symbol, shortcut in self.plugin.get_symbols():
            model.append((symbol, shortcut))

    def on_query_tooltip(self, iconview, x, y, keyboard, tooltip):
        if keyboard:
            return False

        x, y = iconview.convert_widget_to_bin_window_coords(x, y)
        path = iconview.get_path_at_pos(x, y)
        if path is None:
            return False

        model = iconview.get_model()
        iter = model.get_iter(path)
        text = model.get_value(iter, 1)
        if not text:
            return False

        tooltip.set_text(text)
        return True

    def on_activated(self, iconview, path):
        model = iconview.get_model()
        iter = model.get_iter(path)
        text = model.get_value(iter, 0)
        pos = self.textentry.get_position()
        self.textentry.insert_text(text, pos)
        self.textentry.set_position(pos + len(text))

    def on_edit(self, button):
        file = self.plugin.config.get_config_file('symbols.list')
        if edit_config_file(self, file):
            self.plugin.load_file()
            self.load_symbols()

    def run(self):
        self.iconview.grab_focus()
        Dialog.run(self)

    def do_response_ok(self):
        text = self.textentry.get_text()
        textview = self.pageview.textview
        buffer = textview.get_buffer()
        buffer.insert_at_cursor(text)
        return True
Пример #30
0
class InsertSymbolDialog(Dialog):

    def __init__(self, ui, plugin):
        Dialog.__init__(self, ui, _('Insert Symbol'), # T: Dialog title
            button=(_('_Insert'), 'gtk-ok'),  # T: Button label
            defaultwindowsize=(350, 400) )
        self.plugin = plugin

        self.textentry = InputEntry()
        self.vbox.pack_start(self.textentry, False)

        # TODO make this iconview single-click
        model = gtk.ListStore(str, str) # text, shortcut
        self.iconview = gtk.IconView(model)
        self.iconview.set_text_column(0)
        self.iconview.set_column_spacing(0)
        self.iconview.set_row_spacing(0)
        if gtk.gtk_version >= (2, 12, 0):
            self.iconview.set_property('has-tooltip', True)
            self.iconview.connect('query-tooltip', self.on_query_tooltip)
        self.iconview.connect('item-activated', self.on_activated)

        self.vbox.add(ScrolledWindow(self.iconview))

        button = gtk.Button(stock=gtk.STOCK_EDIT)
        button.connect('clicked', self.on_edit)
        self.action_area.add(button)
        self.action_area.reorder_child(button, 0)

        self.load_symbols()

    def load_symbols(self):
        model = self.iconview.get_model()
        model.clear()
        for symbol, shortcut in self.plugin.get_symbols():
            model.append((symbol, shortcut))

    def on_query_tooltip(self, iconview, x, y, keyboard, tooltip):
        if keyboard: return False

        x, y = iconview.convert_widget_to_bin_window_coords(x, y)
        path = iconview.get_path_at_pos(x, y)
        if path is None: return False

        model = iconview.get_model()
        iter = model.get_iter(path)
        text = model.get_value(iter, 1)
        if not text: return False

        tooltip.set_text(text)
        return True

    def on_activated(self, iconview, path):
        model = iconview.get_model()
        iter = model.get_iter(path)
        text = model.get_value(iter, 0)
        text = text.decode('utf-8')
        pos = self.textentry.get_position()
        self.textentry.insert_text(text, pos)
        self.textentry.set_position(pos + len(text))

    def on_edit(self, button):
        file = config_file('symbols.list')
        if self.ui.edit_config_file(file):
            self.plugin.load_file()
            self.load_symbols()

    def run(self):
        self.iconview.grab_focus()
        Dialog.run(self)

    def do_response_ok(self):
        text = self.textentry.get_text()
        textview = self.plugin.pageview.view
        buffer = textview.get_buffer()
        buffer.insert_at_cursor(text)
        return True
Пример #31
0
class InstantsearchMainWindowExtension(WindowExtension):

    uimanager_xml = '''
    <ui>
    <menubar name='menubar'>
            <menu action='tools_menu'>
                    <placeholder name='plugin_items'>
                            <menuitem action='instantsearch'/>
                    </placeholder>
            </menu>
    </menubar>
    </ui>
    '''


    gui = "";

    @action(_('_Instantsearch'), accelerator='<ctrl>e') # T: menu item
    def instantsearch(self):


        #init
        self.cached_titles = []
        #self.menu = defaultdict(_MenuItem)
        self.lastQuery = "" # previous user input
        self.queryO = None
        self.caret = {'pos':0, 'altPos':0, 'text':""}  # cursor position
        self.originalPage = self.window.ui.page.name # we return here after escape
        self.selection = None
        if not self.plugin.preferences['isCached']:
            # reset last search results
            State.reset()
        self.menuPage = None
        self.isClosed = False
        self.lastPage = None

        # preferences
        self.title_match_char = self.plugin.preferences['title_match_char']
        self.start_search_length = self.plugin.preferences['start_search_length']
        self.keystroke_delay = self.plugin.preferences['keystroke_delay']
        self.open_when_unique = self.plugin.preferences['open_when_unique']

        # building quick title cache
        def build(start = ""):
            if hasattr(self.window.ui.notebook, 'pages'):
                o = self.window.ui.notebook.pages
            else: # for Zim 0.66-
                o = self.window.ui.notebook.index
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)
        build()

        # Gtk
        self.gui = Dialog(self.window.ui, _('Search'), buttons=None, defaultwindowsize=(300, -1))
        self.gui.resize(300, 100) # reset size
        self.inputEntry = InputEntry()
        self.inputEntry.connect('key_press_event', self.move)
        self.inputEntry.connect('changed', self.change) # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.inputEntry, False)
        self.labelObject = gtk.Label((''))
        self.labelObject.set_usize(300, -1)
        self.gui.vbox.pack_start(self.labelObject, False)

        #gui geometry
        px, py = self.window.get_position()
        pw, ph = self.window.get_size()
        x, y = self.gui.get_position()

        if self.plugin.preferences['position'] == InstantsearchPlugin.POSITION_RIGHT:
            self.gui.move((pw-300), 0)
        elif self.plugin.preferences['position'] == InstantsearchPlugin.POSITION_CENTER:
            self.gui.resize(300, 100)
            self.gui.move(px + (pw / 2) - 150, py + (ph / 2) - 250)
        else:
            raise AttributeError("Instant search: Wrong position preference.")

        self.gui.show_all()

        self.labelVar = ""
        self.timeout = ""
        self.timeoutOpenPage = None


    #lastPage = ""
    #pageTitleOnly = False
    menu = []
    #queryTime = 0

    def change(self, _): #widget, event,text
        if self.timeout:
            gobject.source_remove(self.timeout)
        q = self.inputEntry.get_text()
        #print("Change. {} {}".format(input, self.lastQuery))
        if q == self.lastQuery: return
        if q == self.title_match_char: return
        if q and q[-1] == "∀": # easter egg: debug option for zim --standalone
            q = q[:-1]
            import ipdb; ipdb.set_trace()
        self.state = State.setCurrent(q)

        if not self.state.isFinished:
            self.isSubset = True if self.lastQuery and q.startswith(self.lastQuery) else False
            self.state.checkTitleSearch(self.title_match_char)
            self.startSearch()
        else: # search completed before
            #print("Search already cached.")
            self.startSearch() # update the results in a page has been modified meanwhile (not if something got deleted in the notebook #16 )
            self.checkLast()
            self.soutMenu()

        self.lastQuery = q

    def startSearch(self):
        """ Search string has certainly changed. We search in indexed titles and/or we start zim search.

        Normally, zim gives 11 points bonus if the search-string appears in the titles.
        If we are ignoring subpages, the search "foo" will match only page "journal:foo",
        but not "journal:foo:subpage" (and score of the parent page will get slightly higher by 1.)
        However, if there are occurences of the string in the fulltext of the subpage,
        subpage remains in the result, but gets bonus only 2 points (not 11).

        """

        query = self.state.query
        menu = self.state.menu
        isInQuery = re.compile(r"(^|:|\s|\()" + query).search  # 'te' matches this page titles: 'test' or 'Journal:test' or 'foo test' or 'foo (test)'
        if self.isSubset and len(query) < self.start_search_length:
            # letter(s) was/were added and full search has not yet been activated
            for path in _MenuItem.titles:
                if path in self.state.menu and not isInQuery(path.lower()):  # 'te' didnt match 'test' etc
                    del menu[path]  # we pop out the result
                else:
                    menu[path].sure = True
        else: # perform new search in cached_titles
            _MenuItem.titles = set()
            found = 0
            if self.state.firstSeen:
                for path, pathLow in self.cached_titles: # quick search in titles
                    if isInQuery(pathLow): # 'te' matches 'test' or 'Journal:test' etc
                        _MenuItem.titles.add(path)
                        if query in path.lower() and query not in path.lower().split(":")[-1]: # "raz" in "raz:dva", but not in "dva"
                            self.state.menu[":".join(path.split(":")[:-1])].bonus += 1 # 1 point for subpage
                            menu[path].bonus = -11
                        menu[path].score += 10 # 10 points for title (zim default) (so that it gets displayed before search finishes)
                        menu[path].intitle = True
                        menu[path].path = path
                        found += 1
                        if found >= 10: # we dont want more than 10 results; we would easily match all of the pages
                            break
                    else:
                        menu[path].intitle = False

        self.processMenu() # show for now results of title search

        if len(query) >= self.start_search_length:
            self.timeout = gobject.timeout_add(self.keystroke_delay, self.startZimSearch) # ideal delay between keystrokes

    def startZimSearch(self):
        """ Starts search for the input. """
        self.timeout = ""
        self.caret['altPos'] = 0 # possible position of caret - beginning
        s = '"*{}*"'.format(self.state.query) if self.plugin.preferences['isWildcarded'] else self.state.query
        self.queryO = Query(unicode(s)) # beware when searching for unicode character. Update the row when going to Python3.

        lastSel = self.selection if self.isSubset and self.state.previous.isFinished else None # it should be quicker to find the string, if we provide this subset from last time (in the case we just added a letter, so that the subset gets smaller)
        self.selection = SearchSelection(self.window.ui.notebook)
        state = self.state # this is thread, so that self.state would can before search finishes
        self.selection.search(self.queryO, selection=lastSel, callback=self._search_callback(self.state.rawQuery))
        state.isFinished = True

        for item in list(state.menu): # remove all the items that we didnt encounter during the search
            if not state.menu[item].sure:
                del state.menu[item]

        if state == self.state:
            self.checkLast()

        self.processMenu(state=state)

    def checkLast(self):
        """ opens the page if there is only one option in the menu """
        if self.open_when_unique and len(self.state.menu) == 1:
            self._open_page(Path(self.state.menu.keys()[0]), excludeFromHistory=False)
            self.close()

    def _search_callback(self, query):
        def _search_callback(results, path):
            if results is not None:
                self._update_results(results, State.get(query)) # we finish the search even if another search is running. If we returned False, the search would be cancelled-
            while gtk.events_pending():
                gtk.main_iteration(block=False)
            return True
        return _search_callback

    def _update_results(self, results, state):
        """
        This method may run many times, due to the _update_results, which are updated many times.
        I may set that _update_results would run only once, but this is nice - the results are appearing one by one.
        """
        changed = False

        state.lastResults = results
        for option in results.scores:
            if state.pageTitleOnly and state.query not in option.name: # hledame jen v nazvu stranky
                continue

            if option.name not in state.menu: # new item found
                if state == self.state and option.name == self.caret['text']: # this is current search
                    self.caret['altPos'] = len(state.menu)-1 #karet byl na tehle pozici, pokud se zuzil vyber, budeme vedet, kam karet opravne umistit
            if option.name not in state.menu or (state.menu[option.name].bonus < 0 and state.menu[option.name].score == 0):
                changed = True
            if not state.menu[option.name].sure:
                state.menu[option.name].sure = True
                changed = True
            state.menu[option.name].score = results.scores[option] #zaradit mezi moznosti

        if changed: # we added a page
            self.processMenu(state=state, sort=False)
        else:
            pass

    def processMenu(self, state=None, sort=True):
        """ Sort menu and generate items and sout menu. """
        if state is None:
            state = self.state

        if sort:
            state.items = sorted(state.menu, reverse=True, key=lambda item: (state.menu[item].intitle, state.menu[item].score + state.menu[item].bonus, -item.count(":"), item))
        else: # when search results are being updated, it's good when the order doesnt change all the time. So that the first result does not become for a while 10th and then become first back.
            state.items = sorted(state.menu, reverse=True, key=lambda item: (state.menu[item].intitle, -state.menu[item].lastOrder))

        state.items = [item for item in state.items if (state.menu[item].score + state.menu[item].bonus) > 0] # i dont know why there are items with 0 score

        if state == self.state:
            self.soutMenu()

    def soutMenu(self, displayImmediately=False):
        """ Displays menu and handles caret position. """
        if self.timeoutOpenPage:
            gobject.source_remove(self.timeoutOpenPage)
        self.gui.resize(300, 100) # reset size
        # treat possible caret deflection
        if self.caret['pos'] < 0 or self.caret['pos'] > len(self.state.items)-1: # place the caret to the beginning or the end of list
            self.caret['pos'] = self.caret['altPos']

        text = ""
        i = 0
        for item in self.state.items:
            score = self.state.menu[item].score + self.state.menu[item].bonus
            self.state.menu[item].lastOrder = i
            if i == self.caret['pos']:
                self.caret['text'] = item # caret is at this position
                text += '→ {} ({}) {}\n'.format(item, score, "" if self.state.menu[item].sure else "?")
            else:
                try:
                    text += '{} ({}) {}\n'.format(item, score, "" if self.state.menu[item].sure else "?")
                except:
                    text += "CHYBA\n"
                    text += item[0:-1] + "\n"
            i += 1

        self.labelObject.set_text(text)
        self.menuPage = Path(self.caret['text'] if len(self.state.items) else self.originalPage)

        if not displayImmediately:
            self.timeoutOpenPage = gobject.timeout_add(self.keystroke_delay, self._open_page, self.menuPage) # ideal delay between keystrokes
        else:
            self._open_page(self.menuPage)

    def move(self, widget, event):
        """ Move caret up and down. Enter to confirm, Esc closes search."""
        keyname = gtk.gdk.keyval_name(event.keyval)
        if keyname == "Up" or keyname == "ISO_Left_Tab":
            self.caret['pos'] -= 1
            self.soutMenu(displayImmediately=False)

        if keyname == "Down" or keyname == "Tab":
            self.caret['pos'] += 1
            self.soutMenu(displayImmediately=False)

        if keyname == "KP_Enter" or keyname == "Return":
            self._open_page(self.menuPage, excludeFromHistory=False)
            self.close()

        if keyname == "Escape":
            self._open_original()
            # GTK closes the windows itself on Escape, no self.close() needed

        return

    ## Safely closes
    # Xwhen closing directly, Python gave allocation error
    def close(self):
        if not self.isClosed:
            self.isClosed = True
            self.gui.emit("close")

    def _open_original(self):
        self._open_page(Path(self.originalPage))

    def _open_page(self, page, excludeFromHistory=True):
        """ Open page and highlight matches """
        self.timeoutOpenPage = None # no delayed page will be open
        if self.isClosed == True:
            return
        if page and page.name and page.name != self.lastPage:
            self.lastPage = page.name
            #print("*** HISTORY BEF", self.window.ui.history._history[-3:])
            self.window.ui.open_page(page)
            if excludeFromHistory:
                # there is no public API, so lets use protected _history instead
                self.window.ui.history._history.pop()
                self.window.ui.history._current = len(self.window.ui.history._history) - 1
        if not excludeFromHistory and self.window.ui.history.get_current().name is not page.name:
            # we insert the page to the history because it was likely to be just visited and excluded
            self.window.ui.history.append(page)

        # Popup find dialog with same query
        if self.queryO:# and self.queryO.simple_match:
            string = self.state.query
            string = string.strip('*') # support partial matches
            if self.plugin.preferences['highlight_search']:
                self._get_mainwindow().pageview.show_find(string, highlight=True)

    def _get_mainwindow(self): # #18
        try:
            return self.window.ui._mainwindow
        except AttributeError:
            return self.window.ui.mainwindow
Пример #32
0
    def __init__(self, parent, tasksview, properties):
        Dialog.__init__(
            self,
            parent,
            _('Task List'),  # T: dialog title
            buttons=Gtk.ButtonsType.CLOSE,
            help=':Plugins:Task List',
            defaultwindowsize=(550, 400))
        self.properties = properties
        self.tasksview = tasksview
        self.notebook = parent.notebook

        hbox = Gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False, True, 0)
        self.hpane = HPaned()
        self.uistate.setdefault('hpane_pos', 75)
        self.hpane.set_position(self.uistate['hpane_pos'])
        self.vbox.pack_start(self.hpane, True, True, 0)

        # Task list
        self.uistate.setdefault('only_show_act', False)
        self.uistate.setdefault('show_flatlist', False)
        self.uistate.setdefault('sort_column', 0)
        self.uistate.setdefault('sort_order', int(Gtk.SortType.DESCENDING))

        opener = parent.navigation
        self.task_list = TaskListTreeView(
            self.tasksview,
            opener,
            _parse_task_labels(properties['labels']),
            nonactionable_tags=_parse_task_labels(
                properties['nonactionable_tags']),
            filter_actionable=self.uistate['only_show_act'],
            tag_by_page=properties['tag_by_page'],
            use_workweek=properties['use_workweek'],
            flatlist=self.uistate['show_flatlist'],
            sort_column=self.uistate['sort_column'],
            sort_order=self.uistate['sort_order'])
        self.task_list.set_headers_visible(True)
        self.task_list.connect('populate-popup', self.on_populate_popup)
        self.hpane.add2(ScrolledWindow(self.task_list))

        # Tag list
        self.tag_list = TagListTreeView(self.task_list)
        self.hpane.add1(ScrolledWindow(self.tag_list))

        self.connectto(properties, 'changed', self.on_properties_changed)

        # Filter input
        hbox.pack_start(Gtk.Label(_('Filter') + ': '), False, True,
                        0)  # T: Input label
        filter_entry = InputEntry()
        filter_entry.set_icon_to_clear()
        hbox.pack_start(filter_entry, False, True, 0)
        filter_cb = DelayedCallback(
            500, lambda o: self.task_list.set_filter(filter_entry.get_text()))
        filter_entry.connect('changed', filter_cb)

        # TODO: use menu button here and add same options as in context menu
        #       for filtering the list
        def on_show_active_toggle(o):
            active = self.act_toggle.get_active()
            if self.uistate['only_show_act'] != active:
                self.uistate['only_show_act'] = active
                self.task_list.set_filter_actionable(active)

        self.act_toggle = Gtk.CheckButton.new_with_mnemonic(
            _('Only Show Active Tasks'))
        # T: Checkbox in task list - this options hides tasks that are not yet started
        self.act_toggle.set_active(self.uistate['only_show_act'])
        self.act_toggle.connect('toggled', on_show_active_toggle)
        self.uistate.connect(
            'changed', lambda o: self.act_toggle.set_active(self.uistate[
                'only_show_act']))
        hbox.pack_start(self.act_toggle, False, True, 0)

        # Statistics label
        self.statistics_label = Gtk.Label()
        hbox.pack_end(self.statistics_label, False, True, 0)

        def set_statistics():
            total = self.task_list.get_n_tasks()
            text = ngettext('%i open item', '%i open items', total) % total
            # T: Label for task List, %i is the number of tasks
            self.statistics_label.set_text(text)

        set_statistics()

        def on_tasklist_changed(o):
            self.task_list.refresh()
            self.tag_list.refresh(self.task_list)
            set_statistics()

        callback = DelayedCallback(10, on_tasklist_changed)
        # Don't really care about the delay, but want to
        # make it less blocking - should be async preferably
        # now it is at least on idle

        from . import TaskListNotebookExtension
        nb_ext = find_extension(self.notebook, TaskListNotebookExtension)
        self.connectto(nb_ext, 'tasklist-changed', callback)
Пример #33
0
class GoogletasksNewTaskDialog(Dialog):

    def __init__(self, *args, task=None, controller=None, **kwargs):
        self.input_title = None
        self.input_due = None
        self.input_notes = None
        self.label_object = None
        self.label_due = None
        self.task = task
        self.controller = controller
        super().__init__(*args, **kwargs)

    # noinspection PyArgumentList
    def setup(self):
        self.resize(300, 100)  # reset size

        # title
        self.label_object = Gtk.Label(label='Update Google task' if self.task.get("id", "") else 'Create Google tasks')
        self.label_object.set_size_request(300, -1)
        self.vbox.pack_start(self.label_object, expand=False, fill=True, padding=0)

        # editable text fields (title and notes)
        self.input_title = InputEntry(allow_empty=False, placeholder_text="task title")
        self.vbox.pack_start(self.input_title, expand=False, fill=True, padding=0)
        self.input_notes = Gtk.TextView()

        # date field
        self.input_due = InputEntry(allow_empty=False)
        try:
            s = self.controller.get_time(mode="date-only", from_string=self.task["due"], past_dates=False)
        except (ValueError, KeyError):  # task["due"] is not set or is in past
            s = self.controller.get_time(add_days=1, mode="date-only")
        self.input_due.set_text(s)
        self.input_due.connect('changed', self.update_date)
        self.label_due = Gtk.Label(self.controller.get_time(add_days=1, mode="day"))
        hbox = Gtk.HBox(spacing=1)
        hbox.pack_start(self.input_due, expand=True, fill=True, padding=0)
        hbox.pack_start(self.label_due, expand=True, fill=True, padding=0)
        self.vbox.pack_start(hbox, expand=False, fill=True, padding=0)

        # we cant tab out from notes textarea field, hence its placed under date
        self.vbox.pack_start(self.input_notes, expand=False, fill=True, padding=0)

        # arbitrary postponing buttons
        hbox = Gtk.HBox()

        def butt(label=None, date=None, days=None):
            """ Call either with label and date are with days."""
            if days:
                label = f"{'_' if days < 10 else ''}{days} {self.controller.get_time(add_days=days, mode='day')}"
                date = self.controller.get_time(add_days=days, mode="date-only")
            b = Gtk.Button.new_with_mnemonic(label=label)
            b.connect("clicked", self.postpone(date))
            b.set_tooltip_text(date)
            hbox.pack_start(b, expand=False, fill=True, padding=0)

        if self.controller.preferences["button_monday"]:
            butt("_Monday", self._slippy_date(next_monday=True))
        if self.controller.preferences["button_next_monday"]:
            butt("_Next Monday", self._slippy_date(next_monday=True, relative_delta={"days": 7}))
        if self.controller.preferences["button_next_month"]:
            butt("Mon_th", self._slippy_date(relative_delta={"months": 1, "day": 1}))
        if self.controller.preferences["button_next_year"]:
            butt("_Year", self._slippy_date(relative_delta={"years": 1, "month": 2, "day": 1}))

        if hbox.get_children():  # there were some buttons to display
            self.vbox.add(hbox)

        # 9 (or more) postponing buttons
        days = self.controller.preferences["postponing_days"]
        if days:
            hbox = Gtk.HBox()
            self.vbox.add(hbox)
            hbox.pack_start(Gtk.Label(label='Days: '), expand=False, fill=True, padding=0)
            for i in range(1, days + 1):
                butt(days=i)
                if not i % 9:  # put every 9 buttons to another line, do not expand the row to infinity
                    hbox = Gtk.HBox()
                    self.vbox.add(hbox)

        # predefined fields
        if "title" in self.task:
            self.input_title.set_text(self.task["title"])
        if "notes" in self.task:
            self.input_notes.get_buffer().set_text(self.task["notes"])
        if "due" in self.task:
            logger.error("Not yet implemented")  # XX

        # display window
        self.show_all()
        return self

    def _slippy_date(self, add_days=0, next_monday=False, relative_delta=None):

        def next_weekday(d, weekday):
            days_ahead = weekday - d.weekday()
            if days_ahead <= 0:  # Target day already happened this week
                days_ahead += 7
            return d + datetime.timedelta(days_ahead)

        d = self.controller.get_time(add_days=add_days, mode="object")

        if next_monday:
            d = next_weekday(d, 0)  # 0 = Monday, 1=Tuesday, 2=Wednesday...
        if relative_delta:
            d += relativedelta(**relative_delta)
        return self.controller.get_time(use_date=d, mode="date-only")

    def update_date(self, _):
        try:
            day = self.controller.get_time(from_string=self.input_due.get_text(), mode="day", past_dates=False)
        except ValueError:
            day = INVALID_DAY
        self.label_due.set_text(day)

    def _load_task(self):
        # noinspection PyBroadException
        try:
            o = self.input_notes.get_buffer()
            self.task["notes"] = o.get_text(*o.get_bounds(), include_hidden_chars=True)
            self.task["title"] = self.input_title.get_text()
            if self.input_due.get_text():
                try:
                    self.task["due"] = self.controller.get_time(from_string=self.input_due.get_text(), mode="morning")
                except Exception:
                    logger.error("Failed due date parsing")
                    raise
        except Exception:
            pass

    def do_response(self, id_):
        """ we cant use parent function because Esc is not handled as cancel """
        if id_ == Gtk.ResponseType.OK:
            self.do_response_ok()
        else:
            self.do_response_cancel()

    def do_response_ok(self):
        if self.label_due.get_text() == INVALID_DAY:
            return False
        self._load_task()
        self.destroy()  # immediately close (so that we wont hit Ok twice)
        if not self.controller.submit_task(task=self.task):
            self.do_response_cancel()
        return True

    def do_response_cancel(self):
        """ something failed, restore original text in the zim-page """
        text = self.controller.get_task_text(self.task, self.controller.preferences["include_start_date"])
        if text:
            buffer = self.controller.window.pageview.textview.get_buffer()
            buffer.insert_parsetree_at_cursor(Parser().parse(text))
        self.destroy()

    # def postpone(self, _, number):
    #     self.input_due.set_text(self.controller.get_time(add_days=number, mode="date-only"))
    #     self.do_response_ok()
    def postpone(self, date):

        def _(_):
            self.input_due.set_text(date)
            self.do_response_ok()

        return _
Пример #34
0
class SearchDialog(Dialog):

    READY = 0
    SEARCHING = 1
    CANCELLED = 2

    def __init__(self, ui):
        Dialog.__init__(
            self,
            ui,
            _('Search'),  # T: Dialog title
            buttons=gtk.BUTTONS_CLOSE,
            help='Help:Searching',
            defaultwindowsize=(400, 300))

        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        hbox.pack_start(gtk.Label(_('Search') + ': '), False)  # T: input label
        self.query_entry = InputEntry()
        hbox.add(self.query_entry)
        self.search_button = gtk.Button(stock=gtk.STOCK_FIND)
        hbox.pack_start(self.search_button, False)

        if gtk.gtk_version >= (2, 20) \
        and gtk.pygtk_version >= (2, 22): # update in pygtk was later
            self.spinner = gtk.Spinner()
            hbox.pack_start(self.spinner, False)
        else:
            self.spinner = None

        self.cancel_button = gtk.Button(stock=gtk.STOCK_STOP)
        hbox.pack_start(self.cancel_button, False)
        self._set_state(self.READY)

        help_text = _('For advanced search you can use operators like\n'
                      'AND, OR and NOT. See the help page for more details.'
                      )  # T: help text for the search dialog
        if gtk.gtk_version >= (2, 12) \
        and gtk.pygtk_version >= (2, 12):
            self.query_entry.set_tooltip_text(help_text)
        else:
            tooltips = gtk.Tooltips()
            tooltips.set_tip(self.query_entry, help_text)

        self.namespacecheckbox = gtk.CheckButton(
            _('Limit search to the current page and sub-pages'))
        # T: checkbox option in search dialog
        self.vbox.pack_start(self.namespacecheckbox, False)

        # TODO advanced query editor
        # TODO checkbox _('Match c_ase')
        # TODO checkbox _('Whole _word')

        self.results_treeview = SearchResultsTreeView(self.ui)
        self.vbox.add(ScrolledWindow(self.results_treeview))

        self.search_button.connect_object('clicked', self.__class__._search,
                                          self)
        self.cancel_button.connect_object('clicked', self.__class__._cancel,
                                          self)
        self.query_entry.connect_object('activate', self.__class__._search,
                                        self)

    def search(self, query):
        '''Trigger a search to be performed.
		Because search can take a long time to execute it is best to
		call this method after the dialog is shown.

		@param query: the query as string
		'''
        self.query_entry.set_text(query)
        self._search()

    def _search(self):
        string = self.query_entry.get_text()
        if self.namespacecheckbox.get_active():
            string = 'Section: "%s" ' % self.ui.page.name + string
        #~ print '!! QUERY: ' + string

        self._set_state(self.SEARCHING)
        try:
            self.results_treeview.search(string)
        except Exception, error:
            ErrorDialog(self, error).run()

        if not self.results_treeview.cancelled:
            self._set_state(self.READY)
        else:
            self._set_state(self.CANCELLED)
Пример #35
0
    def setup(self):
        self.resize(300, 100)  # reset size

        # title
        self.label_object = Gtk.Label(label='Update Google task' if self.task.get("id", "") else 'Create Google tasks')
        self.label_object.set_size_request(300, -1)
        self.vbox.pack_start(self.label_object, expand=False, fill=True, padding=0)

        # editable text fields (title and notes)
        self.input_title = InputEntry(allow_empty=False, placeholder_text="task title")
        self.vbox.pack_start(self.input_title, expand=False, fill=True, padding=0)
        self.input_notes = Gtk.TextView()

        # date field
        self.input_due = InputEntry(allow_empty=False)
        try:
            s = self.controller.get_time(mode="date-only", from_string=self.task["due"], past_dates=False)
        except (ValueError, KeyError):  # task["due"] is not set or is in past
            s = self.controller.get_time(add_days=1, mode="date-only")
        self.input_due.set_text(s)
        self.input_due.connect('changed', self.update_date)
        self.label_due = Gtk.Label(self.controller.get_time(add_days=1, mode="day"))
        hbox = Gtk.HBox(spacing=1)
        hbox.pack_start(self.input_due, expand=True, fill=True, padding=0)
        hbox.pack_start(self.label_due, expand=True, fill=True, padding=0)
        self.vbox.pack_start(hbox, expand=False, fill=True, padding=0)

        # we cant tab out from notes textarea field, hence its placed under date
        self.vbox.pack_start(self.input_notes, expand=False, fill=True, padding=0)

        # arbitrary postponing buttons
        hbox = Gtk.HBox()

        def butt(label=None, date=None, days=None):
            """ Call either with label and date are with days."""
            if days:
                label = f"{'_' if days < 10 else ''}{days} {self.controller.get_time(add_days=days, mode='day')}"
                date = self.controller.get_time(add_days=days, mode="date-only")
            b = Gtk.Button.new_with_mnemonic(label=label)
            b.connect("clicked", self.postpone(date))
            b.set_tooltip_text(date)
            hbox.pack_start(b, expand=False, fill=True, padding=0)

        if self.controller.preferences["button_monday"]:
            butt("_Monday", self._slippy_date(next_monday=True))
        if self.controller.preferences["button_next_monday"]:
            butt("_Next Monday", self._slippy_date(next_monday=True, relative_delta={"days": 7}))
        if self.controller.preferences["button_next_month"]:
            butt("Mon_th", self._slippy_date(relative_delta={"months": 1, "day": 1}))
        if self.controller.preferences["button_next_year"]:
            butt("_Year", self._slippy_date(relative_delta={"years": 1, "month": 2, "day": 1}))

        if hbox.get_children():  # there were some buttons to display
            self.vbox.add(hbox)

        # 9 (or more) postponing buttons
        days = self.controller.preferences["postponing_days"]
        if days:
            hbox = Gtk.HBox()
            self.vbox.add(hbox)
            hbox.pack_start(Gtk.Label(label='Days: '), expand=False, fill=True, padding=0)
            for i in range(1, days + 1):
                butt(days=i)
                if not i % 9:  # put every 9 buttons to another line, do not expand the row to infinity
                    hbox = Gtk.HBox()
                    self.vbox.add(hbox)

        # predefined fields
        if "title" in self.task:
            self.input_title.set_text(self.task["title"])
        if "notes" in self.task:
            self.input_notes.get_buffer().set_text(self.task["notes"])
        if "due" in self.task:
            logger.error("Not yet implemented")  # XX

        # display window
        self.show_all()
        return self
Пример #36
0
    def __init__(self, plugin):
        if ui_environment['platform'] == 'maemo':
            defaultsize = (800, 480)
        else:
            defaultsize = (550, 400)

        Dialog.__init__(self, plugin.ui, _('Task List'),  # T: dialog title
            buttons=gtk.BUTTONS_CLOSE, help=':Plugins:Task List',
            defaultwindowsize=defaultsize)
        
        self.plugin = plugin
        if ui_environment['platform'] == 'maemo':
            self.resize(800, 480)
            # Force maximum dialog size under maemo, otherwise
            # we'll end with a too small dialog and no way to resize it
        hbox = gtk.HBox(spacing=5)
        self.vbox.pack_start(hbox, False)
        self.hpane = HPaned()
        self.uistate.setdefault('hpane_pos', 75)
        self.hpane.set_position(self.uistate['hpane_pos'])
        self.vbox.add(self.hpane)

        # Task list
        self.uistate.setdefault('only_show_act', False)
        self.task_list = TaskListTreeView(self.ui, plugin, filter_actionable=self.uistate['only_show_act'])
        self.task_list.set_headers_visible(True)  # Fix for maemo
        self.hpane.add2(ScrolledWindow(self.task_list))

        # Tag list
        self.tag_list = TagListTreeView(self.task_list)
        self.hpane.add1(ScrolledWindow(self.tag_list))

        # Filter input
        hbox.pack_start(gtk.Label(_('Filter') + ': '), False)  # T: Input label
        filter_entry = InputEntry()
        filter_entry.set_icon_to_clear()
        hbox.pack_start(filter_entry, False)
        filter_cb = DelayedCallback(500,
            lambda o: self.task_list.set_filter(filter_entry.get_text()))
        filter_entry.connect('changed', filter_cb)

        # Dropdown with options - TODO
        # ~ menu = gtk.Menu()
        # ~ showtree = gtk.CheckMenuItem(_('Show _Tree')) # T: menu item in options menu
        # ~ menu.append(showtree)
        # ~ menu.append(gtk.SeparatorMenuItem())
        # ~ showall = gtk.RadioMenuItem(None, _('Show _All Items')) # T: menu item in options menu
        # ~ showopen = gtk.RadioMenuItem(showall, _('Show _Open Items')) # T: menu item in options menu
        # ~ menu.append(showall)
        # ~ menu.append(showopen)
        # ~ menubutton = MenuButton(_('_Options'), menu) # T: Button label
        # ~ hbox.pack_start(menubutton, False)

        self.act_toggle = gtk.CheckButton(_('Only Show Actionable Tasks'))
            # T: Checkbox in task list
        self.act_toggle.set_active(self.uistate['only_show_act'])
        self.act_toggle.connect('toggled', lambda o: self.task_list.set_filter_actionable(o.get_active()))
        hbox.pack_start(self.act_toggle, False)

        # Statistics label
        self.statistics_label = gtk.Label()
        hbox.pack_end(self.statistics_label, False)


        def set_statistics():
            total, stats = self.task_list.get_statistics()
            text = ngettext('%i open item', '%i open items', total) % total
                # T: Label for statistics in Task List, %i is the number of tasks
            text += ' (' + '/'.join(map(str, stats)) + ')'
            self.statistics_label.set_text(text)

        set_statistics()

        def on_tasklist_changed(o):
            self.task_list.refresh()
            self.tag_list.refresh(self.task_list)
            set_statistics()

        callback = DelayedCallback(10, on_tasklist_changed)
            # Don't really care about the delay, but want to
            # make it less blocking - should be async preferably
            # now it is at least on idle
        self.connectto(plugin, 'tasklist-changed', callback)
Пример #37
0
class InstantsearchMainWindowExtension(MainWindowExtension):
    uimanager_xml = '''
    <ui>
    <menubar name='menubar'>
            <menu action='tools_menu'>
                    <placeholder name='plugin_items'>
                            <menuitem action='instantsearch'/>
                    </placeholder>
            </menu>
    </menubar>
    </ui>
    '''

    gui = ""

    @action(_('_Instantsearch'), accelerator='<ctrl>e')  # T: menu item
    def instantsearch(self):

        # init
        self.cached_titles = []
        # self.menu = defaultdict(_MenuItem)
        self.lastQuery = ""  # previous user input
        self.queryO = None
        self.caret = {'pos': 0, 'altPos': 0, 'text': ""}  # cursor position
        self.originalPage = self.window.page.name  # we return here after escape
        self.selection = None
        if not self.plugin.preferences['isCached']:
            # reset last search results
            State.reset()
        self.menuPage = None
        self.isClosed = False
        self.lastPage = None

        # preferences
        self.title_match_char = self.plugin.preferences['title_match_char']
        self.start_search_length = self.plugin.preferences[
            'start_search_length']
        self.keystroke_delay = self.plugin.preferences['keystroke_delay']
        self.open_when_unique = self.plugin.preferences['open_when_unique']

        # building quick title cache
        def build(start=""):
            if hasattr(self.window.notebook, 'pages'):
                o = self.window.notebook.pages
            else:  # for Zim 0.66-
                o = self.window.notebook.index
            for s in o.list_pages(Path(start or ":")):
                start2 = (start + ":" if start else "") + s.basename
                self.cached_titles.append((start2, start2.lower()))
                build(start2)

        build()

        # Gtk
        self.gui = Dialog(self.window,
                          _('Search'),
                          buttons=None,
                          defaultwindowsize=(300, -1))
        self.gui.resize(300, 100)  # reset size
        self.inputEntry = InputEntry()
        self.inputEntry.connect('key_press_event', self.move)
        self.inputEntry.connect(
            'changed',
            self.change)  # self.change is needed by GObject or something
        self.gui.vbox.pack_start(self.inputEntry,
                                 expand=False,
                                 fill=True,
                                 padding=0)
        self.labelObject = Gtk.Label(label=(''))
        self.labelObject.set_size_request(300, -1)
        self.gui.vbox.pack_start(self.labelObject,
                                 expand=False,
                                 fill=True,
                                 padding=0)

        # gui geometry
        px, py = self.window.get_position()
        pw, ph = self.window.get_size()
        x, y = self.gui.get_position()

        if self.plugin.preferences[
                'position'] == InstantsearchPlugin.POSITION_RIGHT:
            self.gui.move((pw - 300), 0)
        elif self.plugin.preferences[
                'position'] == InstantsearchPlugin.POSITION_CENTER:
            self.gui.resize(300, 100)
            self.gui.move(px + (pw / 2) - 150, py + (ph / 2) - 250)
        else:
            raise AttributeError("Instant search: Wrong position preference.")

        self.gui.show_all()

        self.labelVar = ""
        self.timeout = ""
        self.timeoutOpenPage = None

    # lastPage = ""
    # pageTitleOnly = False
    menu = []

    # queryTime = 0

    def change(self, _):  # widget, event,text
        if self.timeout:
            GObject.source_remove(self.timeout)
        q = self.inputEntry.get_text()
        # print("Change. {} {}".format(input, self.lastQuery))
        if q == self.lastQuery: return
        if q == self.title_match_char: return
        if q and q[-1] == "∀":  # easter egg: debug option for zim --standalone
            q = q[:-1]
            import ipdb
            ipdb.set_trace()
        self.state = State.setCurrent(q)

        if not self.state.isFinished:
            self.isSubset = True if self.lastQuery and q.startswith(
                self.lastQuery) else False
            self.state.checkTitleSearch(self.title_match_char)
            self.startSearch()
        else:  # search completed before
            # print("Search already cached.")
            self.startSearch(
            )  # update the results in a page has been modified meanwhile (not if something got deleted in the notebook #16 )
            self.checkLast()
            self.soutMenu()

        self.lastQuery = q

    def startSearch(self):
        """ Search string has certainly changed. We search in indexed titles and/or we start zim search.

        Normally, zim gives 11 points bonus if the search-string appears in the titles.
        If we are ignoring subpages, the search "foo" will match only page "journal:foo",
        but not "journal:foo:subpage" (and score of the parent page will get slightly higher by 1.)
        However, if there are occurences of the string in the fulltext of the subpage,
        subpage remains in the result, but gets bonus only 2 points (not 11).

        """

        query = self.state.query
        menu = self.state.menu
        isInQuery = re.compile(
            r"(^|:|\s|\()" + query
        ).search  # 'te' matches this page titles: 'test' or 'Journal:test' or 'foo test' or 'foo (test)'
        if self.isSubset and len(query) < self.start_search_length:
            # letter(s) was/were added and full search has not yet been activated
            for path in _MenuItem.titles:
                if path in self.state.menu and not isInQuery(
                        path.lower()):  # 'te' didnt match 'test' etc
                    del menu[path]  # we pop out the result
                else:
                    menu[path].sure = True
        else:  # perform new search in cached_titles
            _MenuItem.titles = set()
            found = 0
            if self.state.firstSeen:
                for path, pathLow in self.cached_titles:  # quick search in titles
                    if isInQuery(
                            pathLow
                    ):  # 'te' matches 'test' or 'Journal:test' etc
                        _MenuItem.titles.add(path)
                        if query in path.lower() and query not in path.lower(
                        ).split(":"
                                )[-1]:  # "raz" in "raz:dva", but not in "dva"
                            self.state.menu[":".join(path.split(
                                ":")[:-1])].bonus += 1  # 1 point for subpage
                            menu[path].bonus = -11
                        menu[
                            path].score += 10  # 10 points for title (zim default) (so that it gets displayed before search finishes)
                        menu[path].intitle = True
                        menu[path].path = path
                        found += 1
                        if found >= 10:  # we dont want more than 10 results; we would easily match all of the pages
                            break
                    else:
                        menu[path].intitle = False

        self.processMenu()  # show for now results of title search

        if len(query) >= self.start_search_length:
            self.timeout = GObject.timeout_add(
                self.keystroke_delay,
                self.startZimSearch)  # ideal delay between keystrokes

    def startZimSearch(self):
        """ Starts search for the input. """
        self.timeout = ""
        self.caret['altPos'] = 0  # possible position of caret - beginning
        s = '"*{}*"'.format(
            self.state.query
        ) if self.plugin.preferences['isWildcarded'] else self.state.query
        self.queryO = Query(
            s
        )  # Xunicode(s) beware when searching for unicode character. Update the row when going to Python3.

        lastSel = self.selection if self.isSubset and self.state.previous.isFinished else None  # it should be quicker to find the string, if we provide this subset from last time (in the case we just added a letter, so that the subset gets smaller)
        self.selection = SearchSelection(self.window.notebook)
        state = self.state  # this is thread, so that self.state would can before search finishes
        self.selection.search(self.queryO,
                              selection=lastSel,
                              callback=self._search_callback(
                                  self.state.rawQuery))
        state.isFinished = True

        for item in list(
                state.menu
        ):  # remove all the items that we didnt encounter during the search
            if not state.menu[item].sure:
                del state.menu[item]

        if state == self.state:
            self.checkLast()

        self.processMenu(state=state)

    def checkLast(self):
        """ opens the page if there is only one option in the menu """
        if self.open_when_unique and len(self.state.menu) == 1:
            self._open_page(Path(self.state.menu.keys()[0]),
                            excludeFromHistory=False)
            self.close()

    def _search_callback(self, query):
        def _search_callback(results, path):
            if results is not None:
                self._update_results(
                    results, State.get(query)
                )  # we finish the search even if another search is running. If we returned False, the search would be cancelled-
            while Gtk.events_pending():
                Gtk.main_iteration_do(blocking=False)
            return True

        return _search_callback

    def _update_results(self, results, state):
        """
        This method may run many times, due to the _update_results, which are updated many times.
        I may set that _update_results would run only once, but this is nice - the results are appearing one by one.
        """
        changed = False

        state.lastResults = results
        for option in results.scores:
            if state.pageTitleOnly and state.query not in option.name:  # hledame jen v nazvu stranky
                continue

            if option.name not in state.menu:  # new item found
                if state == self.state and option.name == self.caret[
                        'text']:  # this is current search
                    self.caret['altPos'] = len(
                        state.menu
                    ) - 1  # karet byl na tehle pozici, pokud se zuzil vyber, budeme vedet, kam karet opravne umistit
            if option.name not in state.menu or (
                    state.menu[option.name].bonus < 0
                    and state.menu[option.name].score == 0):
                changed = True
            if not state.menu[option.name].sure:
                state.menu[option.name].sure = True
                changed = True
            state.menu[option.name].score = results.scores[
                option]  # zaradit mezi moznosti

        if changed:  # we added a page
            self.processMenu(state=state, sort=False)
        else:
            pass

    def processMenu(self, state=None, sort=True):
        """ Sort menu and generate items and sout menu. """
        if state is None:
            state = self.state

        if sort:
            state.items = sorted(
                state.menu,
                reverse=True,
                key=lambda item:
                (state.menu[item].intitle, state.menu[item].score + state.menu[
                    item].bonus, -item.count(":"), item))
        else:  # when search results are being updated, it's good when the order doesnt change all the time. So that the first result does not become for a while 10th and then become first back.
            state.items = sorted(
                state.menu,
                reverse=True,
                key=lambda item:
                (state.menu[item].intitle, -state.menu[item].lastOrder))

        state.items = [
            item for item in state.items
            if (state.menu[item].score + state.menu[item].bonus) > 0
        ]  # i dont know why there are items with 0 score

        if state == self.state:
            self.soutMenu()

    def soutMenu(self, displayImmediately=False):
        """ Displays menu and handles caret position. """
        if self.timeoutOpenPage:
            GObject.source_remove(self.timeoutOpenPage)
        self.gui.resize(300, 100)  # reset size
        # treat possible caret deflection
        if self.caret['pos'] < 0 or self.caret['pos'] > len(
                self.state.items
        ) - 1:  # place the caret to the beginning or the end of list
            self.caret['pos'] = self.caret['altPos']

        text = ""
        i = 0
        for item in self.state.items:
            score = self.state.menu[item].score + self.state.menu[item].bonus
            self.state.menu[item].lastOrder = i
            if i == self.caret['pos']:
                self.caret['text'] = item  # caret is at this position
                text += '→ {} ({}) {}\n'.format(
                    item, score, "" if self.state.menu[item].sure else "?")
            else:
                try:
                    text += '{} ({}) {}\n'.format(
                        item, score, "" if self.state.menu[item].sure else "?")
                except:
                    text += "CHYBA\n"
                    text += item[0:-1] + "\n"
            i += 1

        self.labelObject.set_text(text)
        self.menuPage = Path(
            self.caret['text'] if len(self.state.items) else self.originalPage)

        if not displayImmediately:
            self.timeoutOpenPage = GObject.timeout_add(
                self.keystroke_delay, self._open_page,
                self.menuPage)  # ideal delay between keystrokes
        else:
            self._open_page(self.menuPage)

    def move(self, widget, event):
        """ Move caret up and down. Enter to confirm, Esc closes search."""
        keyname = Gdk.keyval_name(event.keyval)
        if keyname == "Up" or keyname == "ISO_Left_Tab":
            self.caret['pos'] -= 1
            self.soutMenu(displayImmediately=False)

        if keyname == "Down" or keyname == "Tab":
            self.caret['pos'] += 1
            self.soutMenu(displayImmediately=False)

        if keyname == "KP_Enter" or keyname == "Return":
            self._open_page(self.menuPage, excludeFromHistory=False)
            self.close()

        if keyname == "Escape":
            self._open_original()
            # GTK closes the windows itself on Escape, no self.close() needed

        return

    ## Safely closes
    # Xwhen closing directly, Python gave allocation error
    def close(self):
        if not self.isClosed:
            self.isClosed = True
            self.gui.emit("close")

    def _open_original(self):
        self._open_page(Path(self.originalPage))

    def _open_page(self, page, excludeFromHistory=True):
        """ Open page and highlight matches """
        self.timeoutOpenPage = None  # no delayed page will be open
        if self.isClosed == True:
            return
        if page and page.name and page.name != self.lastPage:
            self.lastPage = page.name
            # print("*** HISTORY BEF", self.window.ui.history._history[-3:])
            self.window.open_page(page)
            if excludeFromHistory:
                # there is no public API, so lets use protected _history instead
                self.window.history._history.pop()
                self.window.history._current = len(
                    self.window.history._history) - 1
        if not excludeFromHistory and self.window.history.get_current(
        ).name is not page.name:
            # we insert the page to the history because it was likely to be just visited and excluded
            self.window.history.append(page)

        # Popup find dialog with same query
        if self.queryO:  # and self.queryO.simple_match:
            string = self.state.query
            string = string.strip('*')  # support partial matches
            if self.plugin.preferences['highlight_search']:
                self.window.pageview.show_find(string, highlight=True)