"subclass dialog for save options"

import datetime
import re
from comboboxtext import ComboBoxText
from dialog import Dialog
from entry_completion import EntryCompletion
from i18n import _
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject, GLib  # pylint: disable=wrong-import-position

MAX_DPI = 2400
ENTRY_WIDTH_DATE = 10
ENTRY_WIDTH_DATETIME = 19
IMAGE_TYPES = [
    ("pdf", _("PDF"), _("Portable Document Format")),
    ("gif", _("GIF"), _("CompuServe graphics interchange format")),
    ("jpg", _("JPEG"), _("Joint Photographic Experts Group JFIF format")),
    ("png", _("PNG"), _("Portable Network Graphics")),
    ("pnm", _("PNM"), _("Portable anymap")),
    ("ps", _("PS"), _("Postscript")),
    ("tif", _("TIFF"), _("Tagged Image File Format")),
    ("txt", _("Text"), _("Plain text")),
    ("hocr", _("hOCR"), _("hOCR markup language")),
    ("session", _("Session"), _("scantpaper session file")),
    ("prependpdf", _("Prepend to PDF"), _("Prepend to an existing PDF")),
    ("appendpdf", _("Append to PDF"), _("Append to an existing PDF")),
    ("djvu", _("DjVu"), _("Deja Vu")),
]
PDF_COMPRESSION_ALGS = [
    (
        "auto",
        _("Automatic"),
        _("Let scantpaper which type of compression to use."),
    ),
    ("lzw", _("LZW"), _("Compress output with Lempel-Ziv & Welch encoding.")),
    ("g3", _("G3"), _("Compress output with CCITT Group 3 encoding.")),
    ("g4", _("G4"), _("Compress output with CCITT Group 4 encoding.")),
    ("png", _("Flate"), _("Compress output with flate encoding.")),
    ("jpg", _("JPEG"), _("Compress output with JPEG (DCT) encoding.")),
    ("none", _("None"), _("Use no compression algorithm on output.")),
]
PS_BACKENDS = [
    (
        "libtiff",
        _("LibTIFF"),
        _("Use LibTIFF (tiff2ps) to create Postscript files from TIFF."),
    ),
    (
        "pdf2ps",
        _("Ghostscript"),
        _("Use Ghostscript (pdf2ps) to create Postscript files from PDF."),
    ),
    (
        "pdftops",
        _("Poppler"),
        _("Use Poppler (pdftops) to create Postscript files from PDF."),
    ),
]
TIFF_COMPRESSION_ALGS = [
    ("lzw", _("LZW"), _("Compress output with Lempel-Ziv & Welch encoding.")),
    (
        "zip",
        _("Zip"),
        _("Compress output with deflate encoding."),
    ),  # jpeg rather than jpg needed here because tiffcp uses -c jpeg
    ("jpeg", _("JPEG"), _("Compress output with JPEG encoding.")),
    ("packbits", _("Packbits"), _("Compress output with Packbits encoding.")),
    ("g3", _("G3"), _("Compress output with CCITT Group 3 encoding.")),
    ("g4", _("G4"), _("Compress output with CCITT Group 4 encoding.")),
    ("none", _("None"), _("Use no compression algorithm on output.")),
]


