#!/usr/bin/python
from gi.repository import Gtk, Gdk
import skogpasswdmanapi as api
import os
import sys
import tarfile

copywrong = """
Copyright (c) 2013-2016, Oskar Skog <oskar.skog.finland@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1.  Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""

__doc__ = """
NOTE: This code is a messy hell.
Big change will occur in 0.4.x.
"""

def u(x):
    """u(x)
    
    Always decode from utf-8.
    """
    v, f, f, f, f = sys.version_info
    if v == 2:
        return x.decode("utf-8")
    else:
        return x

def gui_progressbar(percent, data):
    """gui_progressbar(percent, data)"""
    #the_GdkWindow = w.get_window()
    ##the_cairo_region = the_GdkWindow.get_clip_region()
    ##the_GdkWindow.begin_paint_region(the_cairo_region)
    #the_cairo = the_GdkWindow.cairo_create()
    w.pb_widget.set_text("{0}%".format(int(percent)))
    w.pb_widget.set_show_text(True)
    w.pb_widget.set_fraction(percent / 100.0)
    w.pb_widget.queue_draw()
    Gtk.main_iteration_do(False)
    ##the_GdkWindow.end_paint()
    #w.pb_widget.draw(the_cairo)

def create_pb():
    return api.progress_bar(1.0, 100.0, gui_progressbar)

def err(parent, msg):
    """err(parent, msg)
    Expected to be called from passwd_tab_c() or honeypot_tab_c().
    If called from main_window(), use 'self.passwd_tab' as parent.
    Show the message 'err' in a dialog.
    """
    dialog = Gtk.MessageDialog(parent.parent, 0,
                    Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
                    "ERROR")
    dialog.format_secondary_text(msg)
    dialog.run()
    dialog.destroy()

def passwd_add_d(parent):
    """passwd_add_d(parent)
    parent has attribute parent, which is the real parent window.
    Add password dialog.
    """
    dialog = Gtk.Dialog("Add existing password.", parent.parent, 0,
                        ("OK", Gtk.ResponseType.OK,
                         "Cancel", Gtk.ResponseType.CANCEL))
    main_box = dialog.get_content_area()
    
    label_n = Gtk.Label("What is the password for:")
    label_v = Gtk.Label("What is the password:")
    
    name = Gtk.Entry()
    value = Gtk.Entry()
    name.set_editable(True)
    value.set_editable(True)
    value.set_visibility(parent.parent.show_passwd)
    
    thegrid = Gtk.Grid()
    thegrid.set_property("row-spacing", 5)
    thegrid.set_property("column-spacing", 5)
    thegrid.attach(label_n, 0, 0, 1, 1)
    thegrid.attach(name, 1, 0, 1, 1)
    thegrid.attach(label_v, 0, 1, 1, 1)
    thegrid.attach(value, 1, 1, 1, 1)
    
    main_box.add(thegrid)
    dialog.show_all()
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        try:
            parent.passwords.add_nometa(u(name.get_text()),
                u(value.get_text()))
            parent.thelist.append([name.get_text(), value.get_text()])
        except api.err_duplicate:
            err(parent, "Another password has the same name.")
    dialog.destroy()

def hp_add_d(parent):
    """hp_add_d(parent)
    parent has attribute parent which is the real parent window.
    Add honeypot dialog.
    """
    dialog = Gtk.Dialog("Add honeypot.", parent.parent, 0,
                        ("OK", Gtk.ResponseType.OK,
                         "Cancel", Gtk.ResponseType.CANCEL))
    main_box = dialog.get_content_area()
    
    label_v = Gtk.Label("What is the fake-password?")
    
    value = Gtk.Entry()
    value.set_editable(True)
    
    box_v = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
    box_v.pack_start(label_v, False, False, 5)
    box_v.pack_start(value, True, True, 5)
    
    main_box.add(box_v)
    dialog.show_all()
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        try:
            parent.honeypots.add(u(value.get_text()))
            parent.thelist.append([value.get_text()])
        except api.err_duplicate:
            err(parent, "Already exists.")
    dialog.destroy()

def passwd_new_d(parent):
    """passwd_new_d(parent)
    parent has attribute parent which is the real parent window.
    Auto-generate new password dialog.
    """
    # label      entry
    # label      spinny min
    # label      spinny max
    # radio      label
    # radio      label
    dialog = Gtk.Dialog("New (auto-generated)", parent.parent, 0,
                        ("OK", Gtk.ResponseType.OK,
                         "Cancel", Gtk.ResponseType.CANCEL))
    main_box = dialog.get_content_area()
    
    b64 = Gtk.RadioButton.new_with_label_from_widget(None, "Digits, "
        "small letters, big letters, underscores and exclamation marks.")
    b10 = Gtk.RadioButton.new_with_label_from_widget(b64, "Digits only.")
    
    name = Gtk.Entry()
    name.set_editable(True)
    
    adj = Gtk.Adjustment(8, 1, 1000000, 1, 16, 0)     # For spinny_min.
    adj2 = Gtk.Adjustment(8, 1, 1000000, 1, 16, 0)    # For spinny_max.
    spinny_min = Gtk.SpinButton()
    spinny_min.set_adjustment(adj)
    spinny_max = Gtk.SpinButton()
    spinny_max.set_adjustment(adj2)
    
    label_name = Gtk.Label("What is the password for:")
    label_min = Gtk.Label("Minimal required length:")
    label_max = Gtk.Label("Maximal length allowed:")
    
    spinners = Gtk.Grid()
    spinners.set_property("row-spacing", 5)
    spinners.set_property("column-spacing", 5)
    spinners.attach(label_min, 0, 0, 1, 1)
    spinners.attach(label_max, 0, 1, 1, 1)
    spinners.attach(spinny_min, 1, 0, 1, 1)
    spinners.attach(spinny_max, 1, 1, 1, 1)
    
    name_box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
    radio_box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
    
    name_box.pack_start(label_name, False, False, 5)
    name_box.pack_start(name, True, True, 5)    # Gtk.Entry()
    radio_box.pack_start(b10, False, False, 5)  # radio button
    radio_box.pack_end(b64, False, False, 5)    # radio button
    
    top_box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
    top_box.pack_start(name_box, False, False, 5)
    top_box.pack_start(spinners, False, False, 5)
    top_box.pack_start(radio_box, False, False, 5)
    
    main_box.add(top_box)
    dialog.show_all()
    
    pb = create_pb()
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        passwd_type = b64.get_active()  # True if "64"
        passwd_min = spinny_min.get_value_as_int()
        passwd_max = spinny_max.get_value_as_int()
        passwd_name = u(name.get_text())
        if passwd_min > passwd_max or passwd_min < 1 or passwd_max < 1:
            err(parent, "Stupid min and max lengths.")
            dialog.destroy()
            return
        thelength = api.getint(passwd_min, passwd_max + 1, 
                                                pb.minibar(0.0, 10.0))
        if passwd_type:
            passwd_type = "64"
            passwd_value = api.get64(thelength, pb.minibar(10.0, 100.0))
        else:
            passwd_type = "10"
            passwd_value = api.get10(thelength, pb.minibar(10.0, 100.0))
        try:
            parent.passwords.add(passwd_name, passwd_value, passwd_type,
                                 passwd_min, passwd_max)
            parent.thelist.append([passwd_name, passwd_value])
        except api.err_duplicate:
            err(parent, "Another password has the same name.")
        
    dialog.destroy()

def passwd_updatemeta_d(parent):
    """passwd_updatemeta_d(parent)
    parent has attribute parent which is the real parent window.
    Update password with new meta-data dialog.
    """
    # Copy-pasted from passwd_new_d and hand-hacked.
    # label      entry
    # label      spinny min
    # label      spinny max
    # radio      label
    # radio      label
    
    if parent.index == None:
        err(parent, "Select first.")
        return
    
    dialog = Gtk.Dialog("Update with new settings.", parent.parent, 0,
                        ("OK", Gtk.ResponseType.OK,
                         "Cancel", Gtk.ResponseType.CANCEL))
    main_box = dialog.get_content_area()
    
    b64 = Gtk.RadioButton.new_with_label_from_widget(None, "Digits, "
        "small letters, big letters, underscores and exclamation marks.")
    b10 = Gtk.RadioButton.new_with_label_from_widget(b64, "Digits only.")
    
    a, b = parent.thelist[parent.index]
    # Remember a, b for a long time.
    the_index = parent.passwords.mkindex(u(a), True)
    
    adj = Gtk.Adjustment(int(
                        parent.passwords[the_index]["meta"]["minlength"]),
                            1, 1000000, 1, 16, 0)     # For spinny_min.
    adj2 = Gtk.Adjustment(int(
                        parent.passwords[the_index]["meta"]["maxlength"]),
                            1, 1000000, 1, 16, 0)     # For spinny_max.
    spinny_min = Gtk.SpinButton()
    spinny_min.set_adjustment(adj)
    spinny_max = Gtk.SpinButton()
    spinny_max.set_adjustment(adj2)
    
    label_min = Gtk.Label("Minimal required length:")
    label_max = Gtk.Label("Maximal length allowed:")
    
    spinners = Gtk.Grid()
    spinners.set_property("row-spacing", 5)
    spinners.set_property("column-spacing", 5)
    spinners.attach(label_min, 0, 0, 1, 1)
    spinners.attach(label_max, 0, 1, 1, 1)
    spinners.attach(spinny_min, 1, 0, 1, 1)
    spinners.attach(spinny_max, 1, 1, 1, 1)
    
    radio_box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
    radio_box.pack_start(b10, False, False, 5)
    radio_box.pack_end(b64, False, False, 5)
    
    if parent.passwords[the_index]["meta"]["type"] == "10":
        b10.set_active(True)
    elif parent.passwords[the_index]["meta"]["type"] == "64":
        b64.set_active(True)
    else:
        err(parent,
            "I don't know what characters are allowed in the password.")
    
    top_box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
    top_box.pack_start(spinners, False, False, 5)
    top_box.pack_start(radio_box, False, False, 5)
    
    main_box.add(top_box)
    dialog.show_all()
    
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        passwd_type = b64.get_active()
        passwd_min = spinny_min.get_value_as_int()
        passwd_max = spinny_max.get_value_as_int()
        if passwd_min > passwd_max or passwd_min < 1 or passwd_max < 1:
            err(parent, "Stupid min and max lengths.")
            dialog.destroy()
            return
        if passwd_type:
            passwd_type = "64"
        else:
            passwd_type = "10"
        parent.passwords.update_meta(the_index, passwd_type, passwd_min,
                                            passwd_max, create_pb())
        parent.thelist[parent.index] = a, parent.passwords[the_index]["value"]
        passwd_updated_d(parent, b, parent.passwords[the_index]["value"])
    dialog.destroy()

def passwd_updated_d(parent, old, new):
    """passwd_updated_d(parent, old, new)
    parent has attribute parent which is the real parent window.
    Show old and new passwords. This is a wrapper for passwd_updated_c.
    """
    dialog = passwd_updated_c(parent, old, new)
    dialog.run()
    dialog.destroy()

class passwd_updated_c(Gtk.Dialog):
    """class passwd_updated_c(Gtk.Dialog)
    Password has been updated dialog class.
    """
    def __init__(self, parent, old, new):
        """__init__(self, parent, old, new)
        parent has attribute parent which is the real parent window.
        There is a wrapper: passwd_updated_d(parent, old, new).
        """
        Gtk.Dialog.__init__(self, "Password has been updated.", parent.parent,
                            0, ("Close", Gtk.ResponseType.CLOSE))
        main_box = self.get_content_area()
        
        self.v_old = old
        self.v_new = new
        old_v = Gtk.Entry()
        old_v.set_text(old)
        old_v.set_editable(False) # Show the passwords in Gtk.Entry()s.
        new_v = Gtk.Entry()
        new_v.set_text(new)
        new_v.set_editable(False)
        old_b = Gtk.Button("copy")
        old_b.connect("clicked", self.copyold)
        new_b = Gtk.Button("copy")
        new_b.connect("clicked", self.copynew)
        
        table = Gtk.Grid()
        table.set_property("row-spacing", 5)
        table.set_property("column-spacing", 5)
        table.attach(Gtk.Label("Old:"), 0, 0, 1, 1)
        table.attach(Gtk.Label("New:"), 0, 1, 1, 1)
        table.attach(old_v, 1, 0, 1, 1)
        table.attach(new_v, 1, 1, 1, 1)
        table.attach(old_b, 2, 0, 1, 1)
        table.attach(new_b, 2, 1, 1, 1)
        
        main_box.add(table)
        self.show_all()
    def copyold(self, button):
        """copyold(self, button)"""
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(self.v_old, len(self.v_old))
        clipboard.store()
    def copynew(self, button):
        """copynew(self, button)"""
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(self.v_new, len(self.v_new))
        clipboard.store()
            
def pick_many_d(parent):
    """pick_many_d(parent)
    Pick many dialog, parent is an instance of honeypot_tab_c().
    It returns nothing
    """
    assert False, "Function burns in HELL."
    dialog = Gtk.Dialog("Pick many", parent.parent, 0,
                        ("OK", Gtk.ResponseType.OK,
                         "Cancel", Gtk.ResponseType.CANCEL))
    main_box = dialog.get_content_area()
    
    label_help = Gtk.Label("Randomly pick fake-passwords.")
    label_n = Gtk.Label("How many:")
    label_s = Gtk.Label("Separate them with:")
    
    spinny = Gtk.SpinButton()   # How many?
    adj = Gtk.Adjustment(1, 1, len(parent.honeypots), 1, 10, 0)
    spinny.set_adjustment(adj)
    
    sep = Gtk.Entry()           # Separator?
    sep.set_editable(True)
    sep.set_text(",")
    
    top_box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
    
    thegrid = Gtk.Grid()
    thegrid.set_property("row-spacing", 5)
    thegrid.set_property("column-spacing", 5)
    thegrid.attach(label_n, 0, 0, 1, 1)
    thegrid.attach(label_s, 0, 1, 1, 1)
    thegrid.attach(spinny, 1, 0, 1, 1)
    thegrid.attach(sep, 1, 1, 1, 1)
    top_box.pack_start(label_help, False, False, 0)
    top_box.pack_start(thegrid, False, False, 5)
    
    main_box.add(top_box)
    dialog.show_all()
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
        # Copy xor show.
        x = parent.honeypots.pick(spinny.get_value_as_int(), u(
                            sep.get_text()), pb=create_pb())
        # x are the choices.
        dialog2 = Gtk.MessageDialog(parent.parent, 0,
                    Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
                    "Copy to clipboard?")
        dialog2.format_secondary_text("Chose 'No' to show the fake-passwords"
            " in a window.")
        response2 = dialog2.run()
        dialog2.destroy()       # Copy xor show.
        if response2 == Gtk.ResponseType.YES:
            clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            clipboard.set_text(x, len(x))
            clipboard.store()
        elif response2 == Gtk.ResponseType.NO:
            dialog3 = Gtk.MessageDialog(parent.parent, 0,
                Gtk.MessageType.OTHER, Gtk.ButtonsType.CLOSE, "Picked:")
            dialog3.format_secondary_text(x)
            dialog3.run()
            dialog3.destroy()
    dialog.destroy()
    
class passwd_tab_c(Gtk.Box):
    """class passwd_tab_c(Gtk.Box)
    Used by main_window().
    """
    def __init__(self, arg=None):
        """__init__(self, arg=None)
        arg is the main window.
        """
        self.parent = arg
        Gtk.Box.__init__(self, orientation = Gtk.Orientation.HORIZONTAL)
        self.passwords = api.passwd()
        self.thelist = Gtk.ListStore(str, str)  # name, value
        tree = Gtk.TreeView(self.thelist)
        tree.set_property("enable-search", True)
        for x in self.passwords:
            self.thelist.append([x["name"], x["value"]])
        
        renderer = Gtk.CellRendererText()
        renderer.set_property("ellipsize", True)
        column1 = Gtk.TreeViewColumn("What is the password for:", renderer,
                                                                text=0)
        column1.set_property("resizable", True)
        tree.append_column(column1)
        column2 = Gtk.TreeViewColumn("The password:", renderer, text=1)
        column2.set_property("resizable", True)
        column2.set_property("visible", self.parent.show_passwd)
        tree.append_column(column2)
        
        select = tree.get_selection()
        select.set_mode(Gtk.SelectionMode.SINGLE)
        select.connect("changed", self.select_change)
        
        self.index = None
        # Create buttons.
        b_copy = Gtk.Button("Copy to clipboard")
        b_show = Gtk.Button("Show password")
        b_update = Gtk.Button("Update")
        b_updatemeta = Gtk.Button("Change meta-data & update")
        b_add = Gtk.Button("Add existing")
        b_new = Gtk.Button("Create new")
        b_remove = Gtk.Button("Remove")
        b_copy.connect("clicked", self.copy)
        b_show.connect("clicked", self.show)
        b_update.connect("clicked", self.update)
        b_updatemeta.connect("clicked", self.update_meta)
        b_add.connect("clicked", self.add)
        b_new.connect("clicked", self.new)
        b_remove.connect("clicked", self.rm)
        # Put the buttons in a box.
        box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
        box.pack_start(b_copy, False, False, 5)
        box.pack_start(b_show, False, False, 5)
        box.pack_start(b_update, False, False, 5)
        box.pack_start(b_updatemeta, False, False, 5)
        box.pack_start(b_add, False, False, 5)
        box.pack_start(b_new, False, False, 5)
        box.pack_start(b_remove, False, False, 5)
        scrolly = Gtk.ScrolledWindow(None, None)        # Scrollbars.
        scrolly.add(tree)
        self.pack_start(scrolly, True, True, 5)
        self.pack_end(box, False, False, 5)
    def select_change(self, selection):
        """select_change(self, selection)"""
        forget, x = selection.get_selected()
        if x != None:
            self.index = x      # Not an integer.
    def copy(self, button):
        """copy(self, button)"""
        if self.index == None:
            err(self, "Select first.")
            return
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        a, b = self.thelist[self.index] # name, value
        clipboard.set_text(b, len(b))
        clipboard.store()
    def show(self, button):
        """show(self, button)"""
        if self.index == None:
            err(self, "Select first.")
            return
        a, b = self.thelist[self.index] # name, value
        dialog = Gtk.MessageDialog(self.parent, 0,
            Gtk.MessageType.OTHER, Gtk.ButtonsType.CLOSE, "")
        dialog.format_secondary_text(b)
        dialog.run()
        dialog.destroy()
    def update_meta(self, button):
        """update_meta(self, button)"""
        if self.index == None:
            err(self, "Select first.")
            return
        passwd_updatemeta_d(self)
    def update(self, button):
        """update(self, button)"""
        if self.index == None:
            err(self, "Select first.")
            return
        a, b = self.thelist[self.index] # b is the old password.
        the_index = self.passwords.mkindex(u(a), True)
        try:
            self.passwords.update(the_index, create_pb())
            self.thelist[self.index] = a, self.passwords[the_index]["value"]
            passwd_updated_d(self, b, self.passwords[the_index]["value"])
        except api.err_nometa:
            self.update_meta(button)    # Just call the right one.
    def add(self, button):
        """add(self, button)"""
        passwd_add_d(self)
    def new(self, button):
        """new(self, button)"""
        passwd_new_d(self)
    def rm(self, button):
        """rm(self, button)"""
        a, b = self.thelist[self.index]
        self.passwords.remove(u(str(a)), True)
        self.thelist.remove(self.index)

class honeypot_tab_c(Gtk.Box):
    """class honeypot_tab_c(Gtk.Box)
    Used by main_window().
    """
    def __init__(self, arg=None):
        """__init__(self, arg=None)
        arg is the main window.
        """
        self.parent = arg
        # Set the parent argument for dialogs.
        Gtk.Box.__init__(self, orientation = Gtk.Orientation.HORIZONTAL)
        # A box inside a notebook.
        self.honeypots = api.honeypot() # Needed by main_window.undo().
        self.thelist = Gtk.ListStore(str)
        for x in self.honeypots:
            self.thelist.append([x])
        tree = Gtk.TreeView(self.thelist)
        tree.set_property("enable-search", True)
        renderer = Gtk.CellRendererText()
        renderer.set_property("ellipsize", True)
        column = Gtk.TreeViewColumn("Value", renderer, text=0)
        tree.append_column(column)
        
        select = tree.get_selection()
        select.set_mode(Gtk.SelectionMode.SINGLE)
        select.connect("changed", self.select_change)
        
        self.index = None
        # Create buttons.
        b_pick_c = Gtk.Button("Pick & copy")
        b_pick_s = Gtk.Button("Pick & show")
        # b_pick_b = Gtk.Button("Bad pick many")        # BURN IN HELL
        b_pick_g = Gtk.Button("Good pick many")
        b_add = Gtk.Button("Add")
        b_remove = Gtk.Button("Remove")
        b_pick_c.connect("clicked", self.pick_copy)
        b_pick_s.connect("clicked", self.pick_show)
        # b_pick_b.connect("clicked", self.pick_many)   # BURN IN HELL
        b_pick_g.connect("clicked", self.pick_many_list)
        b_add.connect("clicked", self.add_hp)
        b_remove.connect("clicked", self.rm_hp)
        # Put the buttons in a box.
        box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
        box.pack_start(b_pick_c, False, False, 5)
        box.pack_start(b_pick_s, False, False, 5)
        # box.pack_start(b_pick_b, False, False, 5)     # BURN IN HELL
        box.pack_start(b_pick_g, False, False, 5)
        box.pack_start(b_add, False, False, 5)
        box.pack_start(b_remove, False, False, 5)
        scrolly = Gtk.ScrolledWindow(None, None)        # Scrollbars.
        scrolly.add(tree)
        self.pack_start(scrolly, True, True, 5)
        self.pack_end(box, False, False, 5)
    def select_change(self, selection):
        """select_change(self, selection)
        
        selection has changed, update index.
        """
        forget, x = selection.get_selected()
        if x != None:
            self.index = x      # Not an integer.
    def pick_copy(self, button):
        """pick_copy(self, button) - Pick and copy to clipboard."""
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        x = self.honeypots.pick(pb=create_pb())
        clipboard.set_text(x, len(x))
        clipboard.store()
    def pick_show(self, button):
        """pick_show(self, button) - Pick and show in a dialog."""
        x = self.honeypots.pick(pb=create_pb())
        dialog = Gtk.MessageDialog(self.parent, 0, Gtk.MessageType.OTHER,
                    Gtk.ButtonsType.CLOSE, "Picked:")
        dialog.format_secondary_text(x)
        dialog.run()
        dialog.destroy()
    def pick_many(self, button):
        """pick_many(self, button)"""
        assert False, "Function burns in HELL."
        pick_many_d(self)
    def add_hp(self, button):
        """add_hp(self, button)"""
        hp_add_d(self)
    def rm_hp(self, button):
        """rm_hp(self, button)"""
        self.honeypots.remove(u(self.thelist[self.index][0]), True)
        self.thelist.remove(self.index)
    def pick_many_list(self, button):
        """pick_many_list(self, button)"""
        dialog = Gtk.Dialog("", self.parent, 0, ("OK", Gtk.ResponseType.OK,
                                           "Cancel", Gtk.ResponseType.CANCEL))
        main_box = dialog.get_content_area()
        adj = Gtk.Adjustment(2, 1, len(self.honeypots), 1, 5, 0)
        spinny = Gtk.SpinButton()
        spinny.set_adjustment(adj)
        label = Gtk.Label("How many?")
        top_box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
        top_box.pack_start(label, False, False, 5)
        top_box.pack_start(spinny, True, True, 5)
        
        main_box.add(top_box)
        dialog.show_all()
        
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            thelist = self.honeypots.pickl(spinny.get_value_as_int(),
                                                        pb=create_pb())
            clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            while len(thelist) > 0:
                # Begin popping up more dialogs.
                if len(thelist) > 1:
                    button_title = "Next"
                else:
                    button_title = "Done"
                dialog2 = Gtk.Dialog("nth choice", self.parent, 0,
                        (button_title, Gtk.ResponseType.OK))
                main_box2 = dialog2.get_content_area()
                help_label = Gtk.Label("Click on the button to show"
                    " the next dialog and copy the choice to clipboard.")
                choice = thelist.pop(0) # Get the choice.
                choice_entry = Gtk.Entry()
                choice_entry.set_text(choice)
                choice_entry.set_editable(False)
                top_box2 = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
                top_box2.pack_start(help_label, False, False, 5)
                top_box2.pack_start(choice_entry, True, True, 5)
                main_box2.add(top_box2)
                dialog2.show_all()
                dialog2.run()
                # Copy to clipboard
                clipboard.set_text(choice, len(choice))
                dialog2.destroy()
            clipboard.store()
        dialog.destroy()
        
class conf_tab_c(Gtk.Box):
    """class conf_tab_c(Gtk.Box)
    Used by main_window().
    """
    def __init__(self, arg=None):
        global rm_unredo_button
        """__init__(self, arg=None)"""
        self.parent = arg
        Gtk.Box.__init__(self, orientation = Gtk.Orientation.VERTICAL)
        info_label = Gtk.Label("Changes are saved immediately.")
        main_grid = Gtk.Grid()
        self.show_passwd_switch = Gtk.Switch()
        self.show_passwd_switch.connect("notify::active", self.update_conf)
        self.show_passwd_switch.set_active(self.parent.show_passwd)
        main_grid.attach(Gtk.Label("Show passwords:"), 0, 0, 1, 1)
        main_grid.attach(self.show_passwd_switch, 1, 0, 1, 1)
        if rm_unredo_button:
            rmundo = Gtk.Button("Edit history of done and redone things.")
            rmredo = Gtk.Button("Edit history of undone things.")
            rmundo.connect("clicked", self.rmundo_action)
            rmredo.connect("clicked", self.rmredo_action)
            main_grid.attach(rmundo, 0, 2, 2, 1)
            main_grid.attach(rmredo, 0, 3, 2, 1)
        self.pack_start(info_label, False, False, 5)
        self.pack_start(main_grid, False, False, 5)
    def update_conf(self, switch, gparam):
        """update_conf(self, switch, gparam)"""
        global w
        global restart
        self.parent.show_passwd = self.show_passwd_switch.get_active()
        try:
            f = open(os.path.expanduser("~/.skogpasswdman/gui.conf"), "w")
        except:
            err(self, "Cannot open '~/.skogpasswdman/gui.conf' for writing';"
                        " cannot save settings.")
            return
        f.write("""#Configuration file for skogpasswdmangui.
        #If there is any number-sign in a line, the line is a comment.
        This is a comment. #
        #It knows about 'show_passwd', 'true' and 'false'.
        #It is case sensitive.
        """)
        if self.parent.show_passwd:
            f.write("show_passwd true\n")
        else:
            f.write("show_passwd false\n")
        f.close()
        restart = True
        Gtk.main_quit()
        return          # What might happen now?
    def rmundo_action(self, button):
        os.system("xfilemanager ~/.skogpasswdman/undoable &")
    def rmredo_action(self, button):
        os.system("xfilemanager ~/.skogpasswdman/redoable &")
        
class about_tab_c(Gtk.Box):
    """class about_tab_c(Gtk.Box)"""
    def __init__(self):
        """__init__(self)
        """
        Gtk.Box.__init__(self)
        copyright_label = Gtk.Label(copywrong)
        info_label = Gtk.Label("""
