#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#   GIMP - The GNU Image Manipulation Program
#   Copyright (C) 1995 Spencer Kimball and Peter Mattis
#
#   Parametric RGB Channel Mask for GIMP3 Japanese version v. 0.1
#   Copyright (C) 2025 Ohnishi, Yasuo
#
#   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 <https://www.gnu.org/licenses/>.

import sys, ast, math, os

import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
gi.require_version('GimpUi', '3.0')
from gi.repository import GimpUi

from gi.repository import Gtk, GLib, Gegl, GObject, Gdk, cairo, Gio
from gimphelpers import pdb_run

def srgbToLinear(v):
    return v/12.92 if v <= 0.04045 else math.pow((v+0.055)/1.055,2.4)

def linearToSrgb(v):
    return v*12.92 if v < 0.0031308 else 1.055*math.pow(v,1/2.4)-0.055



class Param_Ch_mask(Gimp.PlugIn):
    def do_query_procedures(self):
        return [ "py-plug-in-parametric-channel-mask-g3" ]

    def do_set_i18n (self, name):
        return False

    def do_create_procedure(self, name):
        procedure = Gimp.ImageProcedure.new(self, name,
                                            Gimp.PDBProcType.PLUGIN,
                                            self.run, None)

        procedure.set_image_types("*")
        procedure.set_sensitivity_mask(Gimp.ProcedureSensitivityMask.ALWAYS)
        procedure.set_menu_label("RGBチャンネルマスクを付加...")
        procedure.add_menu_path('<Image>/Layer/Mask/パラメータ指定マスク')

        procedure.set_documentation("パラメータ指定 RGB チャンネルマスクを作成",
                                    "Make parametric channel mask",
                                    name)
        procedure.set_attribution("Ohnishi, Yasuo", "Ohnishi, Yasuo", "2025")

        return procedure

    def run(self, procedure, run_mode, image, drawables, config, run_data):
        GimpUi.init("Python-UI_sample4.py")
#--------------------------------------

        self.gray = drawables[0].is_gray()
        print("[spots mask] Gray?: " + str(self.gray))
        if self.gray == True:
            self.display_on_auto_cancel("グレースケール画像が指定されています")
            return procedure.new_return_values \
                    (Gimp.PDBStatusType.CANCEL, GLib.Error())
        self.selected_layers = image.get_selected_layers()
        try:
            filepath = image.get_file().get_path()
        except:
            home_directory = (
                os.environ.get('HOME') or
                os.environ.get('HOMEPATH') or
                os.environ.get('USERPROFILE')
                )
            if os.name == 'nt':
                separator = '\\'
            else:
                separator = '/'
            filepath = home_directory + separator
        print("[spots mask] File path: " + filepath)
        basename, ext = os.path.splitext(filepath)
        #--- make preview image ---
        image2 = image.duplicate()
        orgWidth = image2.get_width()
        orgHeight = image2.get_height()
        scaleFactor = 1280 / orgWidth
        newHeight = int(orgHeight * scaleFactor)
        image2.scale(1280, newHeight)
        n_layers = len(image2.get_layers())
        if n_layers > 1:
            image2.flatten()
        self.layers = image2.get_layers()
            # decompose preview image
        result = pdb_run('plug-in-decompose', {
            'run-mode': Gimp.RunMode.NONINTERACTIVE,
            'image': image2,
            'drawables': self.layers,
            'decompose-type': 'rgb',
            'layers-mode': True
            })
        print("[spots mask] Decompose successed?: " \
                                + str(result.index(0)))
        imagePrv = result.index(1)
        self.layers = imagePrv.get_layers()
        self.layers[0].set_name("Red (Prv)")
        self.layers[1].set_name("Green (Prv)")
        self.layers[2].set_name("Blue (Prv)")
        new_Prvlayer = self.layers[0].copy() # copy red
        imagePrv.insert_layer(new_Prvlayer, None, 3)
        disp2 = Gimp.Display.new(imagePrv)
        self.layers[0].set_visible(False)
        self.layers[1].set_visible(False)
        self.layers[2].set_visible(False)
        new_Prvlayer.set_visible(True)


