yast/yast-storage-ng

View on GitHub
src/lib/y2partitioner/widgets/fstab_options.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2017-2021] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# 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, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast"
require "cwm"
require "y2storage"
require "y2partitioner/dialogs/fstab_options"

Yast.import "Popup"

module Y2Partitioner
  # Partitioner widgets
  module Widgets
    include Yast::Logger

    # The fstab options are mostly checkboxes and combo boxes that share some
    # common methods, so this is a mixin for that shared code.
    module FstabCommon
      # @param controller [Y2Partitioner::Actions::Controllers:Filesystem]
      def initialize(controller)
        super()
        textdomain "storage"

        @controller = controller
      end

      # @macro seeAbstractWidget
      def init
        init_regexp if self.class.const_defined?("REGEXP")
      end

      # Not all the fstab options are supported by all the filesystems so each
      # widget is able to check if the current filesystem is supported
      # explicitely or checking if the values it is responsible for are
      # supported by the filesystem.
      def supported_by_filesystem?
        return false if filesystem.nil?

        if self.class.const_defined?("SUPPORTED_FILESYSTEMS")
          self.class::SUPPORTED_FILESYSTEMS
            .include?(filesystem.type.to_sym)
        elsif self.class.const_defined?("VALUES")
          self.class::VALUES.all? do |v|
            filesystem.type.supported_fstab_options.include?(v)
          end
        else
          false
        end
      end

      # @param widget [CWM::AbstractWidget]
      # @return [CWM::WidgetTerm]
      def to_ui_term(widget)
        return Empty() unless widget.supported_by_filesystem?

        Left(widget)
      end

      # @param widget [CWM::AbstractWidget]
      # @return [Array<CWM::WidgetTerm>]
      def ui_term_with_vspace(widget)
        return [Empty()] unless widget.supported_by_filesystem?

        [Left(widget), VSpacing(1)]
      end

      # Removes from the MountPoint object the options that not longer apply
      def delete_fstab_option!(option)
        # The options can only be modified using MountPoint#mount_options=
        mount_point.mount_options = mount_point.mount_options.grep_v(option)
      end

      # Adds new options to the MountPoint object
      def add_fstab_options(*options)
        # The options can only be modified using MountPoint#mount_options=
        mount_point.mount_options = mount_point.mount_options + options
      end

      alias_method :add_fstab_option, :add_fstab_options

      private

      # Current devicegraph
      #
      # @return [Y2Storage::Devicegraph]
      def working_graph
        DeviceGraphs.instance.current
      end

      # Filesystem currently being edited
      #
      # @return [Y2Storage::Filesystems::Base]
      def filesystem
        @controller.filesystem
      end

      # Check if the underlying filesystem is a btrfs.
      #
      # @return [Boolean]
      def btrfs?
        @controller.btrfs?
      end

      # Mount point of the current filesystem
      #
      # @return [Y2Storage::MountPoint]
      def mount_point
        @controller.mount_point
      end

      # Mount path of the current filesystem
      #
      # @return [String]
      def mount_path
        return nil if filesystem.mount_point.nil?

        filesystem.mount_point.path
      end

      # Common regexp checkbox widgets init.
      def init_regexp
        i = mount_point.mount_options.index { |o| o =~ self.class::REGEXP }

        self.value =
          if i
            mount_point.mount_options[i].gsub(self.class::REGEXP, "")
          else
            self.class::DEFAULT
          end
      end
    end

    # Push button that launch a dialog to set the fstab options
    class FstabOptionsButton < CWM::PushButton
      include FstabCommon

      # @macro seeAbstractWidget
      def label
        _("Fstab Options...")
      end

      # @macro seeAbstractWidget
      def handle
        log.info("fstab_options before dialog: #{mount_point.mount_options}")
        Dialogs::FstabOptions.new(@controller).run
        log.info("fstab_options after dialog: #{mount_point.mount_options}")

        nil
      end
    end

    # FIXME: The help handle does not work without wizard
    # Main widget for set all the available options for a particular filesystem
    class FstabOptions < CWM::CustomWidget
      include FstabCommon

      # Filesystem types that can be configured
      SUPPORTED_FILESYSTEMS = [:btrfs, :ext2, :ext3, :ext4].freeze

      def initialize(controller)
        super # to pass controller to FstabCommon
        @controller = controller

        self.handle_all_events = true
      end

      # @macro seeAbstractWidget
      def init
        @contents = nil
        @values = nil
        @regexps = nil
        disable if !supported_by_filesystem?
      end

      # @macro seeCustomWidget
      def contents
        @contents ||=
          VBox(
            Left(MountBy.new(@controller)),
            VSpacing(1),
            * ui_term_with_vspace(VolumeLabel.new(@controller, self)),
            Left(GeneralOptions.new(@controller)),
            Left(FilesystemsOptions.new(@controller)),
            * ui_term_with_vspace(JournalOptions.new(@controller)),
            Left(ArbitraryOptions.new(@controller, self))
          )
      end

      # Return an array of all VALUES of all widgets in this tree.
      # @return [Array<String>]
      def values
        @values ||= widgets.each_with_object([]) do |widget, values|
          next unless widget.class.const_defined?("VALUES")

          values.concat(widget.class::VALUES)
        end.uniq
      end

      # Return an array of all REGEXPs of all widgets in this tree.
      # @return [Array<Regexp>]
      def regexps
        @regexps ||= widgets.each_with_object([]) do |widget, regexps|
          next unless widget.class.const_defined?("REGEXP")

          regexps << widget.class::REGEXP
        end.uniq
      end

      def widgets
        Yast::CWM.widgets_in_contents([self])
      end
    end

    # Input field to set the partition Label
    class VolumeLabel < CWM::InputField
      include FstabCommon

      # Constructor
      #
      # @param controller [Actions::Controllers:Filesystem]
      # @param parent_widget [Widgets::FstabOptions]
      def initialize(controller, parent_widget)
        super(controller)

        @parent_widget = parent_widget
      end

      # @see FstabCommon::supported_by_filesystem?
      #
      # @return [Boolean]
      def supported_by_filesystem?
        filesystem.supports_label?
      end

      # @macro seeAbstractWidget
      def label
        _("Volume &Label")
      end

      # @macro seeAbstractWidget
      def store
        filesystem.label = value
      end

      # @macro seeAbstractWidget
      def init
        self.value = filesystem.label
        Yast::UI.ChangeWidget(Id(widget_id), :ValidChars, valid_chars)
        Yast::UI.ChangeWidget(Id(widget_id), :InputMaxLength, input_max_length)
        disable unless can_set_volume_label?
      end

      # Validates uniqueness of the given label. The presence of the label is also
      # checked when the filesystem is set to be mounted by label.
      #
      # @note An error popup message is presented when it is needed.
      #
      # @return [Boolean]
      def validate
        presence_validation && uniqueness_validation
      end

      private

      # @return [Widgets::FstabOptions]
      attr_reader :parent_widget

      # Check if the volume label can be set.
      #
      # @return [Boolean]
      def can_set_volume_label?
        return true unless @controller.mounted_in_system_graph?

        blk_dev = @controller.blk_device_name
        fs_type = @controller.filesystem_type
        log.info("#{blk_dev} type #{fs_type} is mounted")
        # Can't change the volume label for a mounted Btrfs or swap (bsc#1211337)
        !btrfs? && !swap?
      end

      def btrfs?
        @controller.filesystem_type.is?(:btrfs)
      end

      def swap?
        @controller.filesystem_type.is?(:swap)
      end

      # Checks whether a label is given when the filesystem is mounted by label
      #
      # @note An error popup is presented when the filesystem is mounted by label
      #   but a label is not given.
      #
      # @return [Boolean]
      def presence_validation
        return true unless mounted_by_label?
        return true unless value.empty?

        # TRANSLATORS: Error messagge when the label is not given.
        Yast::Popup.Error(_("Provide a volume label to mount by label."))
        focus

        false
      end

      # Checks whether a filesystem already exists with the given label
      #
      # @note An error popup is presented when other filesystem has the given label.
      #
      # @return [Boolean]
      def uniqueness_validation
        return true unless duplicated_label?

        # TRANSLATORS: Error message when the given label is already in use.
        Yast::Popup.Error(_("This volume label is already in use. Select a different one."))
        focus

        false
      end

      # Whether the mount by label option is selected
      #
      # @return [Boolean] true if mount by label is selected; false otherwise.
      def mounted_by_label?
        mount_by_widget.value == :label
      end

      # Whether the given label is duplicated
      #
      # @return [Boolean] true if the label is duplicated; false otherwise.
      def duplicated_label?
        return false if value.empty?

        working_graph.filesystems.any? do |fs|
          next false if fs.sid == filesystem.sid
          next false unless fs.respond_to?(:label) # NFS doesn't support labels

          fs.label == value
        end
      end

      # Widget to select the mount by option
      #
      # @return [MountBy]
      def mount_by_widget
        parent_widget.widgets.find { |w| w.is_a?(Y2Partitioner::Widgets::MountBy) }
      end

      # Sets the focus into this widget
      def focus
        Yast::UI.SetFocus(Id(widget_id))
      end

      # Return the valid characters for this input field
      #
      # @return [String]
      def valid_chars
        filesystem.type.label_valid_chars
      end

      # Return the maximum length of the input (the number of characters) for
      # this input field
      #
      # @return [Integer]
      def input_max_length
        filesystem.max_labelsize
      end
    end

    # A combobox to select the type of identifier to be used for mount
    # the specific device (UUID, Label, Path...)
    class MountBy < CWM::ComboBox
      include FstabCommon

      # @macro seeAbstractWidget
      def label
        _("Mount in /etc/fstab By")
      end

      # @macro seeAbstractWidget
      def init
        select_default_mount_by
      end

      # @macro seeAbstractWidget
      def store
        mount_point.mount_by = selected_mount_by
      end

      # @macro seeCustomWidget
      def items
        # CWM does not support symbols for entries in ComboBoxes
        # contrary to libyui. Otherwise a few conversations between
        # string and symbol below could be avoided.

        suitable = mount_point.suitable_mount_bys(label: true, encryption:
          @controller.encrypt).map { |mount_by| mount_by.to_sym.to_s }

        [
          ["device", _("Device Name")],
          ["id", _("Device ID")],
          ["path", _("Device Path")],
          ["uuid", _("UUID")],
          ["label", _("Volume Label")]
        ].select { |item| suitable.include? item[0] }
      end

      private

      def selected_mount_by
        Y2Storage::Filesystems::MountByType.find(value.to_sym)
      end

      def select_default_mount_by
        self.value = mount_point.mount_by.to_s
      end
    end

    # A group of options that are general for many filesystem types.
    class GeneralOptions < CWM::CustomWidget
      include FstabCommon

      # @macro seeCustomWidget
      def contents
        return Empty() unless widgets.any?(&:supported_by_filesystem?)

        VBox(* widgets.map { |w| to_ui_term(w) }, VSpacing(1))
      end

      def widgets
        [
          ReadOnly.new(@controller),
          MountUser.new(@controller),
          Noauto.new(@controller),
          Quota.new(@controller)
        ]
      end
    end

    # Generic checkbox for fstab options
    # VALUES must be a pair: ["fire", "water"] means "fire" is checked and "water" unchecked
    class FstabCheckBox < CWM::CheckBox
      include FstabCommon

      # @macro seeAbstractWidget
      def init
        self.value = mount_point.mount_options.include?(checked_value)
      end

      # @macro seeAbstractWidget
      def store
        delete_fstab_option!(Regexp.union(options))
        add_fstab_option(checked_value) if value
      end

      private

      # Possible values
      def options
        self.class::VALUES
      end

      def checked_value
        self.class::VALUES[0]
      end
    end

    # CheckBox to disable the automount option when starting up
    class Noauto < FstabCheckBox
      # Possible values of the widget
      VALUES = ["noauto", "auto"].freeze

      # @macro seeAbstractWidget
      def label
        _("Do Not Mount at System &Start-up")
      end
    end

    # CheckBox to enable the read only option ("ro")
    class ReadOnly < FstabCheckBox
      include FstabCommon

      # Possible values of the widget
      VALUES = ["ro", "rw"].freeze

      # @macro seeAbstractWidget
      def label
        _("Mount &Read-Only")
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Mount Read-Only:</b>\n" \
          "Writing to the file system is not possible. Default is false. During installation\n" \
          "the file system is always mounted read-write.</p>")
      end
    end

    # CheckBox to enable the user option which means allow to mount the
    # filesystem by an ordinary user
    class MountUser < FstabCheckBox
      # Possible values of the widget
      VALUES = ["user", "nouser"].freeze

      # @macro seeAbstractWidget
      def label
        _("Mountable by User")
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Mountable by User:</b>\nThe file system may be " \
          "mounted by an ordinary user. Default is false.</p>\n")
      end
    end

    # CheckBox to enable the use of user quotas
    class Quota < CWM::CheckBox
      include FstabCommon

      # Possible values of the widget
      VALUES = ["grpquota", "usrquota"].freeze

      # @macro seeAbstractWidget
      def label
        _("Enable &Quota Support")
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Enable Quota Support:</b>\n" \
          "The file system is mounted with user quotas enabled.\n" \
          "Default is false.</p>")
      end

      # @macro seeAbstractWidget
      def init
        self.value = mount_point.mount_options.any? { |o| VALUES.include?(o) }
      end

      # @macro seeAbstractWidget
      def store
        delete_fstab_option!(Regexp.union(VALUES))
        add_fstab_options("usrquota", "grpquota") if value
      end
    end

    # Generic ComboBox for fstab options.
    #
    # This uses some constants that each derived class should define:
    #
    # REGEXP [Regex] The regular expression describing the fstab option.
    # If it ends with "=", the value will be appended to it.
    #
    # ITEMS [Array<String>] The items to choose from.
    # The first one is used as the default (initial) value.
    #
    class FstabComboBox < CWM::ComboBox
      include FstabCommon

      # Set the combo box value to the current value matching REGEXP.
      def init
        i = mount_point.mount_options.index { |o| o =~ self.class::REGEXP }
        self.value = i ? mount_point.mount_options[i].gsub(self.class::REGEXP, "") : default_value
      end

      # Convert REGEXP to the option string. This is a very basic
      # implementation that just removes a "^" if the regexp contains it.
      # For anything more sophisticated, reimplement this.
      #
      # @return [String]
      def option_str
        self.class::REGEXP.source.delete("^")
      end

      # Overriding FstabCommon::supported_by_filesystem? to make use of the
      # REGEXP and to avoid having to duplicate it in VALUES
      #
      # @return [Boolean]
      def supported_by_filesystem?
        return false if filesystem.nil?
        return false unless supported_by_mount_path?

        filesystem.type.supported_fstab_options.any? { |opt| opt =~ self.class::REGEXP }
      end

      # Check if this mount option is supported by the current mount path.
      # For /boot/* or the root filesystem some options might not be supported.
      #
      # @return [Boolean]
      def supported_by_mount_path?
        true
      end

      # The default value for the option.
      #
      # @return [String]
      def default_value
        items.first.first
      end

      # Store the current value in the fstab_options.
      # If the value is nil or empty, it will only remove the old value.
      #
      # If option_str (i.e. normally REGEXP) ends with "=", the value is
      # appended to it, otherwise only the value is used.
      # "codepage=" -> "codepage=value"
      # "foo" -> "value"
      def store
        delete_fstab_option!(self.class::REGEXP)
        return if value.nil? || value.empty?

        opt = option_str
        if opt.end_with?("=")
          opt += value
        else
          opt = value
        end
        add_fstab_option(opt)
      end

      # Convert ITEMS to the format expected by the underlying
      # CWM::ComboBox.
      def items
        self.class::ITEMS.map { |val| [val, val] }
      end

      # Widget options
      def opt
        [:editable, :hstretch]
      end
    end

    # ComboBox to specify the journal mode to use by the filesystem
    class JournalOptions < FstabComboBox
      # Format of the option
      REGEXP = /^data=/

      # @macro seeAbstractWidget
      def label
        _("Data &Journaling Mode")
      end

      def default_value
        "ordered"
      end

      def items
        [
          ["journal", _("journal")],
          ["ordered", _("ordered")],
          ["writeback", _("writeback")]
        ]
      end

      def supported_by_mount_path?
        # journal options tend to break remounting root rw (bsc#1077859).
        # See also root_fstab_options() in lib/y2storage/filesystems/type.rb
        mount_path != "/"
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Data Journaling Mode:</b>\n" \
          "Specifies the journaling mode for file data.\n" \
          "<tt>journal</tt> -- All data is committed to the journal prior to being\n" \
          "written into the main file system. Highest performance impact.<br>\n" \
          "<tt>ordered</tt> -- All data is forced directly out to the main file system\n" \
          "prior to its metadata being committed to the journal. Medium performance impact.<br>\n" \
          "<tt>writeback</tt> -- Data ordering is not preserved. No performance impact.</p>\n")
      end
    end

    # An input field that allows to set other options that are not handled by
    # specific widgets.
    #
    class ArbitraryOptions < CWM::InputField
      include FstabCommon

      def initialize(controller, parent_widget)
        super(controller) # pass controller to FstabCommon
        textdomain "storage"
        @controller = controller
        @parent_widget = parent_widget
        @other_values = nil
        @other_regexps = nil
      end

      # @macro seeCustomWidget
      def opt
        [:hstretch]
      end

      # @macro seeAbstractWidget
      def label
        _("Arbitrary Option &Value")
      end

      # @macro seeAbstractWidget
      def init
        self.value = unhandled_options(mount_point.mount_options).join(",")
      end

      # @macro seeAbstractWidget
      def store
        keep_only_options_handled_in_other_widgets
        return unless value

        options = clean_whitespace(value).split(",")
        # Intentionally NOT filtering out only unhandled options: When the user
        # adds anything here that also has a corresponding checkbox or combo
        # box in this same dialog, the value here will win, and when entering
        # this dialog again the dedicated widget will take the value from
        # there, and it will be filtered out in this arbitrary options widget.
        #
        # So, when a user insists in adding "noauto,user" here, it is applied
        # correctly, but when entering the dialog again, the checkboxes pick up
        # those values and they won't show up in this field anymore.
        add_fstab_options(*options)
      end

      # @macro seeAbstractWidget
      def help
        _(
          "<p><b>Arbitrary Option Value:</b> " \
          "Enter any other mount options here, separated with commas. " \
          "Notice that this does not do any checking, so be careful what you enter here!<br>" \
          "If you want to have utf8 encoded filenames with limited case-insensitivity then " \
          "add 'utf8' here and leave the Charset for File Names field empty." \
          "</p>"
        )
      end

      private

      # Clean whitespace. We need to preserve whitespace that might possibly be
      # intentional within a mount option, but we want graceful error handling
      # when a user put additional blanks between them, e.g. "foo, bar" or
      # "foo , bar".
      def clean_whitespace(str)
        str.gsub(/\s*,\s*/, ",")
      end

      def keep_only_options_handled_in_other_widgets
        mount_point.mount_options = mount_point.mount_options.select do |opt|
          handled_in_other_widget?(opt)
        end
      end

      def unhandled_options(options)
        options.reject do |opt|
          handled_in_other_widget?(opt)
        end
      end

      def handled_in_other_widget?(opt)
        return true if other_values.include?(opt)

        other_regexps.any? { |r| opt =~ r }
      end

      # Return all values that are handled by other widgets in this widget tree.
      # @return [Array<String>]
      def other_values
        return [] unless @parent_widget.respond_to?(:values)

        @other_values ||= @parent_widget.values
      end

      # Return all regexps that are handled by other widgets in this widget tree.
      # @return [Array<Regexp>]
      def other_regexps
        return [] unless @parent_widget.respond_to?(:regexps)

        @other_regexps ||= @parent_widget.regexps
      end
    end

    # Some options that are mainly specific for one filesystem
    class FilesystemsOptions < CWM::CustomWidget
      include FstabCommon

      # @macro seeCustomWidget
      def contents
        return Empty() unless widgets.any?(&:supported_by_filesystem?)

        VBox(* widgets.map { |w| to_ui_term(w) }, VSpacing(1))
      end

      def widgets
        [
          SwapPriority.new(@controller),
          TmpfsSize.new(@controller),
          IOCharset.new(@controller),
          Codepage.new(@controller)
        ]
      end
    end

    # Swap priority
    class SwapPriority < CWM::InputField
      include FstabCommon

      # Possible values of the widget
      VALUES = ["pri="].freeze
      # Format of the option
      REGEXP  = /^pri=/
      # Default value of the widget
      DEFAULT = "".freeze

      # @macro seeAbstractWidget
      def label
        _("Swap &Priority")
      end

      # @macro seeAbstractWidget
      def store
        delete_fstab_option!(REGEXP)
        add_fstab_option("pri=#{value}") if value && !value.empty?
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Swap Priority:</b>\nEnter the swap priority. " \
          "Higher numbers mean higher priority.</p>\n")
      end
    end

    # Size for tmpfs
    class TmpfsSize < CWM::InputField
      include FstabCommon

      # Possible values of the widget
      VALUES = ["size="].freeze
      # Format of the option
      REGEXP  = /^size=/
      # Default value of the widget
      DEFAULT = "".freeze
      # Regular expression to validate the content of #value
      VALUE_REGEXP = /^\d+(k|m|g|%)?$/i

      # @macro seeAbstractWidget
      def label
        _("Max Size")
      end

      # @macro seeAbstractWidget
      def store
        delete_fstab_option!(REGEXP)
        add_fstab_option("size=#{value}") if value && !value.empty?
      end

      # @macro seeAbstractWidget
      def validate
        return true if value.nil? || value.empty?
        return true if value =~ VALUE_REGEXP

        Yast::Popup.Error(
          _(
            "The size must be a number,\n" \
            "optionally followed by a unit (k, m or g) or by a percent sign (%)."
          )
        )
        focus
        false
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Max Size:</b>\nUpper limit on the size of the file system. " \
          "The size is given in bytes by default, optionally followed by a <b>k</b>, <b>m</b> " \
          "or <b>g</b> suffix to specify KiB, MiB or GiB. The size may also have a <b>% </b> " \
          "suffix to limit this instance to a percentage of physical RAM. " \
          "The default, when neither this option nor nr_blocks is specified, is 50%.</p>\n")
      end
    end

    # VFAT IOCharset
    class IOCharset < FstabComboBox
      # Format of the option
      REGEXP = /^iocharset=/
      # Possible values
      ITEMS = [
        "", "iso8859-1", "iso8859-15", "iso8859-2", "iso8859-5", "iso8859-7",
        "iso8859-9", "utf8", "koi8-r", "euc-jp", "sjis", "gb2312", "big5",
        "euc-kr"
      ].freeze

      # @macro seeAbstractWidget
      def store
        delete_fstab_option!(/^utf8=.*/)
        super
      end

      def default_value
        ITEMS.first
      end

      def supported_by_mount_path?
        return false if mount_path.nil?

        # We used to disable the iocharset dialog for filesystems used for booting since
        # iocharset=utf8 breaks VFAT case insensitivity (bsc#1080731).
        #
        # That's taken care of now in {Filesystems::Type#patch_iocharset}
        # when we create a new file system.
        true
      end

      # @macro seeAbstractWidget
      def label
        _("Char&set for File Names")
      end

      # @macro seeAbstractWidget
      def help
        _(
          "<p><b>Charset for File Names:</b>\n" \
          "Set the charset used for display of file names in Windows partitions.<br>\n" \
          "Note that choosing 'utf8' here will implicitly make " \
          "filename comparison case-sensitve.<br>\n" \
          "To avoid this, leave this field empty and instead add 'utf8' " \
          "to the Arbitrary Option Value field." \
          "</p>\n"
        )
      end
    end

    # VFAT Codepage
    class Codepage < FstabComboBox
      # Format of the option
      REGEXP = /^codepage=/
      # Possible values
      ITEMS = ["", "437", "852", "932", "936", "949", "950"].freeze

      def default_value
        ITEMS.first
      end

      # @macro seeAbstractWidget
      def label
        _("Code&page for Short FAT Names")
      end

      # @macro seeAbstractWidget
      def help
        _("<p><b>Codepage for Short FAT Names:</b>\nThis codepage is used for " \
          "converting to shortname characters on FAT file systems.</p>\n")
      end
    end
  end
end