# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# GNOME Activity Journal
#
# Copyright © 2009-2010 Seif Lotfy <seif@lotfy.com>
# Copyright © 2010 Siegfried Gevatter <siegfried@gevatter.com>
# Copyright © 2010 Randal Barlow <email.tehk@gmail.com>
# Copyright © 2011 Stefano Candori <stefano.candori@gmail.com>
# Copyright © 2020 The GNOME Activity Journal developers
#
# 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/>.

import gettext
import time
import datetime
import os
import sys

import gi
from gi.repository import GObject, GLib, Gtk, Gdk, GdkPixbuf, Gio

from .activity_widgets import MultiViewContainer, TimelineViewContainer, ThumbViewContainer
from .supporting_widgets import DayButton, DayLabel, Toolbar, SearchBox, ContextMenu, ThrobberPopupButton, \
ContextMenuMolteplicity, NiceBar, AboutDialog, VideoPreviewTooltip, AudioPreviewTooltip
from .histogram import HistogramWidget
from .store import Store, tdelta, STORE, CLIENT
from .config import settings, preference_settings, get_icon_path, get_data_path, PluginManager
from .Indicator import TrayIconManager
from .common import *
from .blacklist import BLACKLIST
from . import config

#TODO granularity scrool, alignment timelineview_icon
#more metadata? use website cache as thumbpreview??
class ViewContainer(Gtk.Notebook):
    __gsignals__ = {
        "new-view-added" : (GObject.SignalFlags.RUN_LAST, None,(GObject.TYPE_PYOBJECT,)),
        "view-button-clicked" : (GObject.SignalFlags.RUN_LAST, None,(GObject.TYPE_PYOBJECT,GObject.TYPE_INT)),
    }

    class ViewStruct(object):
        view = None
        button = None

        def __init__(self, view, button):
            self.view = view
            self.button = button

    def __init__(self, store):
        super(ViewContainer, self).__init__()
        self.store = store
        self.set_show_tabs(False)
        self.set_show_border(False)
        self.pages = []
        self.tool_buttons = []
#        self.button_group = []

    def set_day(self, day, page=None):
        force_update = True
        if page == None:
            page = self.get_current_page()
            force_update = False
        if hasattr(self.pages[page], "set_day"):
            self.pages[page].set_day(day, self.store, force_update)

    def set_zoom(self, zoom):
        page = self.get_current_page()
        if hasattr(self.pages[page], "set_zoom"):
            self.pages[page].set_zoom(zoom)

    def _register_new_view(self, viewstruct):
        self.append_page(viewstruct.view)
        self.pages.append(viewstruct.view)
        self.tool_buttons.append(viewstruct.button)
        n = len(self.tool_buttons)
#        if n == 1:
#            self.button_group = viewstruct.button.get_group()
#        elif n > 1:
#            viewstruct.button.set_group(self.button_group)
        viewstruct.button.set_can_focus(True)
        viewstruct.button.connect("toggled", self.view_button_toggled, len(self.pages)-1)
        viewstruct.view.show_all()
        return self.pages.index(viewstruct.view)

    def register_new_view(self, viewstruct):
        i = self._register_new_view(viewstruct)
        self.emit("new-view-added", viewstruct)
        return i

    def remove_view(self, i, destroy=False):
        """
        :param i: a page number index starting at zero
        """
        tb = self.tool_buttons[i]
        page = self.pages[i]
        del self.pages[i]
        del self.tool_buttons[i]
        self.remove_page(i)
        tb.parent.remove(tb)
        if destroy:
            page.destroy()
            tb.destroy()

    @property
    def page(self):
        return self.get_current_page()

    @property
    def name(self):
        return self.pages[self.page].name

    def view_button_toggled(self, button, i):
        if not button.get_active():return
        button.grab_focus()
        self.emit("view-button-clicked", button, i)

    def set_view_page(self, i):
        self.set_current_page(i)

    def _register_default_view(self, view):
        toolbutton = Toolbar.get_toolbutton(view.icon_path, view.dsc_text)
        self._register_new_view(self.ViewStruct(view, toolbutton))
        self.set_view_page(0)

    def set_zoom_slider(self, hscale):
        #FIXME dirty hack
        for page in self.pages:
            page.set_slider(hscale)

    def toggle_erase_mode(self):
        for page in self.pages:
            page.toggle_erase_mode()