#-------get parameters from dialog
        dialog = GetDialog(None, self, imagePrv)
        dialog.set_position(Gtk.WindowPosition.CENTER)  # Center the dialog
        dialog.set_default_size(400, 300)
        response = dialog.run()
        if response == Gtk.ResponseType.CANCEL: # process is canceled
            self.display_on_cancel()

            dialog.destroy()  # Fully remove the channel selection dialog
            disp2.delete()
            return procedure.new_return_values \
                    (Gimp.PDBStatusType.CANCEL, GLib.Error())
        self.pointsX = dialog.pointsX # get parameters from dialog
        self.pointsY = dialog.pointsY
        self.save_mask = dialog.save_mask
        self.chName = dialog.chName
        dialog.destroy()
        #--- make mask image ---
        # decompose  original image
        image3 = image.duplicate()
        n_layers = len(image3.get_layers())
        if n_layers > 1:
            image3.flatten()
        drawables3 = image3.get_selected_drawables()
        result3 = pdb_run('plug-in-decompose', {
            'run-mode': Gimp.RunMode.NONINTERACTIVE,
            'image': image3,
            'drawables': drawables3,
            'decompose-type': 'rgb',
            'layers-mode': True
            })
        print("[spots mask] Decompose successed?: " \
                                    + str(result.index(0)))
        imageMask = result3.index(1)
        disp1 = Gimp.Display.new(imageMask)
        mask_layers = imageMask.get_layers()
        mask_layers[0].set_name("Red")
        mask_layers[1].set_name("Green")
        mask_layers[2].set_name("Blue")
        # copy specialised ch.
        if self.chName == "Red":
            new_layer = mask_layers[0].copy()
        elif self.chName == "Green":
            new_layer = mask_layers[1].copy()
        elif self.chName == "Blue":
            new_layer = mask_layers[2].copy()
        # insert copyed layer as new layer
        imageMask.insert_layer(new_layer, None, 3)
        mask_layers[0].set_visible(False)
        mask_layers[1].set_visible(False)
        mask_layers[2].set_visible(False)
        new_layer.set_visible(True)
        curve=[p for xy in zip(self.pointsX,self.pointsY) for p in xy]
        channel = Gimp.HistogramChannel.VALUE
        new_layer.curves_spline(channel, curve)
        Gimp.edit_copy([new_layer])
        # Start a grouped undo step
        image.undo_group_start()

        # add the editing layer to original image
        editing_layer = \
            Gimp.Layer.new_from_visible(image, image, self.chName + " mask")
        #editing_layer.set_name(self.chName + " mask")
        image.insert_layer(editing_layer, None, 0)

        # add mask to editing layer
        if editing_layer.has_alpha() == False:
            editing_layer.add_alpha()
        if editing_layer.get_mask() == None:
            mask = editing_layer.create_mask(Gimp.AddMaskType.WHITE)
            editing_layer.add_mask(mask)
        else:
            mask = editing_layer.get_mask()
        editing_layer.set_edit_mask(True)
        # paste the image in clipboard to mask
        Gimp.edit_paste(mask, False)
        Gimp.floating_sel_anchor(image.get_floating_sel())
                        #get floating_sel as layer!
        editing_layer.set_show_mask(True)
        image.set_selected_layers([editing_layer])

        # End a grouped undo step
        image.undo_group_end()
        if self.save_mask == True:
            # save mask image as tiff
            mask_filepath = basename + "_" + self.chName + "_mask.tif"
            imageMask.merge_visible_layers(Gimp.MergeType.CLIP_TO_IMAGE)
            imageMask.flatten()
            # Once merge with "CLIP_TO_IMAGE", after that flatten the image
            # to avoid saving error
            Gimp.file_save(run_mode=Gimp.RunMode.NONINTERACTIVE, image=imageMask,
                    file=Gio.File.new_for_path(mask_filepath), options=None)
        # delete mask making image
        disp2.delete()
        disp1.delete()

        # do what you want to do, then, in case of success, return:
        return procedure.new_return_values \
                (Gimp.PDBStatusType.SUCCESS, GLib.Error())

    def display_on_cancel(self):
        # Show a warning dialog instead of Gimp.message()
        warning_dialog = Gtk.MessageDialog(
            transient_for=None,
            flags=Gtk.DialogFlags.MODAL,
            message_type=Gtk.MessageType.WARNING,  # ⚠️ Yellow warning triangle
            buttons=Gtk.ButtonsType.OK,
            text="Process canceled"
        )
        warning_dialog.set_position(Gtk.WindowPosition.CENTER)
            # Center the dialog
        warning_dialog.format_secondary_text("ユーザによって" + \
                    "動作が取り消されました")
        warning_dialog.run()
        warning_dialog.destroy()

    def display_on_auto_cancel(self, message):
        # Show a warning dialog instead of Gimp.message()
        warning_dialog = Gtk.MessageDialog(
            transient_for=None,
            flags=Gtk.DialogFlags.MODAL,
            message_type=Gtk.MessageType.WARNING,  # ⚠️ Yellow warning triangle
            buttons=Gtk.ButtonsType.OK,
            text="Process canceled"
        )
        warning_dialog.set_position(Gtk.WindowPosition.CENTER)
            # Center the dialog
        warning_dialog.format_secondary_text(message)
        warning_dialog.run()
        warning_dialog.destroy()



