Commit 4a20589d authored by Cédric Bellegarde's avatar Cédric Bellegarde

Add to search engines. Fix #44

parent 48d2b42e
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkDialog" id="dialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Search engines</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkListBox" id="list_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="row-selected" handler="_on_row_selected" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_on_add_button_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-add-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_on_remove_button_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-remove-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="linked"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="edit_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Name:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Address:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">30</property>
<signal name="changed" handler="_on_name_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="uri_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">30</property>
<signal name="changed" handler="_on_uri_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Default:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="default_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">start</property>
<signal name="state-set" handler="_on_default_switch_state_set" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
<property name="width">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Bang:</property>
<property name="justify">right</property>
<property name="width_chars">2</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="bang_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">If bang is g, use g: in search bar to search using this engine</property>
<property name="max_length">1</property>
<property name="width_chars">2</property>
<property name="max_width_chars">1</property>
<signal name="changed" handler="_on_bang_entry_changed" swapped="no"/>
<signal name="key-press-event" handler="_on_bang_key_press_event" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Search:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">30</property>
<signal name="changed" handler="_on_search_entry_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="width">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="icon_name">dialog-information-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="label" translatable="yes">To determine the search address, perform a search using the search engine that you want to add and check the resulting address. Remove the search term from the resulting address and replace it with %s.</property>
<property name="wrap">True</property>
<property name="max_width_chars">40</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<placeholder/>
</child>
</object>
</interface>
......@@ -125,30 +125,13 @@
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">15</property>
<property name="label" translatable="yes">Web engine:</property>
<property name="label" translatable="yes">Web engines:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="combo_engine">
<property name="visible">True</property>
<property name="can_focus">False</property>
<items>
<item id="Google" translatable="yes">Google</item>
<item id="DuckDuckGo" translatable="yes">DuckDuckGo</item>
<item id="Yahoo" translatable="yes">Yahoo</item>
<item id="Bing" translatable="yes">Bing</item>
</items>
<signal name="changed" handler="_on_engine_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
......@@ -259,6 +242,19 @@
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Configure engines</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_on_configure_engines_clicked" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<placeholder/>
</child>
......
......@@ -58,6 +58,12 @@
background-color: #C71585;
}
.invalid-entry {
color: @theme_fg_color;
background-color: alpha(red, 0.1);
background-image: none;
}
.input {
color: @theme_fg_color;
background-color: alpha(@theme_selected_bg_color, 0.1);
......
......@@ -14,6 +14,7 @@
<file compressed="true" preprocess="xml-stripblanks">Appmenu.ui</file>
<file compressed="true" preprocess="xml-stripblanks">BookmarkEdit.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ClearData.ui</file>
<file compressed="true" preprocess="xml-stripblanks">DialogSearchEngine.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ImportBookmarks.ui</file>
<file compressed="true" preprocess="xml-stripblanks">Languages.ui</file>
<file compressed="true" preprocess="xml-stripblanks">PopoverCookies.ui</file>
......
......@@ -13,6 +13,7 @@ app_PYTHON = \
database_settings.py\
dialog_clear_data.py\
dialog_import_bookmarks.py\
dialog_search_engine.py\
download_manager.py\
define.py\
extension_adblock.py\
......
# Copyright (c) 2017 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import Gtk, GLib, GObject, Gio
from gettext import gettext as _
from urllib.parse import urlparse
import json
from eolie.search import Search
from eolie.define import El, EOLIE_LOCAL_PATH
class Item(GObject.GObject):
name = GObject.Property(type=str,
default="")
uri = GObject.Property(type=str,
default="")
search = GObject.Property(type=str,
default="")
bang = GObject.Property(type=str,
default="")
editable = GObject.Property(type=bool,
default=False)
def __init__(self):
GObject.GObject.__init__(self)
class Row(Gtk.ListBoxRow):
"""
A cookie row
"""
def __init__(self, item):
"""
Init row
@param item as Item
"""
Gtk.ListBoxRow.__init__(self)
self.__item = item
self.__label = Gtk.Label.new(item.get_property("name"))
self.__label.set_max_width_chars(20)
self.__label.set_property("halign", Gtk.Align.START)
self.__label.show()
self.add(self.__label)
def set_name(self, name):
"""
Set name
@param name as str
"""
self.__label.set_text(name)
@property
def is_valid(self):
"""
True if item is valid
@return bool
"""
uri = self.__item.get_property("uri")
search = self.__item.get_property("search")
parsed = urlparse(uri)
if parsed.scheme not in ["http", "https"] or not parsed.netloc:
return False
parsed = urlparse(search)
if parsed.scheme not in ["http", "https"] or search.find("%s") == -1:
return False
return True
@property
def item(self):
"""
Get associated item
@return item
"""
return self.__item
class SearchEngineDialog:
"""
A search engine dialog
THANKS TO EPIPHANY DEVS FOR UI FILE!
"""
def __init__(self, parent):
"""
Init widget
@param parent as Gtk.Window
"""
builder = Gtk.Builder()
builder.add_from_resource("/org/gnome/Eolie/DialogSearchEngine.ui")
self.__dialog = builder.get_object("dialog")
self.__dialog.set_transient_for(parent)
self.__edit_box = builder.get_object("edit_box")
self.__listbox = builder.get_object("list_box")
self.__name_entry = builder.get_object("name_entry")
self.__uri_entry = builder.get_object("uri_entry")
self.__search_entry = builder.get_object("search_entry")
self.__bang_entry = builder.get_object("bang_entry")
self.__default_switch = builder.get_object("default_switch")
self.__add_button = builder.get_object("add_button")
self.__remove_button = builder.get_object("remove_button")
builder.connect_signals(self)
def run(self):
"""
Run dialog
"""
self.__populate()
self.__dialog.run()
# Save engines
engines = {}
for child in self.__listbox.get_children():
if child.item.get_property("editable") and child.is_valid:
name = child.item.get_property("name")
uri = child.item.get_property("uri")
search = child.item.get_property("search")
bang = child.item.get_property("bang")
if name and search:
engines[name] = [uri, search, "", "", bang]
content = json.dumps(engines)
f = Gio.File.new_for_path(EOLIE_LOCAL_PATH + "/engines.json")
f.replace_contents(content.encode("utf-8"),
None,
False,
Gio.FileCreateFlags.REPLACE_DESTINATION,
None)
self.__dialog.destroy()
# Update application search engines
El().search = Search()
#######################
# PROTECTED #
#######################
def _on_default_switch_state_set(self, switch, state):
"""
Update engine state
@param switch as Gtk.Switch
@param state as bool
"""
if state:
row = self.__listbox.get_selected_row()
if row is not None:
name = row.item.get_property("name")
El().settings.set_value("search-engine",
GLib.Variant("s", name))
else:
row = self.__listbox.get_children()[0]
name = row.item.get_property("name")
El().settings.set_value("search-engine",
GLib.Variant("s", name))
def _on_add_button_clicked(self, button):
"""
Add a new engine
@param button as Gtk.Button
"""
# Only one New engine
for child in self.__listbox.get_children():
if child.item.name == _("New engine"):
return
item = Item()
item.set_property("name", _("New engine"))
item.set_property("uri", "")
item.set_property("search", "")
item.set_property("bang", "")
item.set_property("editable", True)
child = Row(item)
child.show()
self.__listbox.add(child)
self.__listbox.select_row(child)
def _on_remove_button_clicked(self, button):
"""
Remove engine
@param button as Gtk.Button
"""
row = self.__listbox.get_selected_row()
if row is not None:
row.destroy()
def _on_row_selected(self, listbox, row):
"""
Update entries
@param listbox as Gtk.ListBox
@param row as Row
"""
if row is None:
listbox.select_row(listbox.get_children()[0])
return
self.__name_entry.set_text(row.item.get_property("name"))
self.__uri_entry.set_text(row.item.get_property("uri"))
self.__search_entry.set_text(row.item.get_property("search"))
self.__bang_entry.set_text(row.item.get_property("bang"))
self.__name_entry.set_sensitive(row.item.get_property("editable"))
self.__uri_entry.set_sensitive(row.item.get_property("editable"))
self.__search_entry.set_sensitive(row.item.get_property("editable"))
self.__bang_entry.set_sensitive(row.item.get_property("editable"))
self.__remove_button.set_sensitive(row.item.get_property("editable"))
default_search_engine = El().settings.get_value(
"search-engine").get_string()
self.__default_switch.set_active(default_search_engine ==
row.item.get_property("name"))
def _on_name_entry_changed(self, entry):
"""
Update search engine name
@param entry as Gtk.Entry
"""
new_name = entry.get_text()
row = self.__listbox.get_selected_row()
if row is None or not new_name:
return
# Update search engine if needed
name = row.item.get_property("name")
if name == El().settings.get_value("search-engine").get_string():
El().settings.set_value("search-engine",
GLib.Variant("s", new_name))
row.item.set_property("name", new_name)
row.set_name(new_name)
def _on_uri_entry_changed(self, entry):
"""
Update search engine uri
@param entry as Gtk.Entry
"""
uri = entry.get_text()
row = self.__listbox.get_selected_row()
if row is None or not uri:
return
row.item.set_property("uri", uri)
parsed = urlparse(uri)
if parsed.scheme not in ["http", "https"] or not parsed.netloc:
entry.get_style_context().add_class("invalid-entry")
else:
entry.get_style_context().remove_class("invalid-entry")
def _on_search_entry_changed(self, entry):
"""
Update search engine search uri
@param entry as Gtk.Entry
"""
search = entry.get_text()
row = self.__listbox.get_selected_row()
if row is None or not search:
return
row.item.set_property("search", search)
parsed = urlparse(search)
if parsed.scheme not in ["http", "https"] or search.find("%s") == -1:
entry.get_style_context().add_class("invalid-entry")
else:
entry.get_style_context().remove_class("invalid-entry")
def _on_bang_entry_changed(self, entry):
"""
Update search engine bang
@param entry as Gtk.Entry
"""
row = self.__listbox.get_selected_row()
if row is None or not entry.get_text():
return
row.item.set_property("bang", entry.get_text())
def _on_bang_key_press_event(self, entry, event):
"""
Validate bang value
@param entry as Gtk.Entry
@param event as Gdk.EventKey
"""
row = self.__listbox.get_selected_row()
if row is None:
return