#!/usr/bin/env python

# python-gphoto2 - Python interface to libgphoto2
# http://github.com/jim-easterbrook/python-gphoto2
# Copyright (C) 2014-19  Jim Easterbrook  jim@jim-easterbrook.me.uk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function

from datetime import datetime
import logging
import sys

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt

import gphoto2 as gp

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        self.do_init = QtCore.QEvent.registerEventType()
        QtWidgets.QMainWindow.__init__(self)
        self.setWindowTitle("Camera config")
        self.setMinimumWidth(600)
        # quit shortcut
        quit_action = QtWidgets.QAction('Quit', self)
        quit_action.setShortcuts(['Ctrl+Q', 'Ctrl+W'])
        quit_action.triggered.connect(QtWidgets.qApp.closeAllWindows)
        self.addAction(quit_action)
        # main widget
        widget = QtWidgets.QWidget()
        widget.setLayout(QtWidgets.QGridLayout())
        widget.layout().setColumnStretch(0, 1)
        self.setCentralWidget(widget)
        # 'apply' button
        self.apply_button = QtWidgets.QPushButton('apply changes')
        self.apply_button.setEnabled(False)
        self.apply_button.clicked.connect(self.apply_changes)
        widget.layout().addWidget(self.apply_button, 1, 1)
        # 'cancel' button
        quit_button = QtWidgets.QPushButton('cancel')
        quit_button.clicked.connect(QtWidgets.qApp.closeAllWindows)
        widget.layout().addWidget(quit_button, 1, 2)
        # defer full initialisation (slow operation) until gui is visible
        QtWidgets.QApplication.postEvent(
            self, QtCore.QEvent(self.do_init), Qt.LowEventPriority - 1)

    def event(self, event):
        if event.type() != self.do_init:
            return QtWidgets.QMainWindow.event(self, event)
        event.accept()
        QtWidgets.QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            self.initialise()
        finally:
            QtWidgets.QApplication.restoreOverrideCursor()
        return True

    def initialise(self):
        # get camera config tree
        self.camera = gp.check_result(gp.gp_camera_new())
        gp.check_result(gp.gp_camera_init(self.camera))
        self.camera_config = gp.check_result(
            gp.gp_camera_get_config(self.camera))
        # create corresponding tree of tab widgets
        self.setWindowTitle(
            gp.check_result(gp.gp_widget_get_label(self.camera_config)))
        top_widget = SectionWidget(self.config_changed, self.camera_config)
        scroll_area = QtWidgets.QScrollArea()
        scroll_area.setWidget(top_widget)
        scroll_area.setWidgetResizable(True)
        self.centralWidget().layout().addWidget(scroll_area, 0, 0, 1, 3)

    def config_changed(self):
        self.apply_button.setEnabled(True)

    def apply_changes(self):
        gp.check_result(gp.gp_camera_set_config(
            self.camera, self.camera_config))
        QtWidgets.qApp.closeAllWindows()