class GetDialog(Gtk.Dialog):
    def __init__(self, parent, plugin, image):
        Gtk.Dialog.__init__(self, title="パラメータ指定 RGB チャンネルマスク", \
                            parent=parent, flags=Gtk.DialogFlags.MODAL)
        self.plugin = plugin
        self.update_timer_id = None
        self.auto = False
        self.save_mask = False
        self.image = image
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_default_size(400, 200)

        box = self.get_content_area()
        box.set_spacing(10)
        box.set_margin_top(20)
        box.set_margin_bottom(20)
        box.set_margin_start(20)
        box.set_margin_end(20)

        #---add combo box for channel selection---
        label_combo_box = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(label_combo_box)

        label = Gtk.Label(label="マスク作成チャンネルを選択:")
        label.set_xalign(0)
        label_combo_box.pack_start(label, False, False, 0)

        channels = [
            "Red",
            "Green",
            "Blue"
        ]
        self.chName = channels[0]
        self.channel_combo = Gtk.ComboBoxText()
        self.channel_combo.set_entry_text_column(0)
        self.channel_combo.connect("changed", self.on_channel_combo_changed)
        for channel in channels:
            self.channel_combo.append_text(channel)
        self.channel_combo.set_active(0)
        self.channel_combo.set_tooltip_text("マスクを作成したいチャンネルを選びます")
        label_combo_box.pack_start(self.channel_combo, False, False, 0)

        #--- add lower scale label
        self.label = Gtk.Label(label="下位スロープ")
        self.label.set_xalign(0)
        box.pack_start(self.label, False, False, 0)

        #---add scale1 (slider) for 1st WP---
        box_4_scale1 = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(box_4_scale1)
        label = Gtk.Label(label="   ホワイトポイント")
        label.set_xalign(0)
        box_4_scale1.pack_start(label, False, False, 0)
        self.scale1 = Gtk.Scale.new_with_range\
            (Gtk.Orientation.HORIZONTAL, 0.0, 100.0, 0.01)
        # 🔹 Store the contrast value
        self.WP1 = 100.0  # Default value
        self.scale1.set_value(self.WP1)
        self.scale1.set_digits(2)
        self.scale1.add_mark(50.0, Gtk.PositionType.TOP, None)
        self.scale1.connect("value-changed", self.on_scale_value_changed, 1)
        box_4_scale1.pack_start(self.scale1, True, True, 0) # not use fixed

        #---add scale2 (slider) for 1st BP---
        box_4_scale2 = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(box_4_scale2)
        label = Gtk.Label(label="   ブラックポイント")
        label.set_xalign(0)
        box_4_scale2.pack_start(label, False, False, 0)
        self.scale2 = Gtk.Scale.new_with_range\
            (Gtk.Orientation.HORIZONTAL, 0.0, 100.0, 0.01)
        # 🔹 Store the contrast value
        self.BP1 = 0.0  # Default value
        self.scale2.set_value(self.BP1)
        self.scale2.set_digits(2)
        self.scale2.add_mark(50.0, Gtk.PositionType.TOP, None)
        self.scale2.connect("value-changed", self.on_scale_value_changed, 2)
        box_4_scale2.pack_start(self.scale2, True, True, 0) # not use fixed

        #--- add lower scale label
        label = Gtk.Label(label="上位スロープ")
        label.set_xalign(0)
        label.set_tooltip_text\
            ("ホワイトポイント = ブラックポイントに設定すると、\n" +\
              "上位スロープの指定は無効になります.")
        box.pack_start(label, False, False, 0)

        #---add scale3 (slider) for 1st WP---
        box_4_scale3 = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(box_4_scale3)
        label = Gtk.Label(label="   ホワイトポイント")
        label.set_xalign(0)
        box_4_scale3.pack_start(label, False, False, 0)
        self.scale3 = Gtk.Scale.new_with_range\
            (Gtk.Orientation.HORIZONTAL, 0.0, 100.0, 0.01)
        # 🔹 Store the contrast value
        self.WP2 = 0.0  # Default value
        self.scale3.set_value(self.WP2)
        self.scale3.set_digits(2)
        self.scale3.add_mark(50.0, Gtk.PositionType.TOP, None)
        self.scale3.connect("value-changed", self.on_scale_value_changed, 3)
        box_4_scale3.pack_start(self.scale3, True, True, 0) # not use fixed

        #---add scale4 (slider) for 1st BP---
        box_4_scale4 = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(box_4_scale4)
        label = Gtk.Label(label="   ブラックポイント")
        label.set_xalign(0)
        box_4_scale4.pack_start(label, False, False, 0)
        self.scale4 = Gtk.Scale.new_with_range\
            (Gtk.Orientation.HORIZONTAL, 0.0, 100.0, 0.01)
        # 🔹 Store the contrast value
        self.BP2 = 0.0  # Default value
        self.scale4.set_value(self.BP2)
        self.scale4.set_digits(2)
        self.scale4.add_mark(50.0, Gtk.PositionType.TOP, None)
        self.scale4.connect("value-changed", self.on_scale_value_changed, 4)
        box_4_scale4.pack_start(self.scale4, True, True, 0) # not use fixed

        #---add linear check box----
        label_check_box = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(label_check_box)

        self.linear = False
        self.linear_check = \
            Gtk.CheckButton.new_with_label(" リニアモードで計算")
        self.linear_check.set_tooltip_text("チェックを入れると"\
            + "計算はリニア値で行われます\n"\
            + "チェックを外すと計算は知覚値で行われます\n"
            )
        label_check_box.pack_start(self.linear_check, False, False, 0)
        self.linear_check.connect("toggled", self.on_linear_check_toggled)


        #---add graph---
        graph_box = \
            Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        box.add(graph_box)
        ydata = [0] * 256
        xdata = [0] * 256
        self.graph = GraphArea(ydata, xdata, self.chName, self.linear) # graph on Dialog
        graph_box.add(self.graph)
        label = Gtk.Label\
            (label="トーンカーブ ")
        label.set_xalign(1)
        graph_box.pack_start(label, True, True, 0)
        #---add end----

        #---add save mask check box----
        self.save_mask = False
        label_check2_box = \
            Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.add(label_check2_box)
        self.save_check = \
            Gtk.CheckButton.new_with_label(" マスク画像を tiff として保存")
        self.save_check.set_tooltip_text("マスク画像は元画像ファイルと同じ"\
             +"ディレクトリに保存されます\nもし元画像のファイル名が未設定なら"\
            + "マスク画像はユーザディレクトリの\n直下に \"_<カラー名>_mask.tif\" "\
            + "という名で保存されます")

        label_check2_box.pack_start(self.save_check, False, False, 0)
        self.save_check.connect("toggled", self.on_save_check_toggled)
        #---add end----

        #---add buttons---
        self.add_buttons("Cancel", Gtk.ResponseType.CANCEL,
                         "OK", Gtk.ResponseType.OK)
        self.show_all()

    def on_channel_combo_changed(self, combo): #combo: channel_combo
        chName = combo.get_active_text()
        self.chName = chName
        if chName == "Red":
            Channel = Gimp.HistogramChannel.RED
        elif chName == "Green":
            Channel = Gimp.HistogramChannel.GREEN
        elif chName == "Blue":
            Channel = Gimp.HistogramChannel.BLUE
        try:
            self.graph.update_data(self.pointsY, self.pointsX, self.chName, self.linear)
            self.update_curve(self.pointsY, self.pointsX)
        except:
            return

    def on_scale_value_changed(self, scale, data):
        # Trigger the curve update when the scale1 value changes, but debounce updates.
        # data: 1= White point1   2= Black point1
        #       3= White point2   4= Black point2
        #Gimp.message("scale number= " + str(scale.get_value()))
        # ---- paremeters update -------
        self.Lslope_up = True
        self.label.set_label("下位スロープ [上昇]")
        if data ==1:
            self.WP1 = scale.get_value()
            if self.WP2 < max(self.WP1, self.BP1):
                self.WP2 = max(self.WP1, self.BP1)
                self.scale3.set_value(self.WP2)
                self.BP2 = max(self.WP1, self.BP1)
                self.scale4.set_value(self.BP2)
            if self.WP1 > self.BP1:
                self.Lslope_up = True
                self.label.set_label("下位スロープ [上昇]")
            else:
                self.Lslope_up = False
                self.label.set_label("下位スロープ [下降]")
        elif data ==2:
            self.BP1 = scale.get_value()
            if self.BP2 < max(self.WP1, self.BP1):
                self.WP2 = max(self.WP1, self.BP1)
                self.scale3.set_value(self.WP2)
                self.BP2 = max(self.WP1, self.BP1)
                self.scale4.set_value(self.BP2)
            if self.WP1 >= self.BP1:
                self.Lslope_up = True
                self.label.set_label("下位スロープ [上昇]")
            else:
                self.Lslope_up = False
                self.label.set_label("下位スロープ [下降]")
        elif data ==3:
            if self.WP2 < max(self.WP1, self.BP1):
                self.WP2 = max(self.WP1, self.BP1)
                self.scale3.set_value(self.WP2)
                self.BP2 = max(self.WP1, self.BP1)
                self.scale4.set_value(self.BP2)
            if self.WP1 >= self.BP1:
                self.Lslope_up = True
                self.label.set_label("下位スロープ [上昇]")
            else:
                self.Lslope_up = False
                self.label.set_label("下位スロープ [下降]")
            self.WP2 = scale.get_value()

        elif data ==4:
            if self.BP2 < max(self.WP1, self.BP1):
                self.WP2 = max(self.WP1, self.BP1)
                self.scale3.set_value(self.WP2)
                self.BP2 = max(self.WP1, self.BP1)
                self.scale4.set_value(self.BP2)
            if self.WP1 >= self.BP1:
                self.Lslope_up = True
                self.label.set_label("下位スロープ [上昇]")
            else:
                self.Lslope_up = False
                self.label.set_label("下位スロープ [下降]")
            #Gimp.message(str(scale.get_value()) + str(self.Lslope_up))
            self.BP2 = scale.get_value()

        # Cancel any existing pending update
        if self.update_timer_id:
            GLib.source_remove(self.update_timer_id)

        # Set a new timer (e.g., 100ms delay)
        self.update_timer_id = \
            GLib.timeout_add(300, self._delayed_update)


    def _delayed_update(self):
        #Executes the update_curve function after a short delay.
        #---- calculate curves ------
        if self.BP2 < max(self.WP1, self.BP1):
            self.WP2 = max(self.WP1, self.BP1)
            self.scale3.set_value(self.WP2)
            self.BP2 = max(self.WP1, self.BP1)
            self.scale4.set_value(self.BP2)
        if self.Lslope_up == True and self.WP2 > self.BP2:
            self.BP2 = self.WP2
            self.scale4.set_value(self.BP2)
        elif self.Lslope_up == False and self.WP2 < self.BP2:
            self.BP2 = self.WP2
            self.scale4.set_value(self.BP2)

        if self.WP1 == 0.0 and self.BP1 == 0.0:
            self.WP1 = 0.001
        if self.Lslope_up == True:
            y0 = 0.0
        else:
            y0 = 1.0

        if self.WP2 != self.BP2: # enable second slope: BP2 != WP2
            if self.Lslope_up == True:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0,\
                        self.WP2 / 100.0, self.BP2 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 1.0, 0.0, 0.0]
            else:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0,\
                        self.WP2 / 100.0, self.BP2 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 1.0, 0.0, 1.0]
        elif self.WP2 != 100: # disable second slope: BP2 == WP2
            if self.Lslope_up == True:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0, self.WP2 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 1.0, 1.0]
            else:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0, self.BP2 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 0.0, 0.0]
        else:
            if self.Lslope_up == True:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 1.0]
            else:
                pointsX = [0.0, self.WP1 / 100.0, self.BP1 / 100.0, 1.0]
                pointsY=[y0, 1.0, 0.0, 0.0]

        self.graph.update_data(pointsY, pointsX, self.chName, self.linear)
        self.update_curve(pointsY, pointsX)

        self.pointsX = pointsX # output pointsX and Y
        self.pointsY = pointsY

        self.update_timer_id = None  # Reset timer ID
        return False  # Ensures the timeout runs only once

    def update_curve(self, pointsY, pointsX):
        #Gimp.message(self.layer.get_name())
        # Updates the curve dynamically when the user changes the value.
        #Gimp.message("SP: " + str(self.sp))
        #---- update tone curve -----
        try:
            layers = self.image.get_layers()
            self.image.remove_layer(layers[3])
            if self.chName == "Red":
                new_layer = layers[0].copy()
            elif self.chName == "Green":
                new_layer = layers[1].copy()
            elif self.chName == "Blue":
                new_layer = layers[2].copy()
            self.image.insert_layer(new_layer, None, 3)
            new_layer.set_visible(True)
            curve=[p for xy in zip(pointsX,pointsY) for p in xy]
            if self.linear == False:
                curve = [srgbToLinear(p) for p in curve]
            channel = Gimp.HistogramChannel.VALUE
            new_layer.curves_spline(channel, curve)
            Gimp.displays_flush()

        except Exception as e:
            Gimp.message(f"❌ カーブの更新に失敗:\n\n{e}")

    def on_linear_check_toggled(self, check): #check: linear_check
        self.linear = check.get_active()
        if self.linear == True:
            midPoint = srgbToLinear(0.5)
        else:
            midPoint = 0.5
        self.scale1.clear_marks()
        self.scale2.clear_marks()
        self.scale3.clear_marks()
        self.scale4.clear_marks()
        self.scale1.add_mark(midPoint * 100, Gtk.PositionType.TOP, None)
        self.scale2.add_mark(midPoint * 100, Gtk.PositionType.TOP, None)
        self.scale3.add_mark(midPoint * 100, Gtk.PositionType.TOP, None)
        self.scale4.add_mark(midPoint * 100, Gtk.PositionType.TOP, None)
        self._delayed_update()

    def on_save_check_toggled(self, check):
        self.save_mask = check.get_active()