skogpasswdman is a program for remembering and updating passwords. It also
remembers honey-pots (fake-passwords to catch crackers). It has infinite
undo/redo.

It can remember old human-generated passwords, create new passwords (digits or
big letters, small letters, digits, underscores and exclamation marks) with
randomized length, update passwords and obviously show and remove passwords.
It can add, remove honey-pots and randomly choose (pick) honey-pots in two
different flavors.

It stores the passwords and honey-pots in XML files, so they can be read with
a normal text-editor (normal for unix), depending on the user. The XML files
are not encrypted, so that you don't need to remember an extra password.
  If you want encryption you should encrypt your home-directory.
""")
        about_tab_scrolled = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
        about_tab_scrolled.pack_start(info_label, False, False, 5)
        about_tab_scrolled.pack_start(copyright_label, False, False, 5)
        
        scrollable_about = Gtk.Viewport()
        scrollable_about.add(about_tab_scrolled)
        
        scrolly_about = Gtk.ScrolledWindow(None, None)
        scrolly_about.add(scrollable_about)
        self.pack_start(scrolly_about, True, True, 5)

class help_tab_c(Gtk.Box):
    """class help_tab_c(Gtk.Box)"""
    def __init__(self):
        """__init__(self)"""
        Gtk.Box.__init__(self)
        help_label = Gtk.Label("""