class Save(Dialog):
    "subclass dialog for save options"

    _meta_datetime = None
    _meta_datetime_widget = None

    @GObject.Property(type=object)
    def meta_datetime(self):
        "Datetime object for document date"
        if self.meta_now_widget.get_active():
            return datetime.datetime.now()
        if (
            self._meta_datetime_widget is not None
            and self._meta_specify_widget.get_active()
        ):
            text = self._meta_datetime_widget.get_text()
            if text:
                try:
                    res = datetime.datetime.fromisoformat(text)
                    if self._meta_datetime is not None:
                        # If include_time is False, we only care about the date part
                        if not self.include_time:
                            old_date = (
                                self._meta_datetime.date()
                                if hasattr(self._meta_datetime, "hour")
                                else self._meta_datetime
                            )
                            if res.date() == old_date:
                                return self._meta_datetime
                        # If include_time is True, we care about the whole thing
                        elif (
                            hasattr(self._meta_datetime, "hour")
                            and res == self._meta_datetime
                        ):
                            return self._meta_datetime

                    if hasattr(self._meta_datetime, "hour"):
                        return res
                    if hasattr(self._meta_datetime, "day"):
                        return res.date()
                    return res.date() if not self.include_time else res
                except (ValueError, TypeError):
                    pass
        return self._meta_datetime

    @meta_datetime.setter
    def meta_datetime(self, newval):
        if newval != self._meta_datetime:
            self._meta_datetime = newval
            if self._meta_datetime_widget is not None:
                self._meta_datetime_widget.set_text(newval.isoformat(sep=" "))

    select_datetime = GObject.Property(
        type=bool,
        default=False,
        nick="Select datetime",
        blurb="TRUE = show datetime entry, FALSE = now/today",
    )
    _include_time = False

    @GObject.Property(type=bool, default=False)
    def include_time(self):
        "Whether to allow the time, as well as the date, to be entered"
        return self._include_time

    @include_time.setter
    def include_time(self, newval):
        if newval != self._include_time:
            self._on_toggle_include_time(newval)
            self._include_time = newval

    _meta_title = None
    _meta_title_suggestions = None
    _meta_title_widget = None

    @GObject.Property(type=str, default="")
    def meta_title(self):
        "Title metadata"
        if self._meta_title_widget is None:
            return self._meta_title
        return self._meta_title_widget.get_text()

    @meta_title.setter
    def meta_title(self, newval):
        self._meta_title = newval
        if self._meta_title_widget is not None:
            self._meta_title_widget.set_text(newval)
            self._meta_title_widget.add_to_suggestions([newval])

    @GObject.Property(type=object)
    def meta_title_suggestions(self):
        "Array of title metadata suggestions, used by entry completion widget"
        if self._meta_title_widget is None:
            return self._meta_title_suggestions
        return self._meta_title_widget.get_suggestions()

    @meta_title_suggestions.setter
    def meta_title_suggestions(self, newval):
        self._meta_title_suggestions = newval
        if self._meta_title_widget is not None:
            self._meta_title_widget.set_suggestions(newval)

    _meta_author = None
    _meta_author_suggestions = None
    _meta_author_widget = None

    @GObject.Property(type=str, default="")
    def meta_author(self):
        "Author metadata"
        if self._meta_author_widget is None:
            return self._meta_author
        return self._meta_author_widget.get_text()

    @meta_author.setter
    def meta_author(self, newval):
        self._meta_author = newval
        if self._meta_author_widget is not None:
            self._meta_author_widget.set_text(newval)
            self._meta_author_widget.add_to_suggestions([newval])

    @GObject.Property(type=object)
    def meta_author_suggestions(self):
        "Array of author metadata suggestions, used by entry completion widget"
        if self._meta_author_widget is None:
            return self._meta_author_suggestions
        return self._meta_author_widget.get_suggestions()

    @meta_author_suggestions.setter
    def meta_author_suggestions(self, newval):
        self._meta_author_suggestions = newval
        if self._meta_author_widget is not None:
            self._meta_author_widget.set_suggestions(newval)

    _meta_subject = None
    _meta_subject_suggestions = None
    _meta_subject_widget = None

    @GObject.Property(type=str, default="")
    def meta_subject(self):
        "Subject metadata"
        if self._meta_subject_widget is None:
            return self._meta_subject
        return self._meta_subject_widget.get_text()

    @meta_subject.setter
    def meta_subject(self, newval):
        self._meta_subject = newval
        if self._meta_subject_widget is not None:
            self._meta_subject_widget.set_text(newval)
            self._meta_subject_widget.add_to_suggestions([newval])

    @GObject.Property(type=object)
    def meta_subject_suggestions(self):
        "Array of subject metadata suggestions, used by entry completion widget"
        if self._meta_subject_widget is None:
            return self._meta_subject_suggestions
        return self._meta_subject_widget.get_suggestions()

    @meta_subject_suggestions.setter
    def meta_subject_suggestions(self, newval):
        self._meta_subject_suggestions = newval
        if self._meta_subject_widget is not None:
            self._meta_subject_widget.set_suggestions(newval)

    _meta_keywords = None
    _meta_keywords_suggestions = None
    _meta_keywords_widget = None

    @GObject.Property(type=str, default="")
    def meta_keywords(self):
        "Keyword metadata"
        if self._meta_keywords_widget is None:
            return self._meta_keywords
        return self._meta_keywords_widget.get_text()

    @meta_keywords.setter
    def meta_keywords(self, newval):
        self._meta_keywords = newval
        if self._meta_keywords_widget is not None:
            self._meta_keywords_widget.set_text(newval)
            self._meta_keywords_widget.add_to_suggestions([newval])

    @GObject.Property(type=object)
    def meta_keywords_suggestions(self):
        "Array of keyword metadata suggestions, used by entry completion widget"
        if self._meta_keywords_widget is None:
            return self._meta_keywords_suggestions
        return self._meta_keywords_widget.get_suggestions()

    @meta_keywords_suggestions.setter
    def meta_keywords_suggestions(self, newval):
        self._meta_keywords_suggestions = newval
        if self._meta_keywords_widget is not None:
            self._meta_keywords_widget.set_suggestions(newval)

    image_types = GObject.Property(
        type=object,
        nick="Array of available image types",
        blurb="To allow djvu, pdfunite dependencies to be optional",
    )
    image_type = GObject.Property(
        type=str,
        default="pdf",
        nick="Image type",
        blurb="Currently selected image type",
    )
    ps_backends = GObject.Property(
        type=object, nick="PS backends", blurb="Array of available postscript backends"
    )
    ps_backend = GObject.Property(
        type=str,
        default="pdftops",
        nick="PS backend",
        blurb="Currently selected postscript backend",
    )
    tiff_compression = GObject.Property(
        type=str,
        default=None,
        nick="TIFF compression",
        blurb="Currently selected TIFF compression method",
    )
    jpeg_quality = GObject.Property(
        type=float,
        minimum=1,
        maximum=100,
        default=75,
        nick="JPEG quality",
        blurb="Affects the compression level of JPEG encoding",
    )
    downsample_dpi = GObject.Property(
        type=float,
        minimum=1,
        maximum=MAX_DPI,
        default=150,
        nick="Downsample DPI",
        blurb="Resolution to use when downsampling",
    )
    downsample = GObject.Property(
        type=bool, default=False, nick="Downsample", blurb="Whether to downsample"
    )
    pdf_compression = GObject.Property(
        type=str,
        default="auto",
        nick="PDF compression",
        blurb="Currently selected PDF compression method",
    )
    can_encrypt_pdf = GObject.Property(
        type=bool,
        default=False,
        nick="Can encrypt PDF",
        blurb="Backend is capable of encrypting the PDF",
    )
    pdf_user_password = GObject.Property(
        type=str, default=None, nick="PDF user password", blurb="PDF user password"
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        box = self.get_content_area()

        # it needs its own box to be able to hide it if necessary
        self._meta_box_widget = Gtk.Box()
        box.pack_start(self._meta_box_widget, False, False, 0)

        # Frame for metadata
        frame = Gtk.Frame(label=_("Document Metadata"))
        self._meta_box_widget.pack_start(frame, True, True, 0)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.set_border_width(
            self.get_style_context().get_border(Gtk.StateFlags.NORMAL).left
        )  # ._get('content-area-border')
        frame.add(box)

        # grid to align widgets
        grid = Gtk.Grid()
        row = 0
        box.pack_start(grid, True, True, 0)

        # Date/time
        frame = Gtk.Frame(label=_("Date/Time"))
        grid.attach(frame, 0, row, 2, 1)
        row += 1
        frame.set_hexpand(True)
        vboxdt = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vboxdt.set_border_width(
            self.get_style_context().get_border(Gtk.StateFlags.NORMAL).left
        )  # ._get('content-area-border')
        frame.add(vboxdt)

        # the first radio button has to set the group,
        # which is None for the first button
        # Now button
        self.meta_now_widget = Gtk.RadioButton.new_with_label(None, _("Now"))
        self.meta_now_widget.set_tooltip_text(_("Use current date and time"))
        vboxdt.pack_start(self.meta_now_widget, True, True, 0)

        # Specify button
        self._meta_specify_widget = Gtk.RadioButton.new_with_label_from_widget(
            self.meta_now_widget, _("Specify")
        )
        self._meta_specify_widget.set_tooltip_text(_("Specify date and time"))
        vboxdt.pack_start(self._meta_specify_widget, True, True, 0)
        hboxe = Gtk.Box()
        self._meta_specify_widget.connect(
            "clicked", self._clicked_specify_date_button, hboxe
        )
        self._meta_datetime_widget = Gtk.Entry()
        self._meta_datetime_widget.set_activates_default(True)
        self._meta_datetime_widget.set_tooltip_text(_("Year-Month-Day"))
        self._meta_datetime_widget.set_alignment(1.0)  # Right justify
        self._meta_datetime_widget.connect("insert-text", self._insert_text_handler)
        self._meta_datetime_widget.connect(
            "focus-out-event", self._datetime_focus_out_callback
        )
        icon = Gtk.Image.new_from_icon_name("document-edit", Gtk.IconSize.BUTTON)
        button = Gtk.Button()
        button.set_image(icon)
        button.connect("clicked", self._clicked_edit_date_button)
        button.set_tooltip_text(_("Select date with calendar"))
        vboxdt.pack_start(hboxe, True, True, 0)
        hboxe.pack_end(button, False, False, 0)
        hboxe.pack_end(self._meta_datetime_widget, False, False, 0)

        # Don't show these widgets when the window is shown
        hboxe.set_no_show_all(True)
        self._meta_datetime_widget.show()
        button.show()
        self._meta_specify_widget.set_active(self.select_datetime)
        self._add_metadata_widgets(grid, row)
        self._on_toggle_include_time(self.include_time)

        # set this after self._meta_specify_widget.set_active() to prevent
        # meta_now_widget overwriting it
        if self.meta_datetime is not None and self.meta_datetime != "":
            self._meta_datetime_widget.set_text(self.meta_datetime.isoformat())

    def _clicked_specify_date_button(self, widget, hboxe):
        if widget.get_active():
            hboxe.show()
            self.select_datetime = True
        else:
            hboxe.hide()
            self.select_datetime = False

    def _datetime_focus_out_callback(self, entry_widget, _event):
        text = entry_widget.get_text()
        if text not in [None, ""]:
            self.meta_datetime = datetime.datetime.fromisoformat(text)
        return False

    def _clicked_edit_date_button(self, _widget):
        window_date = Dialog(
            transient_for=self,
            title=_("Select Date"),
        )
        vbox_date = window_date.get_content_area()
        window_date.set_resizable(False)
        calendar = Gtk.Calendar()

        # Update the date from the entry before showing the calendar
        # This uses the property getter which now reads from the widget
        current_date = self.meta_datetime
        if current_date:
            calendar.select_day(current_date.day)
            calendar.select_month(current_date.month - 1, current_date.year)
        calendar_s = None

        def calendar_day_selected_callback(_widget):
            year, month, day = calendar.get_date()
            self.meta_datetime = datetime.datetime(year, month + 1, day)

        calendar_s = calendar.connect("day-selected", calendar_day_selected_callback)

        def calendar_day_selected_double_click_callback(widget):
            calendar_day_selected_callback(widget)
            window_date.destroy()

        calendar.connect(
            "day-selected-double-click", calendar_day_selected_double_click_callback
        )
        vbox_date.pack_start(calendar, True, True, 0)
        today_b = Gtk.Button(label=_("Today"))

        def today_clicked_callback(_widget):
            today = datetime.date.today()

            # block and unblock signal, and update entry manually
            # to remove possibility of race conditions
            calendar.handler_block(calendar_s)
            calendar.select_day(today.day)
            calendar.select_month(today.month - 1, today.year)
            calendar.handler_unblock(calendar_s)
            self._meta_datetime_widget.set_text(today.isoformat())

        today_b.connect("clicked", today_clicked_callback)
        vbox_date.pack_start(today_b, True, True, 0)
        window_date.show_all()

    def _insert_text_handler(self, widget, string, _length, _position):
        text = widget.get_text()
        text_len = len(text)
        widget.handler_block_by_func(self._insert_text_handler)

        # trap + & - for incrementing and decrementing date
        if (
            (not self.include_time and text_len == ENTRY_WIDTH_DATE)
            or (self.include_time and text_len == ENTRY_WIDTH_DATETIME)
        ) and string in ["+", "-"]:
            day_offset = 1
            if string == "-":
                day_offset = -day_offset
            date = datetime.datetime.fromisoformat(text) + datetime.timedelta(
                days=day_offset
            )
            widget.set_text(date.isoformat(sep=" "))
        elif (
            not self.include_time
            and re.search(
                r"^[\d\-]+$", string[:ENTRY_WIDTH_DATE], re.ASCII
            )  # only allow integers and -
        ) or (
            self.include_time
            and re.search(
                r"^[\d\-: ]+$", string, re.ASCII
            )  # only allow integers and -:
        ):
            position = widget.get_position()
            widget.insert_text(string, position)
            GLib.idle_add(widget.set_position, position + len(string))

        widget.handler_unblock_by_func(self._insert_text_handler)
        widget.stop_emission_by_name("insert-text")

    def _add_metadata_widgets(self, grid, row):
        for name, label in [
            ("title", _("Title")),
            ("author", _("Author")),
            ("subject", _("Subject")),
            ("keywords", _("Keywords")),
        ]:
            hbox = Gtk.Box()
            grid.attach(hbox, 0, row, 1, 1)
            label = Gtk.Label(label=label)
            hbox.pack_start(label, False, True, 0)
            hbox = Gtk.Box()
            grid.attach(hbox, 1, row, 1, 1)
            row += 1
            setattr(
                self,
                f"_meta_{name}_widget",
                EntryCompletion(
                    getattr(self, f"meta_{name}"),
                    getattr(self, f"meta_{name}_suggestions"),
                ),
            )
            hbox.pack_start(getattr(self, f"_meta_{name}_widget"), True, True, 0)

    def _on_toggle_include_time(self, newval):
        if hasattr(self, "_meta_box_widget"):
            if newval:
                self.meta_now_widget.get_child().set_text(_("Now"))
                self.meta_now_widget.set_tooltip_text(_("Use current date and time"))
                self._meta_datetime_widget.set_max_length(ENTRY_WIDTH_DATETIME)
                self._meta_datetime_widget.set_text(
                    self._meta_datetime_widget.get_text() + " 00:00:00"
                )
            else:
                self.meta_now_widget.get_child().set_text(_("Today"))
                self.meta_now_widget.set_tooltip_text(_("Use today's date"))
                self._meta_datetime_widget.set_max_length(ENTRY_WIDTH_DATE)

    def add_image_type(self):
        "add image type dropdown"
        vbox = self.get_content_area()

        # Image type ComboBox
        hboxi = Gtk.Box()
        vbox.pack_start(hboxi, False, False, 0)
        label = Gtk.Label(label=_("Document type"))
        hboxi.pack_start(label, False, False, 0)
        combobi = ComboBoxText(data=filter_table(IMAGE_TYPES, self.image_types))
        hboxi.pack_end(combobi, False, False, 0)

        # Postscript backend
        hboxps = Gtk.Box()
        vbox.pack_start(hboxps, True, True, 0)
        label = Gtk.Label(label=_("Postscript backend"))
        hboxps.pack_start(label, False, False, 0)
        combops = ComboBoxText(data=filter_table(PS_BACKENDS, self.ps_backends))

        def ps_backend_changed_callback(_widget):
            self.ps_backend = combops.get_active_index()

        combops.connect("changed", ps_backend_changed_callback)
        combops.set_active_index(
            "pdftops" if self.ps_backend is None else self.ps_backend
        )
        hboxps.pack_end(combops, True, True, 0)

        # Compression ComboBox
        hboxc = Gtk.Box()
        vbox.pack_start(hboxc, False, False, 0)
        label = Gtk.Label(label=_("Compression"))
        hboxc.pack_start(label, False, False, 0)

        # Set up quality spinbutton here
        # so that it can be shown or hidden by callback
        hboxtq, _spinbuttontq = self.add_quality_spinbutton(vbox)

        # Fill compression ComboBox
        combobtc = ComboBoxText(data=TIFF_COMPRESSION_ALGS)

        def tiff_compression_changed_callback(_widget):
            self.tiff_compression = combobtc.get_active_index()
            if self.tiff_compression == "jpeg":
                hboxtq.show()
            else:
                hboxtq.hide()
                self.resize(1, 1)

        combobtc.connect("changed", tiff_compression_changed_callback)
        combobtc.set_active_index(self.tiff_compression)
        hboxc.pack_end(combobtc, False, False, 0)

        # PDF options
        vboxp, hboxpq = self.add_pdf_options()
        combobi.connect(
            "changed",
            self._image_type_changed_callback,
            [
                vboxp,
                hboxpq,
                hboxc,
                hboxtq,
                hboxps,
            ],
        )
        self.show_all()
        hboxc.set_no_show_all(True)
        hboxtq.set_no_show_all(True)
        hboxps.set_no_show_all(True)
        combobi.set_active_index(self.image_type)

    def _image_type_changed_callback(self, widget, data):
        (
            vboxp,
            hboxpq,
            hboxc,
            hboxtq,
            hboxps,
        ) = data
        self.image_type = widget.get_active_index()
        if re.search(r"pdf", self.image_type):
            self._pdf_selected_callback(data)
        elif self.image_type == "djvu":
            self._meta_box_widget.show()
            hboxc.hide()
            vboxp.hide()
            hboxpq.hide()
            hboxtq.hide()
            hboxps.hide()
        elif self.image_type == "tif":
            hboxc.show()
            self._meta_box_widget.hide()
            vboxp.hide()
            hboxpq.hide()
            if self.tiff_compression == "jpeg":
                hboxtq.show()
            else:
                hboxtq.hide()
            hboxps.hide()
        elif self.image_type == "ps":
            hboxc.hide()
            self._meta_box_widget.hide()
            vboxp.hide()
            hboxpq.hide()
            hboxtq.hide()
            hboxps.show()
        elif self.image_type == "jpg":
            self._meta_box_widget.hide()
            hboxc.hide()
            vboxp.hide()
            hboxpq.hide()
            hboxtq.show()
            hboxps.hide()
        else:
            self._meta_box_widget.hide()
            vboxp.hide()
            hboxc.hide()
            hboxpq.hide()
            hboxtq.hide()
            hboxps.hide()

        self.resize(1, 1)

    def _pdf_selected_callback(self, data):
        (
            vboxp,
            hboxpq,
            hboxc,
            hboxtq,
            hboxps,
        ) = data
        vboxp.show()
        hboxc.hide()
        hboxtq.hide()
        hboxps.hide()
        if self.image_type == "pdf":
            self._meta_box_widget.show()
        else:  # don't show metadata for pre-/append to pdf
            self._meta_box_widget.hide()

        if self.pdf_compression == "jpg":
            hboxpq.show()
        else:
            hboxpq.hide()

    def add_quality_spinbutton(self, vbox):
        """Set up quality spinbutton here so that it can be shown or hidden by callback"""
        hbox = Gtk.Box()
        vbox.pack_start(hbox, True, True, 0)
        label = Gtk.Label(label=_("JPEG Quality"))
        hbox.pack_start(label, False, False, 0)
        spinbutton = Gtk.SpinButton.new_with_range(1, 100, 1)
        spinbutton.set_value(self.jpeg_quality)
        hbox.pack_end(spinbutton, False, False, 0)
        return hbox, spinbutton

    def add_pdf_options(self):
        "add pdf options"
        # pack everything in one vbox to be able to show/hide them all at once
        vboxp = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox = self.get_content_area()
        vbox.pack_start(vboxp, False, False, 0)

        self._add_pdf_downsample_options(vboxp)

        # Compression ComboBox
        hbox = Gtk.Box()
        vboxp.pack_start(hbox, True, True, 0)
        label = Gtk.Label(label=_("Compression"))
        hbox.pack_start(label, False, False, 0)

        # Set up quality spinbutton here so that it can be shown or hidden by callback
        hboxq, spinbuttonq = self.add_quality_spinbutton(vboxp)
        combob = ComboBoxText(data=PDF_COMPRESSION_ALGS)
        combob.connect("changed", self._pdf_compression_changed_callback, hboxq)

        def jpg_quality_changed_callback(_widget):
            self.jpeg_quality = spinbuttonq.get_value()

        spinbuttonq.connect("value-changed", jpg_quality_changed_callback)
        hbox.pack_end(combob, False, False, 0)

        if self.can_encrypt_pdf:
            passb = Gtk.Button(label=_("Encrypt PDF"))
            vboxp.pack_start(passb, True, True, 0)
            passb.connect("clicked", self._encrypt_clicked_callback)

        vboxp.show_all()
        hboxq.set_no_show_all(True)
        vboxp.set_no_show_all(True)

        # do this after show all and set_no_show_all
        # to make sure child widgets are shown.
        combob.set_active_index(self.pdf_compression)
        return vboxp, hboxq

    def _add_pdf_downsample_options(self, vboxp):
        hbox = Gtk.Box()
        vboxp.pack_start(hbox, False, False, 0)
        button = Gtk.CheckButton(label=_("Downsample to"))
        hbox.pack_start(button, False, False, 0)
        spinbutton = Gtk.SpinButton.new_with_range(1, MAX_DPI, 1)
        spinbutton.set_value(self.downsample_dpi)
        label = Gtk.Label(label=_("PPI"))
        hbox.pack_end(label, False, False, 0)
        hbox.pack_end(spinbutton, False, False, 0)

        def downsample_toggled_callback(_widget):
            self.downsample = button.get_active()
            spinbutton.set_sensitive(self.downsample)

        button.connect("toggled", downsample_toggled_callback)

        def downsample_dpi_changed_callback(_widget):
            self.downsample_dpi = spinbutton.get_value()

        spinbutton.connect("value-changed", downsample_dpi_changed_callback)
        spinbutton.set_sensitive(self.downsample)
        button.set_active(self.downsample)

    def _pdf_compression_changed_callback(self, widget, hboxq):
        self.pdf_compression = widget.get_active_index()
        if self.pdf_compression == "jpg":
            hboxq.show()
        else:
            hboxq.hide()
            self.resize(1, 1)

    def _encrypt_clicked_callback(self, _widget):
        passwin = Dialog(
            transient_for=self,
            title=_("Set password"),
        )
        passwin.set_modal(True)
        passvbox = passwin.get_content_area()
        grid = Gtk.Grid()
        row = 0
        passvbox.pack_start(grid, True, True, 0)
        hbox = Gtk.Box()
        label = Gtk.Label(label=_("User password"))
        hbox.pack_start(label, False, False, 0)
        grid.attach(hbox, 0, row, 1, 1)
        userentry = Gtk.Entry()
        if self.pdf_user_password:
            userentry.set_text(self.pdf_user_password)

        grid.attach(userentry, 1, row, 1, 1)
        row += 1

        def clicked_ok_callback():
            self.pdf_user_password = userentry.get_text()
            passwin.destroy()

        def clicked_cancel_callback():
            passwin.destroy()

        passwin.add_actions(
            [
                ("gtk-ok", clicked_ok_callback),
                ("gtk-cancel", clicked_cancel_callback),
            ]
        )
        passwin.show_all()

    def update_config_dict(self, config):
        "update config based from instance metadata"
        for name in ["author", "title", "subject", "keywords"]:
            config[name] = getattr(self, f"meta_{name}")
            config[f"{name}-suggestions"] = getattr(self, f"meta_{name}_suggestions")

        if self.meta_datetime is not None:

            # convert from date to datetime if necessary
            args = self.meta_datetime.timetuple()[:6]
            doc_datetime = datetime.datetime(*args)

            config["datetime offset"] = doc_datetime - datetime.datetime.now()

    def update_from_import_metadata(self, metadata):
        "update instance from imported metadata"
        for name in ["author", "title", "subject", "keywords", "datetime"]:
            if name in metadata:
                setattr(self, f"meta_{name}", metadata[name])


def filter_table(table, types):
    "filter table list by types"
    sub_table = []
    for row in table:
        if row[0] in types:
            sub_table.append(row)
    return sub_table