class GraphArea(Gtk.DrawingArea):
    def __init__(self, ydata, xdata, chName, linear):
        super().__init__()
        self.linear = linear
        if chName == "Red":
            self.ch = Gimp.HistogramChannel.RED #channel color
        elif chName == "Green":
            self.ch = Gimp.HistogramChannel.GREEN #channel color
        elif chName == "Blue":
            self.ch = Gimp.HistogramChannel.BLUE #channel color
        self.chName = chName
        self.ydata = ydata
        self.xdata = xdata
        self.set_size_request(400, 300)
        self.connect("draw", self.on_draw)

    def update_data(self, new_data, new_xdata, chName, linear):
        self.linear = linear
        self.ydata = new_data
        self.xdata = new_xdata
        self.chName = chName # channel name
        self.queue_draw()

    #def on_draw(self, cr):
    def on_draw(self, widget, cr):
        width = self.get_allocated_width()
        height = self.get_allocated_height()
        # re-order xdata, ydata
        for i in range(len(self.xdata)):
            for j in range(i + 1, len(self.xdata)):
                if self.xdata[i] > self.xdata[j]:
                    tempx = self.xdata[i]
                    self.xdata[i] = self.xdata[j]
                    self.xdata[j] = tempx
                    tempy = self.ydata[i]
                    self.ydata[i] = self.ydata[j]
                    self.ydata[j] = tempy

        cr.set_source_rgb(0, 0, 0)  # background: black
        cr.paint()

        # draw axes
        cr.set_source_rgb(1, 1, 1)
        cr.set_line_width(2)
        cr.move_to(40, 10)
        cr.line_to(40, height - 20)
        cr.line_to(width - 10, height - 20)
        if self.linear == False:
            midgray = (width - 10 - 40) / 2 + 40
        else: #リニア
            midgray = (width - 10 - 40) * srgbToLinear(0.5)  + 40
        cr.move_to(midgray, height - 20)
        cr.line_to(midgray, (height - 20) - (height - 20) * 0.03)

        cr.stroke()

        if not self.ydata:
            return

        # draw data
        #max_val = max(self.ydata)
        max_val = 1.0
        step_x = (width - 60) / (len(self.ydata) - 1)
        scale_y = (height - 40) / max_val

        if self.chName == "Red":
            cr.set_source_rgb(1.0, 0.0, 0.0)  # line color: R
        elif  self.chName == "Green":
            cr.set_source_rgb(0.0, 1.0, 0.0)  # line color: G
        elif  self.chName == "Blue":
            cr.set_source_rgb(0.0, 0.0, 1.0)  # line color: B
        cr.set_line_width(2)
        xfactor = (width - 10) - 40 # factor for drawing x axis
        #print ("-------------")
        for i in range(len(self.xdata)):
            #print (self.xdata[i], self.ydata[i])
            x = 40 + self.xdata[i] * xfactor
            y = height - 20 - self.ydata[i] * scale_y
            if i == 0:
                cr.move_to(x, y)
            else:
                cr.line_to(x, y)
        cr.stroke()

Gimp.main(Param_Ch_mask.__gtype__, sys.argv)