class PortalWindow(Gtk.ApplicationWindow):
    """
    The primary application window
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.application = kwargs["application"]

        p = Gtk.CssProvider.new()
        s = Gdk.Screen.get_default()
        p.load_from_path(get_data_path("style.css"))
        Gtk.StyleContext.add_provider_for_screen(s, p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        # write to gsettings from current theme
        update_default_search_colors()

        # Important
        self.date = None
        self.paused = False
        self._request_size()
        self.store = STORE
        self.day_iter = self.store.today
        self.pages_loaded = 0
        self.current_zoom = settings["zoom-level"]
        self.thumb_view_zoom = settings["thumb-view-zoom-level"]
        self.timeline_view_zoom = settings["timeline-view-zoom-level"]
        self.in_erase_mode = False
        self.view = ViewContainer(self.store)
        self.toolbar = Toolbar()
        default_views = (MultiViewContainer(), ThumbViewContainer(), TimelineViewContainer())
        default_views[0].connect("view-ready", self._on_view_ready)
        # FixMe: change all 'nolazy' maps to normal loops
        nolazy = list(map(self.view._register_default_view, default_views))
        nolazy = list(map(self.toolbar.add_new_view_button, self.view.tool_buttons))
        ContextMenu.set_parent_window(self)
        ContextMenuMolteplicity.set_parent_window(self)
        self.histogram = HistogramWidget()
        self.histogram.set_store(self.store)
        self.backward_button, ev_backward_button = DayButton.new(0)
        self.forward_button, ev_forward_button = DayButton.new(1, sensitive=False)
        self.nicebar = NiceBar()
        self.nicebar_timeout = None
        self.spinner = None
        self.use_spinner = True

        # use a table for the spinner (otherwise the spinner is massive!)
        spinner_table = Gtk.Table(3, 3, False)
        label = Gtk.Label()
        label.set_markup(_("<span size=\"x-large\"><b>Loading Journal...</b></span>"))
        vbox = Gtk.VBox(False, 5)

        if self.use_spinner:
            self.spinner = Gtk.Spinner()
            self.spinner.start()
            vbox.pack_start(self.spinner, False, False, 0)
        else:
            pix = GdkPixbuf.Pixbuf.new_from_file(get_data_path("zeitgeist-logo.svg"))
            pix = pix.scale_simple(200, 200, GdkPixbuf.InterpType.BILINEAR)
            zlogo = Gtk.Image.new_from_pixbuf(pix)
            vbox.pack_start(zlogo, False, False, 0)

        vbox.pack_start(label, True, True, 0)
        spinner_table.attach(vbox, 1, 2, 1, 2, Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.EXPAND)

        self.hscale_range_start = 0.0
        self.hscale_range_end = 3.0
        self.hscale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL,
                                               self.hscale_range_start,
                                               self.hscale_range_end,
                                               1.0)
        self.load_zoom()
        self.hscale.set_size_request(120, -1)
        self.hscale.set_draw_value(False)

        al = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
        al.add(self.hscale)

        self.btn_zoom_in = btn_zoom_in = Gtk.Button.new_from_icon_name("zoom-in-symbolic", Gtk.IconSize.MENU)
        btn_zoom_in.connect("clicked", self._on_zoom_in_button_clicked)
        btn_zoom_in.set_relief(Gtk.ReliefStyle.NONE)

        self.btn_zoom_out = btn_zoom_out =  Gtk.Button.new_from_icon_name("zoom-out-symbolic", Gtk.IconSize.MENU)
        btn_zoom_out.connect("clicked", self._on_zoom_out_button_clicked)
        btn_zoom_out.set_relief(Gtk.ReliefStyle.NONE)

        self.throbber_popup_button = ThrobberPopupButton()

        # Search button
        self.ignore_search_toggle = False
        self.search_button = sb = Gtk.ToggleButton()
        image = Gtk.Image.new_from_icon_name("edit-find-symbolic", Gtk.IconSize.BUTTON)
        self.search_button.set_image(image)
        self.search_button.connect("toggled", self.toggle_searchbox_visibility)

        # search bar
        self.search_dialog = SearchBox
        self.search_bar = self.create_search_bar()
        self.search_bar_revealed = False
        self.search_bar.connect("notify::search-mode-enabled", self.search_bar_mode_enabled_changed)
        self.search_bar.show_all()
        self.search_dialog.connect("matching-day", self.set_matching_day)

        # Widget placement
        vbox = Gtk.VBox(); hbox = Gtk.HBox();self.scale_box = Gtk.HBox();self.histogramhbox = Gtk.HBox()
        vbox_general = Gtk.VBox();
        self.toolbar_box = Gtk.HBox()
        self.scale_menu_box = Gtk.HBox()
        hbox.pack_start(ev_backward_button, False, False, 0); hbox.pack_start(self.view, True, True, 6)
        hbox.pack_end(ev_forward_button, False, False, 0);

        self.scale_box.set_valign(Gtk.Align.CENTER)
        self.scale_box.pack_start(btn_zoom_out, False, False, 0);
        self.scale_box.pack_start(al, False, False, 0)
        self.scale_box.pack_start(btn_zoom_in, False, False, 0);

        self.toolbar_box.pack_start(self.toolbar, False, True, 0);
        self.scale_menu_box.pack_end(self.throbber_popup_button, False, False, 6);
        self.scale_menu_box.pack_end(self.search_button, False, False, 6);
        self.scale_menu_box.pack_end(self.scale_box, False, False, 6);

        self.histogramhbox.pack_end(self.histogram, True, True, 32);
        self.histogramhbox.set_sensitive(False)

        vbox.pack_start(self.search_bar, False, False, 0);
        vbox.pack_start(self.nicebar, False, False, 0);
        vbox.pack_start(hbox, True, True, 5);
        vbox.pack_end(self.histogramhbox, False, False, 0)

        self.spinner_notebook = Gtk.Notebook()
        self.spinner_notebook.set_show_tabs(False)
        self.spinner_notebook.set_show_border(False)
        self.spinner_notebook.append_page(spinner_table)
        self.spinner_notebook.append_page(vbox)
        vbox_general.pack_start(self.spinner_notebook, False, True, 0)
        self.add(vbox_general)
        vbox_general.show_all()
        self.scale_box.hide()

        self.header_bar = Gtk.HeaderBar.new()
        self.header_bar.pack_start(self.toolbar_box)
        self.header_bar.pack_end(self.scale_menu_box)
        self.header_bar.set_show_close_button(True)
        self.set_titlebar(self.header_bar)
        self.header_bar.show_all()
        self.scale_box.hide()

        self.show()
        self.nicebar.hide()
        SearchBox.hide()

        #Tray Icon
        self.tray_manager = TrayIconManager(self)
        # Settings
        self.view.set_day(self.store.today)
        self.view.set_zoom_slider(self.hscale)
        # Signal connections
        self.view.connect("new-view-added", lambda w, v: self.toolbar.add_new_view_button(v.button, len(self.view.tool_buttons)))
        self.connect("destroy", self.quit)
        self.connect("delete-event", self.on_delete)
        self.toolbar.connect("previous", self.previous)
        self.toolbar.connect("jump-to-today", lambda w: self.set_date(datetime.date.today()))
        self.toolbar.connect("jump-to-selected-day", self.jump_to_selected_day)
        self.toolbar.connect("next", self.next)
        self.hscale.connect("value-changed", self._on_zoom_changed)
        self.backward_button.connect("clicked", self.previous)
        self.forward_button.connect("clicked", self.next)
        self.forward_button.connect("jump-to-today", lambda w: self.set_date(datetime.date.today()))
        self.histogram.connect("date-changed", lambda w, date: self.set_date(date))
        self.view.connect("view-button-clicked", self.on_view_button_click)
        self.store.connect("update", self.histogram.histogram.set_store)
        SearchBox.connect("search", self._on_search)
        SearchBox.connect("clear", self._on_search_clear)

        # Window configuration
        self.set_icon_name("org.gnome.ActivityJournal")
        self.set_icon_list(
            [GdkPixbuf.Pixbuf.new_from_file(get_icon_path(f)) for f in (
                "hicolor/16x16/apps/org.gnome.ActivityJournal.png",
                "hicolor/24x24/apps/org.gnome.ActivityJournal.png",
                "hicolor/32x32/apps/org.gnome.ActivityJournal.png",
                "hicolor/48x48/apps/org.gnome.ActivityJournal.png",
                "hicolor/256x256/apps/org.gnome.ActivityJournal.png")])
        GObject.idle_add(self.setup)
        GObject.idle_add(self.load_plugins)

        # set window title as per journal logging state
        enabled = BLACKLIST.get_incognito()
        self.update_title_from_status(enabled)

        self.init_actions()

    def init_actions(self):
        action = Gio.SimpleAction.new("toggle_erase_mode", None)
        action.connect("activate", self.on_toggle_erase_mode)
        self.add_action(action)

    def search_bar_mode_enabled_changed(self, a, b):
        if not a.props.search_mode_enabled:
            self.search_button.set_active(False)

    def on_key_pressed(self, window, event, search_bar):
        search_bar_revealed = search_bar.handle_event(event)
        if search_bar_revealed:
            self.toggle_search_bar(True)
        return search_bar_revealed

    def set_searchbutton_state(self, state):
        self.ignore_search_toggle = True
        search_button.set_active(True)
        self.ignore_search_toggle = False

    def toggle_searchbox_visibility(self, w):
        if self.ignore_search_toggle:
            return

        show = self.search_button.get_active()
        self.toggle_search_bar(show)

    def toggle_search_bar(self, show=True):
        self.search_bar.set_search_mode(show)
        self.search_button.set_active(show)

        if show:
            self.search_bar.show_all()

    def create_search_bar(self):
        search_bar = Gtk.SearchBar.new()
        hbox = Gtk.HBox.new(False, 6)

        search_bar.add(hbox)
        hbox.pack_start(self.search_dialog, True, True, 0)
        search_bar.connect_entry(self.search_dialog.search)

        self.connect("key-press-event", self.on_key_pressed, search_bar)

        return search_bar

    def load_plugins(self):
        # rewrite plugin using peas gtk
        return False

    def setup(self, *args):
        self.set_title_from_date(self.day_iter.date)
        self.histogram.set_dates(self.active_dates)
        self.histogram.scroll_to_end()
        return False

    def set_visibility(self, val):
        if val: self.show()
        else: self.hide()

    def toggle_visibility(self):
        if self.get_property("visible"):
            self.hide()
            return False
        self.show()
        return True

    @property
    def active_dates(self):
        date = self.day_iter.date
        if self.view.page != 0: return [date]
        dates = []
        for i in range(self.view.pages[0].num_pages):
            dates.append(date + tdelta(-i))
        dates.sort()
        return dates

    def set_day(self, day):
        self.toolbar.sync_date(day.date)
        self.day_iter = day
        self.handle_button_sensitivity(day.date)
        self.view.set_day(day)
        self.histogram.set_dates(self.active_dates)
        self.set_title_from_date(day.date)

    def set_date(self, date):
        self.set_day(self.store[date])

    def set_matching_day(self, searchbox, date):
        self.set_date(date)

    def next(self, *args):
        day = self.day_iter.next(self.store)
        self.set_day(day)

    def set_today(self):
        self.set_date(datetime.date.today())
        return False

    def jump_to_selected_day(self, toolbar, date):
        today = datetime.date.today()
        date = datetime.date(date.year, date.month + 1, date.day)

        if date > today:
            GLib.timeout_add(100, self.set_today)
            return

        self.set_day(self.store[date])

    def previous(self, *args):
        day = self.day_iter.previous(self.store)
        self.set_day(day)

    def handle_button_sensitivity(self, date):
        today = datetime.date.today()
        if date == today:
            self.forward_button.set_sensitive(False)
            self.toolbar.nextd_button.set_sensitive(False)
            self.toolbar.home_button.set_sensitive(False)
        else:
            self.forward_button.set_leading(True)
            self.forward_button.set_sensitive(True)
            self.toolbar.nextd_button.set_sensitive(True)
            self.toolbar.home_button.set_sensitive(True)

    def on_view_button_click(self, w, button, i):
        self.view.set_view_page(i)
        self.view.set_day(self.day_iter, page=i)
        self.histogram.set_dates(self.active_dates)
        self.set_title_from_date(self.day_iter.date)
        if i == 0:
            self.scale_box.hide()
        else:
            zoom_value = self.load_zoom()
            self.view.set_zoom(zoom_value)
            self.scale_box.show()

    def load_zoom(self):
        view = self.view.name

        if not config.SEPARATE_ZOOM_PER_VIEW:
            zoom_value = self.current_zoom
        else:
            if view == "thumb-view":
                zoom_value = self.thumb_view_zoom
            elif view == "timeline-view":
                zoom_value = self.timeline_view_zoom
            elif view == "multi-view":
                zoom_value = -1
            else:
                raise Exception("Unsupported view: %s" % view)

        if zoom_value >= 0:
            self.hscale.set_value(zoom_value)

        return zoom_value

    def _switch_page(self, view):
        if self.pages_loaded == view.num_pages - 1:
            self.histogramhbox.set_sensitive(True)
            self.spinner.stop()
            self.spinner_notebook.set_current_page(1)
        else:
            self.pages_loaded += 1

        return False

    def _on_view_ready(self, view):
        GLib.idle_add (self._switch_page, view)

    def _on_search(self, box, results):
        dates = []
        for obj in results:
            dates.append(datetime.date.fromtimestamp(int(obj.event.timestamp)/1000.0))
        self.histogram.histogram.set_highlighted(dates)

    def _on_search_clear(self, *args):
        self.histogram.histogram.clear_highlighted()
        self.set_today()

    def _request_size(self):
        screen = self.get_screen().get_monitor_geometry(
            self.get_screen().get_monitor_at_point(*self.get_position()))
        min_size = (1024, 600) # minimum netbook size
        size = [
            min(max(int(screen.width * 0.80), min_size[0]), screen.width),
            min(max(int(screen.height * 0.75), min_size[1]), screen.height)
        ]

        if settings["window-width"] and settings["window-width"] <= screen.width:
            size[0] = settings['window-width']
        if settings["window-height"] and settings["window-height"] <= screen.height:
            size[1] = settings["window-height"]

        geometry = Gdk.Geometry()
        geometry.min_width = 1024
        geometry.min_height = 360

        self.set_geometry_hints(None, geometry, Gdk.WindowHints.MIN_SIZE)
        self.resize(size[0], size[1])
        self._requested_size = size

    def save_zoom(self, zoom_value):
        view = self.view.name

        if not config.SEPARATE_ZOOM_PER_VIEW:
            self.current_zoom = zoom_value
            zoom_key = "zoom-level"
        else:
            if view == "thumb-view":
                self.thumb_view_zoom = zoom_value
                zoom_key = "thumb-view-zoom-level"
            elif view == "timeline-view":
                self.timeline_view_zoom = zoom_value
                zoom_key = "timeline-view-zoom-level"
            else:
                raise Exception("Unsupported view: %s" % view)

        settings[zoom_key] = zoom_value

    def _on_zoom_changed(self, hscale):
        zoom_value = int(hscale.get_value())
        self.view.set_zoom(zoom_value)

        self.save_zoom(zoom_value)

        zoom_out = (self.hscale.get_value() > self.hscale_range_start)
        self.btn_zoom_out.set_sensitive(zoom_out)
        zoom_in = (self.hscale.get_value() < self.hscale_range_end)
        self.btn_zoom_in.set_sensitive(zoom_in)

    def _adjust_scale_value(self, delta):
        current_zoom = int(self.hscale.get_value())
        self.hscale.set_value(current_zoom + delta)

    def _on_zoom_out_button_clicked(self, button):
        if self.hscale.get_value() > self.hscale_range_start:
            self._adjust_scale_value(-1)

    def _on_zoom_in_button_clicked(self, button):
        if self.hscale.get_value() < self.hscale_range_end:
            self._adjust_scale_value(1)

    def on_toggle_erase_mode(self, *args):
        self.in_erase_mode = not self.in_erase_mode
        if self.in_erase_mode:
            if self.nicebar_timeout:
                GObject.source_remove(self.nicebar_timeout)
                self.nicebar_timeout = None
            message = _("Erase Mode is active")
            background = self.nicebar.ALERTBACKGROUND
        else:
            message = _("Erase Mode deactivated")
            background = self.nicebar.NORMALBACKGROUND
            self.nicebar_timeout = GObject.timeout_add(3000, self.nicebar.remove_message, self)

        self.nicebar.display_message(message, background=background)
        self.view.toggle_erase_mode()

    def set_title_from_date(self, date):
        self.date = date
        pages = self.view.pages[0].num_pages
        if self.view.page == 0:
            start_date = date + tdelta(-pages+1)
        else:
            start_date = date
        if date == datetime.date.today():
            end = _("Today")
            start = start_date.strftime("%A")
        elif date == datetime.date.today() + tdelta(-1):
            end = _("Yesterday")
            start = start_date.strftime("%A")
        elif date + tdelta(6) > datetime.date.today():
            end = date.strftime("%A")
            start = start_date.strftime("%A")
        else:
            start = start_date.strftime("%d %B")
            end = date.strftime("%d %B")
        if self.view.page != 0:
            title = end + " - " + _("Activity Journal")
        else:
            title = _("%(start)s to %(end)s") % ({ 'start': start, 'end': end}) + " - " + _("Activity Journal")

        if self.paused:
            title += " " +  _("(Paused)")
        self.set_title (title)

    def update_title_from_status(self, paused):
        self.paused = paused
        if not self.date:
            return

        self.set_title_from_date(self.date)

    def on_delete(self, w, event):
        x, y = self.get_size()
        settings["window-width"] = x
        settings["window-height"] = y
        if preference_settings["enable-tray-icon"]:
            self.set_visibility(False)
            return True

    def quit_and_save(self, *args):
        x, y = self.get_size()
        settings["window-width"] = x
        settings["window-height"] = y
        self.application.quit()

    def quit(self, *args):
        self.application.quit()

class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="org.gnome.ActivityJournal",
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
            **kwargs
        )
        self.window = None

        self.add_main_option(
            "debug",
            ord("d"),
            GLib.OptionFlags.NONE,
            GLib.OptionArg.NONE,
            "Print debug information",
            None,
        )

        self.add_main_option(
            "version",
            ord("v"),
            GLib.OptionFlags.NONE,
            GLib.OptionArg.NONE,
            "Print version information",
            None,
        )

    def do_startup(self):
        Gtk.Application.do_startup(self)

        action = Gio.SimpleAction.new("preferences", None)
        action.connect("activate", self.on_preferences)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        self.set_accels_for_action("app.quit", ["<Primary>q"])

    def do_activate(self):
        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = PortalWindow(application=self, title="Main Window")

        self.window.present()

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()
        # convert GVariantDict -> GVariant -> dict
        options = options.end().unpack()

        if "debug" in options:
            config.DEBUG = options["debug"]

        if "version" in options:
            print("%s %s" % (config.NAME, config.VERSION))
            sys.exit(0)

        self.activate()
        return 0

    def on_about(self, action, param):
        about_dialog = AboutDialog(transient_for=self.window, modal=True)
        about_dialog.run()
        about_dialog.destroy()

    def on_preferences(self, action, param):
        pref = Preferences(self.window)
        pref.show()

    def on_quit(self, action, param):
        self.window.quit_and_save()
        self.quit()

class Preferences(object):
    def __init__(self, window):
        self.window = window
        self.load_widgets()

    def load_widgets(self):
        builder = Gtk.Builder.new_from_file(get_data_path("ui/preferences.ui"))

        # dialog
        self.preferences_dialog = builder.get_object("preferences_dialog")

        # info bar
        self.info_bar = info_bar = builder.get_object("info_bar")
        info_bar.connect("response", self.on_info_bar_response)
        self.info_bar_label = label = Gtk.Label()
        content_area = info_bar.get_content_area()
        content_area.add(label)
        self.info_bar.hide()

        # separate zoom per view check button
        self.separate_zoom_per_view_check_button = builder.get_object("use_separate_zoom_levels_checkbutton")
        self.separate_zoom_per_view_check_button.connect("toggled", self.separate_zoom_per_view_check_button_toggled)
        preference_settings.bind("separate-zoom-per-view", self.separate_zoom_per_view_check_button, "active", Gio.SettingsBindFlags.DEFAULT)

        # enable tray icon check button
        self.enable_tray_icon_check_button = builder.get_object("enable_tray_icon_checkbutton")
        self.enable_tray_icon_check_button.connect("toggled", self.enable_tray_icon_check_button_toggled)
        preference_settings.bind("enable-tray-icon", self.enable_tray_icon_check_button, "active", Gio.SettingsBindFlags.GET)

        # delete confirm in erase mode check button
        self.delete_confirm_check_button = builder.get_object("delete_confirm_checkbutton")
        self.delete_confirm_check_button.connect("toggled", self.delete_confirm_check_button_toggled)
        preference_settings.bind("confirm-item-deletion", self.delete_confirm_check_button, "active", Gio.SettingsBindFlags.DEFAULT)

        # hide empty day labels check button
        self.hide_empty_day_labels_check_button = builder.get_object("hide_empty_day_labels_checkbutton")
        preference_settings.bind("hide-empty-day-labels", self.hide_empty_day_labels_check_button, "active", Gio.SettingsBindFlags.DEFAULT)
        self.hide_empty_day_labels_check_button.connect("toggled", self.hide_empty_day_labels_check_button_toggled)

        # search in subject uri also
        self.search_in_subject_uri_check_button = builder.get_object("search_in_subject_uri_checkbutton")
        preference_settings.bind("search-in-subject-uri", self.search_in_subject_uri_check_button, "active", Gio.SettingsBindFlags.DEFAULT)
        self.search_in_subject_uri_check_button.connect("toggled", self.search_in_subject_uri_check_button_toggled)

        # search ignore single char
        self.search_ignore_single_char_check_button = builder.get_object("search_ignore_single_char_checkbutton")
        preference_settings.bind("search-ignore-single-char", self.search_ignore_single_char_check_button, "active", Gio.SettingsBindFlags.DEFAULT)
        self.search_ignore_single_char_check_button.connect("toggled", self.search_ignore_single_char_check_button_toggled)

        # search is case sensitive
        self.search_is_case_sensitive_check_button = builder.get_object("search_is_case_sensitive_checkbutton")
        preference_settings.bind("search-is-case-sensitive", self.search_is_case_sensitive_check_button, "active", Gio.SettingsBindFlags.DEFAULT)
        self.search_is_case_sensitive_check_button.connect("toggled", self.search_is_case_sensitive_check_button_toggled)

        # search results bold text
        self.search_results_use_bold_check_button = builder.get_object("search_results_use_bold_checkbutton")
        preference_settings.bind("search-results-use-bold-style", self.search_results_use_bold_check_button, "active", Gio.SettingsBindFlags.GET)
        self.search_results_use_bold_check_button.connect("toggled", self.search_results_use_bold_check_button_toggled)

        # search results large text
        self.search_results_use_large_check_button = builder.get_object("search_results_use_large_checkbutton")
        preference_settings.bind("search-results-use-large-style", self.search_results_use_large_check_button, "active", Gio.SettingsBindFlags.GET)
        self.search_results_use_large_check_button.connect("toggled", self.search_results_use_large_check_button_toggled)

        # default search color check button
        self.search_results_use_default_color_check_button = builder.get_object("search_results_use_default_color_checkbutton")
        preference_settings.bind("search-results-use-default-color", self.search_results_use_default_color_check_button, "active", Gio.SettingsBindFlags.GET)
        self.search_results_use_default_color_check_button.connect("toggled", self.search_results_use_default_color_check_button_toggled)

        use_default_search_colors = self.search_results_use_default_color_check_button.get_active()

        # custom text search results color
        text_rgba = get_gdk_rgba_from_string(config.SEARCH_RESULTS_TEXT_COLOR)
        self.search_results_text_color_button = builder.get_object("search_results_text_color_button")
        self.search_results_text_color_button.set_sensitive(not use_default_search_colors)
        self.search_results_text_color_button.set_property("rgba", text_rgba)
        self.search_results_text_color_button.connect("color-set", self.search_results_text_color_changed)

        # custom histogram search results color
        histogram_rgba = get_gdk_rgba_from_string(config.SEARCH_RESULTS_HISTOGRAM_COLOR)
        self.search_results_histogram_color_button = builder.get_object("search_results_histogram_color_button")
        self.search_results_histogram_color_button.set_property("rgba", histogram_rgba)
        self.search_results_histogram_color_button.set_sensitive(not use_default_search_colors)
        self.search_results_histogram_color_button.connect("color-set", self.search_results_histogram_color_changed)

        # audio preview check button
        self.show_audio_preview_check_button = builder.get_object("show_audio_preview_checkbutton")
        preference_settings.bind("show-audio-preview", self.show_audio_preview_check_button, "active", Gio.SettingsBindFlags.GET)
        self.show_audio_preview_check_button.connect("toggled", self.show_audio_preview_check_button_toggled)

        # video preview check button
        self.show_video_preview_check_button = builder.get_object("show_video_preview_checkbutton")
        preference_settings.bind("show-video-preview", self.show_video_preview_check_button, "active", Gio.SettingsBindFlags.GET)
        self.show_video_preview_check_button.connect("toggled", self.show_video_preview_check_button_toggled)

        # day part spin buttons
        self.early_morning_to_spin_button = builder.get_object("early_morning_to_spin_button")
        self.morning_to_spin_button = builder.get_object("morning_to_spin_button")
        self.afternoon_to_spin_button = builder.get_object("afternoon_to_spin_button")
        self.evening_to_spin_button = builder.get_object("evening_to_spin_button")

        preference_settings.bind("day-part-early-morning-to", self.early_morning_to_spin_button, "value", Gio.SettingsBindFlags.DEFAULT)
        preference_settings.bind("day-part-morning-to", self.morning_to_spin_button, "value", Gio.SettingsBindFlags.DEFAULT)
        preference_settings.bind("day-part-afternoon-to", self.afternoon_to_spin_button, "value", Gio.SettingsBindFlags.DEFAULT)
        preference_settings.bind("day-part-evening-to", self.evening_to_spin_button, "value", Gio.SettingsBindFlags.DEFAULT)

        self.early_morning_to_spin_button.connect("value-changed", self.day_part_spin_button_value_changed)
        self.morning_to_spin_button.connect("value-changed", self.day_part_spin_button_value_changed)
        self.afternoon_to_spin_button.connect("value-changed", self.day_part_spin_button_value_changed)
        self.evening_to_spin_button.connect("value-changed", self.day_part_spin_button_value_changed)

        # multi view spin button
        self.multi_view_spin_button = builder.get_object("multi_view_spin_button")
        preference_settings.bind("multi-view-num-days", self.multi_view_spin_button, "value", Gio.SettingsBindFlags.DEFAULT)
        self.multi_view_spin_button.connect("value-changed", self.on_multi_view_spin_button_value_changed)

        # multi view grouping events
        self.multi_view_grouping_combo = builder.get_object("multi_view_grouping_combo")
        preference_settings.bind("multi-view-num-grouping-events", self.multi_view_grouping_combo, "active-id", Gio.SettingsBindFlags.DEFAULT)
        self.multi_view_grouping_combo.connect("changed", self.on_multi_view_grouping_combo_value_changed)

        # scale thumbnail to center / fill radio buttons
        self.scale_to_center_radio_button = builder.get_object("scale_to_center_radiobutton")
        self.scale_to_fill_radio_button = builder.get_object("scale_to_fill_radiobutton")
        self.scale_to_center_radio_button.connect("toggled", self.scale_to_radio_button_toggled)
        if preference_settings["scale-to-center-thumbnail"]:
            self.scale_to_center_radio_button.set_active(True)
        else:
            self.scale_to_fill_radio_button.set_active(True)

        # show thumbnails in timeline check button
        self.show_thumbnails_in_timeline = builder.get_object("show_thumbnails_in_timeline_checkbutton")
        self.show_thumbnails_in_timeline.connect("toggled", self.show_thumbnails_in_timeline_toggled)
        preference_settings.bind("show-thumbnails-in-timeline", self.show_thumbnails_in_timeline, "active", Gio.SettingsBindFlags.DEFAULT)

        # show day / night gradient in timeline header check button
        self.show_day_night_gradient_in_timeline = builder.get_object("show_day_night_in_timeline_checkbutton")
        self.show_day_night_gradient_in_timeline.connect("toggled", self.show_day_night_in_timeline_toggled)
        preference_settings.bind("show-day-night-gradient-in-timeline", self.show_day_night_gradient_in_timeline, "active", Gio.SettingsBindFlags.DEFAULT)

    def show(self):
        self.preferences_dialog.set_transient_for(self.window)
        self.preferences_dialog.present()

    def separate_zoom_per_view_check_button_toggled(self, button):
        config.SEPARATE_ZOOM_PER_VIEW = button.get_active()

    def delete_confirm_check_button_toggled(self, button):
        config.CONFIRM_ITEM_DELETION = button.get_active()

    def hide_empty_day_labels_check_button_toggled(self, button):
        config.HIDE_EMPTY_DAY_LABELS = button.get_active()
        self.show_info_bar_restart_app()

    def enable_tray_icon_check_button_toggled(self, button):
        enabled = False
        # update gsettings manually
        if TrayIconManager.check_dependencies():
            enabled = button.get_active()
        else:
            label=_("Please install an appIndicator3 gir package.")
            self.show_info_bar(label)

        preference_settings["enable-tray-icon"] = enabled
        config.ENABLE_TRAY_ICON = enabled

    def show_thumbnails_in_timeline_toggled(self, button):
        config.SHOW_THUMBNAILS_IN_TIMELINE = button.get_active()

    def show_day_night_in_timeline_toggled(self, button):
        config.SHOW_DAY_NIGHT_GRADIENT_IN_TIMELINE = button.get_active()

    def scale_to_radio_button_toggled(self, button):
        state = button.get_active()
        preference_settings["scale-to-center-thumbnail"] = state
        config.SCALE_TO_CENTER = state

    def day_part_spin_button_value_changed(self, button):
        self.show_info_bar_restart_app()

    def on_multi_view_spin_button_value_changed(self, button):
        self.show_info_bar_restart_app()

    def on_multi_view_grouping_combo_value_changed(self, combo):
        self.show_info_bar_restart_app()

    def search_in_subject_uri_check_button_toggled(self, button):
        config.SEARCH_IN_SUBJECT_URI = button.get_active()

    def search_ignore_single_char_check_button_toggled(self, button):
        config.SEARCH_IGNORE_SINGLE_CHAR = button.get_active()

    def search_is_case_sensitive_check_button_toggled(self, button):
        config.SEARCH_IS_CASE_SENSITIVE = button.get_active()

    def search_results_use_bold_check_button_toggled(self, button):
        enabled = button.get_active()
        if not self.search_results_highlight_check():
            enabled = not enabled
        preference_settings["search-results-use-bold-style"] = enabled
        config.SEARCH_RESULTS_USE_BOLD_STYLE = enabled

    def search_results_use_large_check_button_toggled(self, button):
        enabled = button.get_active()
        if not self.search_results_highlight_check():
            enabled = not enabled
        preference_settings["search-results-use-large-style"] = enabled
        config.SEARCH_RESULTS_USE_LARGE_STYLE = enabled

    def search_results_use_default_color_check_button_toggled(self, button):
        enabled = button.get_active()
        if not self.search_results_highlight_check():
            enabled = not enabled
        preference_settings["search-results-use-default-color"] = enabled
        config.SEARCH_RESULTS_USE_DEFAULT_COLOR = enabled

        self.search_results_text_color_button.set_sensitive(not enabled)
        self.search_results_histogram_color_button.set_sensitive(not enabled)

    def search_results_text_color_changed(self, color_button):
        text_color = Gdk.RGBA.to_string(color_button.get_rgba())
        preference_settings["search-results-text-color"] = text_color
        preference_settings["search-results-text-color-updated"] = True
        config.SEARCH_RESULTS_TEXT_COLOR = text_color
        config.SEARCH_RESULTS_TEXT_COLOR_UPDATED = True

    def search_results_histogram_color_changed(self, color_button):
        histogram_color = Gdk.RGBA.to_string(color_button.get_rgba())
        preference_settings["search-results-histogram-color"] = histogram_color
        preference_settings["search-results-histogram-color-updated"] = True
        config.SEARCH_RESULTS_HISTOGRAM_COLOR = histogram_color
        config.SEARCH_RESULTS_HISTOGRAM_COLOR_UPDATED = True

    def show_audio_preview_check_button_toggled(self, button):
        enabled = False
        # update gsettings manually
        if AudioPreviewTooltip and AudioPreviewTooltip.check_dependencies():
            self.show_info_bar_restart_app()
            enabled = button.get_active()
        else:
            label=_("Please install gstreamer1.0 gir package.")
            self.show_info_bar(label)

        preference_settings["show-audio-preview"] = enabled

    def show_video_preview_check_button_toggled(self, button):
        enabled = False
        # update gsettings manually
        if VideoPreviewTooltip and VideoPreviewTooltip.check_dependencies():
            self.show_info_bar_restart_app()
            enabled = button.get_active()
        else:
            label=_("Please install gstreamer1.0-gtk3 package.")
            self.show_info_bar(label)

        preference_settings["show-video-preview"] = enabled

    def show_info_bar(self, label):
        self.info_bar_label.set_text(label)
        self.info_bar.set_revealed(True)
        self.info_bar.show_all()

    def show_info_bar_restart_app(self):
        label=_("Restart application for changes to take effect.")
        self.show_info_bar(label)

    def on_info_bar_response(self, info_bar, response):
        self.info_bar.hide()

    def search_results_highlight_check(self):
        bold = self.search_results_use_bold_check_button.get_active()
        large = self.search_results_use_large_check_button.get_active()
        default_color = self.search_results_use_default_color_check_button.get_active()

        if not (bold or large):
            if default_color or not config.SEARCH_RESULTS_TEXT_COLOR_UPDATED:
                label = _("Custom text color or bold or large style should be enabled ")
                self.show_info_bar(label)
                return False
        return True