The "change meta-data & update"-button in the "Passwords"-tab:
    1.  If the password was created with the "Add existing"-button: use the
        "change meta-data & update"-button.
    2.  If the password was created with the "Create new"-button: use the
        "update"-button.
    3.  If the minimal required length for the password or the maximal allowed
        length or the allowable characters have changed: use the
        "change meta-data & update"-button.
    
Difference between "Add existing" and "Create new":
    Click on those buttons and see what happens. They are not dangerous.

The "Undo"- and "Redo"-buttons undoes/redoes the LATEST change in the password
tab or the fake-password tab, independent of which tab you're currently in.

The fake-passwords/honey pots are intended to be used as traps to catch
crackers. The "pick"-buttons picks a random fake-password, independent of what
you have selected. You should add fake-passwords such as "password" and
"drowssap".
    """)
        scrollable_help = Gtk.Viewport()
        scrollable_help.add(help_label)
        scrolly_help = Gtk.ScrolledWindow(None, None)
        scrolly_help.add(scrollable_help)
        self.pack_start(scrolly_help, True, True, 5)

class main_window(Gtk.Window):
    """class main_window(Gtk.Window)"""
    def __init__(self):
        """__init__(self)"""
        global restart
        Gtk.Window.__init__(self, title="skogpasswdmangui")
        try:
            # To understand this: read "~/.skogpasswdman/gui.conf".
            conf = open(os.path.expanduser("~/.skogpasswdman/gui.conf"), "r")
            for l in conf:
                if "show_passwd" in l and not "#" in l:
                    if "true" in l:
                        self.show_passwd = True
                        break
                    if "false" in l:
                        self.show_passwd = False
                        break
            else:
                self.show_passwd = False
        except:
            self.show_passwd = False
        self.set_default_size(768, 512)
        self.passwd_tab = passwd_tab_c(self)
        self.honeypot_tab = honeypot_tab_c(self)
        self.conf_tab = conf_tab_c(self)
        about_tab = about_tab_c()
        help_tab = help_tab_c()
        
        tabs = Gtk.Notebook()
        tabs.append_page(self.passwd_tab, Gtk.Label("Passwords"))
        tabs.append_page(self.honeypot_tab, Gtk.Label("Fake-passwords"))
        tabs.append_page(help_tab, Gtk.Label("HELP"))
        tabs.append_page(about_tab, Gtk.Label("About"))
        tabs.append_page(self.conf_tab, Gtk.Label("Settings"))
        if restart:     # BUG: 4x tabs.next_page() doesn't work.
            restart = False
        
        button_undo = Gtk.Button("Undo")
        button_undo.connect("clicked", self.undo)
        button_redo = Gtk.Button("Redo")
        button_redo.connect("clicked", self.redo)
        button_quit = Gtk.Button("Quit")
        button_quit.connect("clicked", Gtk.main_quit)
        button_backup = Gtk.Button("Make backup")
        button_backup.connect("clicked", self.make_backup)
        button_clear = Gtk.Button("Clear clipboard")
        button_clear.connect("clicked", self.clear_clipboard)
        
        # Reinvent progress bar after THE BIG CHANGE.
        self.pb_widget = Gtk.ProgressBar()
        self.pb_widget.set_app_paintable(True)
        self.pb_widget.set_double_buffered(False)
        
        box2 = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
        box2.pack_start(button_undo, False, False, 5)
        box2.pack_start(button_redo, False, False, 5)
        box2.pack_start(button_backup, False, False, 10)
        box2.pack_end(button_quit, False, False, 5)
        box2.pack_end(button_clear, False, False, 10)
        box2.pack_end(self.pb_widget, True, True, 2)
        
        box1 = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
        box1.pack_start(tabs, True, True, 5)
        box1.pack_start(box2, False, False, 5)
        
        self.add(box1)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()
    def undo(self, button):
        """undo(self, button)"""
        try:
            api.undo(self.passwd_tab.passwords, self.honeypot_tab.honeypots)
            # Update self.passwd_tab.thelist and
            # self.honeypot_tab.thelist.
            self.passwd_tab.thelist.clear()
            self.honeypot_tab.thelist.clear()
            for x in self.passwd_tab.passwords:
                self.passwd_tab.thelist.append([x["name"], x["value"]])
            for x in self.honeypot_tab.honeypots:
                self.honeypot_tab.thelist.append([x])
        except:
            # 'self.passwd_tab' has attribute 'parent' which is 'self'.
            err(self.passwd_tab, "CANNOT UNDO!")
    def redo(self, button):
        """redo(self, button)"""
        # Copy-pasted from undo() and hand-hacked.
        try:
            api.redo(self.passwd_tab.passwords, self.honeypot_tab.honeypots)
            # Update self.passwd_tab.thelist and
            # self.honeypot_tab.thelist.
            self.passwd_tab.thelist.clear()
            self.honeypot_tab.thelist.clear()
            for x in self.passwd_tab.passwords:
                self.passwd_tab.thelist.append([x["name"], x["value"]])
            for x in self.honeypot_tab.honeypots:
                self.honeypot_tab.thelist.append([x])
        except:
            # 'self.passwd_tab' has attribute 'parent' which is 'self'.
            err(self.passwd_tab, "CANNOT REDO!")
    def make_backup(self, button):
        """make_backup(self, button)"""
        # Create a tarball of the files.
        dialog = Gtk.FileChooserDialog(
                "skogpasswdmangui - Choose name for tarball", self,
                Gtk.FileChooserAction.SAVE, ("OK", Gtk.ResponseType.OK,
                                        "Cancel", Gtk.ResponseType.CANCEL))
        filter0 = Gtk.FileFilter()
        filter0.set_name("Tarball (.tar.gz)")
        filter0.add_pattern("*.tar.gz")
        dialog.add_filter(filter0)
        filter1 = Gtk.FileFilter()
        filter1.set_name("Tarball (.tgz, .taz)")
        filter1.add_mime_type("application/x-gtar-compressed")
        dialog.add_filter(filter1)
        filter2 = Gtk.FileFilter()
        filter2.set_name("Any")
        filter2.add_pattern("*")
        dialog.add_filter(filter2)
        dialog.set_current_name("backup.tar.gz")
        
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            old_dir = os.getcwd()
            os.chdir(os.path.expanduser("~"))
            tarball_name = dialog.get_filename()
            
            # SHIT, this BUG was old.
            assert "'" not in tarball_name
            os.system("touch '{}'".format(tarball_name))
            os.system("chmod 600 '{}'".format(tarball_name))
            
            tarball = tarfile.open(tarball_name, "w:gz")
            tarball.add(".skogpasswdman")
            tarball.close()
            os.chdir(old_dir)
            dialog2 = Gtk.MessageDialog(self, 0,
                    Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
                    "Tarball saved.")
            dialog2.format_secondary_text(
                "Extract the tarball in your home-directory ('~').")
            dialog2.run()
            dialog2.destroy()
        dialog.destroy()
    def clear_clipboard(self, button):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        x = "EMPTY"
        clipboard.set_text(x, len(x))
        clipboard.store()

if __name__ == "__main__":
    restart = False
    rm_unredo_button = __XFM__
    w = main_window()
    Gtk.main()
    try:
        w.destroy()
        del w
    except:
        pass
    while restart:      # Only used for applying...
        w = main_window() # ...changes in the configuration
        Gtk.main()
        try:
            w.destroy()
            del w
        except:
            pass