class SectionWidget(QtWidgets.QWidget):
    def __init__(self, config_changed, camera_config, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.setLayout(QtWidgets.QFormLayout())
        if gp.check_result(gp.gp_widget_get_readonly(camera_config)):
            self.setDisabled(True)
        child_count = gp.check_result(gp.gp_widget_count_children(camera_config))
        if child_count < 1:
            return
        tabs = None
        for child in gp.check_result(gp.gp_widget_get_children(camera_config)):
            label = gp.check_result(gp.gp_widget_get_label(child))
            name = gp.check_result(gp.gp_widget_get_name(child))
            label = '{} ({})'.format(label, name)
            child_type = gp.check_result(gp.gp_widget_get_type(child))
            if child_type == gp.GP_WIDGET_SECTION:
                if not tabs:
                    tabs = QtWidgets.QTabWidget()
                    self.layout().insertRow(0, tabs)
                tabs.addTab(SectionWidget(config_changed, child), label)
            elif child_type == gp.GP_WIDGET_TEXT:
                self.layout().addRow(label, TextWidget(config_changed, child))
            elif child_type == gp.GP_WIDGET_RANGE:
                self.layout().addRow(label, RangeWidget(config_changed, child))
            elif child_type == gp.GP_WIDGET_TOGGLE:
                self.layout().addRow(label, ToggleWidget(config_changed, child))
            elif child_type == gp.GP_WIDGET_RADIO:
                choice_count = gp.check_result(gp.gp_widget_count_choices(
                    child))
                if choice_count > 3:
                    widget = MenuWidget(config_changed, child)
                else:
                    widget = RadioWidget(config_changed, child)
                self.layout().addRow(label, widget)
            elif child_type == gp.GP_WIDGET_MENU:
                self.layout().addRow(label, MenuWidget(config_changed, child))
            elif child_type == gp.GP_WIDGET_DATE:
                self.layout().addRow(label, DateWidget(config_changed, child))
            else:
                print('Cannot make widget type %d for %s' % (child_type, label))

class TextWidget(QtWidgets.QLineEdit):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QLineEdit.__init__(self, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        value = gp.check_result(gp.gp_widget_get_value(config))
        if value:
            if sys.version_info[0] < 3:
                value = value.decode('utf-8')
            self.setText(value)
        self.editingFinished.connect(self.new_value)

    def new_value(self):
        if sys.version_info[0] < 3:
            value = unicode(self.text()).encode('utf-8')
        else:
            value = str(self.text())
        gp.check_result(gp.gp_widget_set_value(self.config, value))
        self.config_changed()

class RangeWidget(QtWidgets.QSlider):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QSlider.__init__(self, Qt.Horizontal, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        lo, hi, self.inc = gp.check_result(gp.gp_widget_get_range(config))
        value = gp.check_result(gp.gp_widget_get_value(config))
        self.setRange(int(lo * self.inc), int(hi * self.inc))
        self.setValue(int(value * self.inc))
        self.sliderReleased.connect(self.new_value)

    def new_value(self):
        value = float(self.value()) * self.inc
        gp.check_result(gp.gp_widget_set_value(self.config, value))
        self.config_changed()

class ToggleWidget(QtWidgets.QCheckBox):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QCheckBox.__init__(self, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        value = gp.check_result(gp.gp_widget_get_value(config))
        self.setChecked(value != 0)
        self.clicked.connect(self.new_value)

    def new_value(self):
        value = self.isChecked()
        gp.check_result(gp.gp_widget_set_value(self.config, (0, 1)[value]))
        self.config_changed()

class RadioWidget(QtWidgets.QWidget):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        self.setLayout(QtWidgets.QHBoxLayout())
        value = gp.check_result(gp.gp_widget_get_value(config))
        self.buttons = []
        for choice in gp.check_result(gp.gp_widget_get_choices(config)):
            if choice:
                button = QtWidgets.QRadioButton(choice)
                self.layout().addWidget(button)
                if choice == value:
                    button.setChecked(True)
                self.buttons.append((button, choice))
                button.clicked.connect(self.new_value)

    def new_value(self):
        for button, choice in self.buttons:
            if button.isChecked():
                gp.check_result(gp.gp_widget_set_value(self.config, choice))
                self.config_changed()
                return

class MenuWidget(QtWidgets.QComboBox):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QComboBox.__init__(self, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        value = gp.check_result(gp.gp_widget_get_value(config))
        choice_count = gp.check_result(gp.gp_widget_count_choices(config))
        for n in range(choice_count):
            choice = gp.check_result(gp.gp_widget_get_choice(config, n))
            if choice:
                self.addItem(choice)
                if choice == value:
                    self.setCurrentIndex(n)
        self.currentIndexChanged.connect(self.new_value)

    def new_value(self, value):
        value = str(self.itemText(value))
        gp.check_result(gp.gp_widget_set_value(self.config, value))
        self.config_changed()

class DateWidget(QtWidgets.QDateTimeEdit):
    def __init__(self, config_changed, config, parent=None):
        QtWidgets.QDateTimeEdit.__init__(self, parent)
        self.config_changed = config_changed
        self.config = config
        if gp.check_result(gp.gp_widget_get_readonly(config)):
            self.setDisabled(True)
        assert gp.check_result(gp.gp_widget_count_children(config)) == 0
        value = gp.check_result(gp.gp_widget_get_value(config))
        if value:
            self.setDateTime(datetime.fromtimestamp(value))
        self.dateTimeChanged.connect(self.new_value)
        self.setDisplayFormat('yyyy-MM-dd hh:mm:ss')

    def new_value(self, value):
        value = value.toPyDateTime() - datetime.fromtimestamp(0)
        value = int(value.total_seconds())
        gp.check_result(gp.gp_widget_set_value(self.config, value))
        self.config_changed()

if __name__ == "__main__":
    logging.basicConfig(
        format='%(levelname)s: %(name)s: %(message)s', level=logging.WARNING)
    callback_obj = gp.check_result(gp.use_python_logging())
    app = QtWidgets.QApplication([])
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())
