diff --git a/krita/Messages.sh b/krita/Messages.sh --- a/krita/Messages.sh +++ b/krita/Messages.sh @@ -9,7 +9,17 @@ | grep -v gemini/KritaGeminiWin.rc ` $EXTRACTRC $RCFILES >> rc.cpp + +ACTIONFILES=`find . -name \*.action` +./action_i18n.pl --context=action $ACTIONFILES >> rc.cpp + +# extracti18n.pl extracts additional data from brushes, palettes etc. perl extracti18n.pl > i18ndata -# ignore sdk/templates since it contains templates for use a future plugins, none of the strings there will ever be seen by any user -calligra_xgettext krita.pot i18ndata rc.cpp `find . -name \*.cc -o -name \*.h -o -name \*.cpp | grep -v '/tests/' | grep -v './sdk/templates'` + +# Ignore sdk/templates which contains templates for writing future plugins. +# None of the placeholder strings inside will be seen by users. +calligra_xgettext krita.pot i18ndata rc.cpp \ + `find . -name \*.cc -o -name \*.h -o -name \*.cpp | grep -v '/tests/' | grep -v './sdk/templates'` + +# Clean up rm -f i18ndata rc.cpp diff --git a/krita/action_i18n.pl b/krita/action_i18n.pl new file mode 100755 --- /dev/null +++ b/krita/action_i18n.pl @@ -0,0 +1,473 @@ +#! /usr/bin/env perl + +### TODO: other copyrights, license? +# Copyright (c) 2004 Richard Evans + +sub usage +{ + warn <<"EOF"; + +extractrc [flags] filenames + +This script extracts messages from designer (.ui) and XMLGUI (.rc) files and +writes on standard output (usually redirected to rc.cpp) the equivalent +i18n() calls so that xgettext can parse them. + +--tag=name : Also extract the tag name(s). Repeat the flag to specify + multiple names: --tag=tag_one --tag=tag_two + +--tag-group=group : Use a group of tags - uses 'default' if omitted. + Valid groups are: @{[TAG_GROUPS()]} + +--context=name : Give i18n calls a context name: i18nc("name", ...) +--lines : Include source line numbers in comments (deprecated, it is switched on by default now) +--cstart=chars : Start of to-EOL style comments in output, defaults to // +--language=lang : Create i18n calls appropriate for KDE bindings + in the given language. Currently known languages: + C++ (default), Python +--ignore-no-input : Do not warn if there were no filenames specified +--help|? : Display this summary +--no-unescape-xml : Don't do xml unescaping + +EOF + + exit; +} + +########################################################################################### + +use strict; +use warnings; +use Getopt::Long; +use Data::Dumper; # Provides debugging command: print Dumper(\%hash); + +use constant TAG_GROUP => +{ + default => "[tT][eE][xX][tT]|title|string|whatsthis|toolTip|label", + koffice => "Example|GroupName|Text|Comment|Syntax|TypeName", + krita => "[tT][eE][xX][tT]|title|string|whatsThis|toolTip|iconText", + none => "", +}; + +use constant TAG_GROUPS => join ", ", map "'$_'", sort keys %{&TAG_GROUP}; + +# Specification to extract nice element-context for strings. +use constant CONTEXT_SPEC => +{ + # Data structure: extension => {tag => [ctxlevel, [attribute, ...]], ...} + # Order of attributes determines their order in the extracted comment. + "ui" => { + "widget" => [10, ["class", "name"]], + "item" => [15, []], + "property" => [20, ["name"]], + "attribute" => [20, ["name"]], + }, + "rc" => { + "Menu" => [10, ["name"]], + "ToolBar" => [10, ["name"]], + }, + "kcfg" => { + "group" => [10, ["name"]], + "entry" => [20, ["name"]], + "whatsthis" => [30, []], + "tooltip" => [30, []], + "label" => [30, []], + }, + "action" => { + "action" => [20, ["name"]], + } +}; + +# Specification to exclude strings by trailing section of element-context. +use constant CONTEXT_EXCLUDE => +[ + # Data structure: [[tag, attribute, attrvalue], [...]] + # Empty ("") attribute means all elements with given tag, + # empty attrvalue means element with given tag and attribute of any value. + [["widget", "class", "KFontComboBox"], ["item", "", ""], ["property", "", ""]], + [["widget", "class", "KPushButton"], ["attribute", "name", "buttonGroup"]], + [["widget", "class", "QRadioButton"], ["attribute", "name", "buttonGroup"]], + [["widget", "class", "QToolButton"], ["attribute", "name", "buttonGroup"]], + [["widget", "class", "QCheckBox"], ["attribute", "name", "buttonGroup"]], + [["widget", "class", "QPushButton"], ["attribute", "name", "buttonGroup"]], + [["widget", "class", "KTimeZoneWidget"], ["property", "name", "text"]], +]; + +# The parts between the tags of the extensions will be copied verbatim +# Same data structure as in CONTEXT_EXCLUDE, but per extension. +my %EXTENSION_VERBATIM_TAGS = ( + "kcfg" => [["code", "", ""], ["default", "code", "true"], + ["min", "code", "true"], ["max", "code", "true"]], + ); + +# Add attribute lists as hashes, for membership checks. +for my $ext ( keys %{&CONTEXT_SPEC} ) { + for my $tag ( keys %{CONTEXT_SPEC->{$ext}} ) { + my $arr = CONTEXT_SPEC->{$ext}{$tag}[1]; + CONTEXT_SPEC->{$ext}{$tag}[2] = {map {$_ => 1} @{$arr}}; + } +} + +########################################################################################### +# Add options here as necessary - perldoc Getopt::Long for details on GetOptions + +GetOptions ( "tag=s" => \my @opt_extra_tags, + "tag-group=s" => \my $opt_tag_group, + "context=s" => \my $opt_context, # I18N context + "lines" => \my $opt_lines, + "cstart=s" => \my $opt_cstart, + "language=s" => \my $opt_language, + "ignore-no-input" => \my $opt_ignore_no_input, + "no-unescape-xml" => \my $opt_no_unescape_xml, + "help|?" => \&usage ); + +unless( @ARGV ) +{ + warn "No filename specified" unless $opt_ignore_no_input; + exit; +} + +$opt_tag_group ||= "default"; + +die "Unknown tag group: '$opt_tag_group', should be one of " . TAG_GROUPS + unless exists TAG_GROUP->{$opt_tag_group}; + +my $tags = TAG_GROUP->{$opt_tag_group}; +my $extra_tags = join "", map "|" . quotemeta, @opt_extra_tags; +my $text_string = qr/($tags$extra_tags)( [^>]*)?>/; # Precompile regexp +my $cstart = $opt_cstart; # no default, selected by language if not given +my $language = $opt_language || "C++"; +my $context_known_exts = join "|", keys %{&CONTEXT_SPEC}; + +########################################################################################### + +# Unescape basic XML entities. +sub unescape_xml ($) { + my $text = shift; + + if (not $opt_no_unescape_xml) { + $text =~ s/<//g; + $text =~ s/&/&/g; + $text =~ s/"/"/g; + } + + return $text; +} + +# Convert uic to C escaping. +sub escape_uic_to_c ($) { + my $text = shift; + + $text = unescape_xml($text); + + $text =~ s/\\/\\\\/g; # escape \ + $text =~ s/\"/\\\"/g; # escape " + $text =~ s/\r//g; # remove CR (Carriage Return) + $text =~ s/\n/\\n\"\n\"/g; # escape LF (Line Feed). uic also change the code line at a LF, we do not do that. + + return $text; +} + +########################################################################################### + +sub dummy_call_infix { + my ($cstart, $stend, $ctxt, $text, @cmnts) = @_; + for my $cmnt (@cmnts) { + print qq|$cstart $cmnt\n|; + } + if (defined $text) { + $text = escape_uic_to_c($text); + if (defined $ctxt) { + $ctxt = escape_uic_to_c($ctxt); + print qq|i18nc("$ctxt", "$text")$stend\n|; + } else { + print qq|i18n("$text")$stend\n|; + } + } +} + +my %dummy_calls = ( + "C++" => sub { + dummy_call_infix($cstart || "//", ";", @_); + }, + "Python" => sub { + dummy_call_infix($cstart || "#", "", @_); + }, +); + +die "unknown language '$language'" if not defined $dummy_calls{$language}; +my $dummy_call = $dummy_calls{$language}; + +# Program start proper - outer loop runs once for each file in the argument list. +for my $file_name ( @ARGV ) +{ + my $fh; + + unless ( open $fh, "<", $file_name ) + { + # warn "Failed to open: '$file_name': $!"; + next; + } + + # Ready element-context extraction. + my $context_ext; + my $context_string; # Regexp used to validate context + if ( $file_name =~ /\.($context_known_exts)(\.(in|cmake))?$/ ) { + $context_ext = $1; + my $context_tag_gr = join "|", keys %{CONTEXT_SPEC->{$context_ext}}; + $context_string = qr/($context_tag_gr)( [^>]*)?>/; # precompile regexp + } + + my $string = ""; + my $origstring = ""; + my $in_text = 0; # Are we currently inside a block of raw text? + my $start_line_no = 0; + my $in_skipped_prop = 0; # Are we currently inside XML property that shouldn't be translated? + my $tag = ""; + my $attr = ""; + my $context = ""; + my $notr = ""; + + # Element-context data: [[level, tag, [[attribute, value], ...]], ...] + # such that subarrays are ordered increasing by level. + my @context = (); + + # All comments to pending dummy call. + my @comments = (); + + # Begin looping through the file + while ( <$fh> ) + { + # If your Perl is a bit rusty: $. is the current line number + # Also, =~ and !~ are pattern-matching operators. :) + if ( $. == 1 and $_ !~ /^(?:{$context_ext}{$tag} ) + { + my @atts; + for my $context_att ( @{CONTEXT_SPEC->{$context_ext}{$tag}[1]} ) + { + if ( $attr and $attr =~ /\b$context_att\s*=\s*(["'])([^"']*?)\1/ ) + { + my $aval = $2; + push @atts, [$context_att, $aval]; + } + } + # Kill all tags in element-context with level higer or equal to this, + # and add it to the end. + my $clevel = CONTEXT_SPEC->{$context_ext}{$tag}[0]; + for ( my $i = 0; $i < @context; ++$i ) + { + if ( $clevel <= $context[$i][0] ) + { + @context = @context[0 .. ($i - 1)]; + last; + } + } + push @context, [$clevel, $tag, [@atts]]; + } + + if ( ($tag, $attr) = $string =~ /<$text_string/o ) + { + my ($attr_comment) = $attr =~ /\bcomment=\"([^\"]*)\"/ if $attr; + $context = $attr_comment if $attr_comment; + my ($attr_context) = $attr =~ /\bcontext=\"([^\"]*)\"/ if $attr; + $context = $attr_context if $attr_context; + # It is unlikely that both attributes 'context' and 'comment' + # will be present, but if so happens, 'context' has priority. + my ($attr_extracomment) = $attr =~ /\bextracomment=\"([^\"]*)\"/ if $attr; + push @comments, "i18n: $attr_extracomment" if $attr_extracomment; + + my ($attr_notr) = $attr =~ /\bnotr=\"([^\"]*)\"/ if $attr; + $notr = $attr_notr if $attr_notr; + + my $nongreedystring = $string; + $string =~ s/^.*<$text_string//so; + $nongreedystring =~ s/^.*?<$text_string//so; + if ($string cmp $nongreedystring) + { + print STDERR "Warning: Line $origstring in file $file_name has more than one tag to extract on the same line, that is not supported by extractrc\n"; + } + if ( not $attr or $attr !~ /\/ *$/ ) + { + $in_text = 1; + $start_line_no = $.; + } + } + else + { + @comments = (); + $string = ""; + } + } + + next unless $in_text; + next unless $string =~ /<\/$text_string/o; + + my $text = $string; + $text =~ s/<\/$text_string.*$//o; + + if ( $text cmp "" ) + { + # See if the string should be excluded by trailing element-context. + my $exclude_by_context = 0; + my @rev_context = reverse @context; + for my $context_tail (@{&CONTEXT_EXCLUDE}) + { + my @rev_context_tail = reverse @{$context_tail}; + my $i = 0; + $exclude_by_context = (@rev_context > 0 and @rev_context_tail > 0); + while ($i < @rev_context and $i < @rev_context_tail) + { + my ($tag, $attr, $aval) = @{$rev_context_tail[$i]}; + $exclude_by_context = (not $tag or ($tag eq $rev_context[$i][1])); + if ($exclude_by_context and $attr) + { + $exclude_by_context = 0; + for my $context_attr_aval (@{$rev_context[$i][2]}) + { + if ($attr eq $context_attr_aval->[0]) + { + $exclude_by_context = $aval ? $aval eq $context_attr_aval->[1] : 1; + last; + } + } + } + last if not $exclude_by_context; + ++$i; + } + last if $exclude_by_context; + } + + if (($context and $context eq "KDE::DoNotExtract") or ($notr eq "true")) + { + push @comments, "Manually excluded message at $file_name line $."; + } + elsif ( $exclude_by_context ) + { + push @comments, "Automatically excluded message at $file_name line $."; + } + else + { + # Write everything to file + (my $clean_file_name = $file_name) =~ s/^\.\///; + push @comments, "i18n: file: $clean_file_name:$."; + if ( @context ) { + # Format element-context. + my @tag_gr; + for my $tgr (reverse @context) + { + my @attr_gr; + for my $agr ( @{$tgr->[2]} ) + { + #push @attr_gr, "$agr->[0]=$agr->[1]"; + push @attr_gr, "$agr->[1]"; # no real nead for attribute name + } + my $attr = join(", ", @attr_gr); + push @tag_gr, "$tgr->[1] ($attr)" if $attr; + push @tag_gr, "$tgr->[1]" if not $attr; + } + my $context_str = join ", ", @tag_gr; + push @comments, "i18n: context: $context_str"; + } + push @comments, "xgettext: no-c-format" if $text =~ /%/o; + $dummy_call->($context, $text, @comments); + @comments = (); + } + } + else + { + push @comments, "Skipped empty message at $file_name line $."; + } + + $string =~ s/^.*<\/$text_string//o; + $in_text = 0; + + # Text can be multiline in .ui files (possibly), but we warn about it in XMLGUI .rc files. + + warn "there is floating in: '$file_name'" if $. != $start_line_no and $file_name =~ /\.rc$/i; + } + + close $fh or warn "Failed to close: '$file_name': $!"; + + die "parsing error in $file_name" if $in_text; + + if ($context_ext && exists $EXTENSION_VERBATIM_TAGS{$context_ext}) + { + unless ( open $fh, "<", $file_name ) + { + # warn "Failed to open: '$file_name': $!"; + next; + } + + while ( <$fh> ) + { + chomp; + $string .= "\n" . $_; + + for my $elspec (@{ $EXTENSION_VERBATIM_TAGS{$context_ext} }) + { + my ($tag, $attr, $aval) = @{$elspec}; + my $rx; + if ($attr and $aval) { + $rx = qr/<$tag[^<]*$attr=["']$aval["'][^<]*>(.*)<\/$tag>/s + } elsif ($attr) { + $rx = qr/<$tag[^<]*$attr=[^<]*>(.*)<\/$tag>/s + } else { + $rx = qr/<$tag>(.*)<\/$tag>/s + } + if ($string =~ $rx) + { + # Add comment before any line that has an i18n substring in it. + my @matched = split /\n/, $1; + my $mlno = $.; + (my $norm_fname = $file_name) =~ s/^\.\///; + for my $mline (@matched) { + # Assume verbatim code is in language given by --language. + # Therefore format only comment, and write code line as-is. + if ($mline =~ /i18n/) { + $dummy_call->(undef, undef, ("i18n: file: $norm_fname:$mlno")); + } + $mline = unescape_xml($mline); + print "$mline\n"; + ++$mlno; + } + $string = ""; + } + } + } + + close $fh or warn "Failed to close: '$file_name': $!"; + } +} diff --git a/krita/data/actions/TextTool.action b/krita/data/actions/TextTool.action --- a/krita/data/actions/TextTool.action +++ b/krita/data/actions/TextTool.action @@ -15,7 +15,7 @@ Decrease Indent Decrease Indent - format-indent-less + false @@ -81,7 +81,7 @@ Increase Indent Increase Indent - format-indent-more + false diff --git a/krita/plugins/extensions/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp b/krita/plugins/extensions/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp --- a/krita/plugins/extensions/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp +++ b/krita/plugins/extensions/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp @@ -42,6 +42,8 @@ #include "kis_color_selector_settings.h" #include "kis_color_selector_container.h" +#include "kis_action_registry.h" + KisColorSelectorNgDockerWidget::KisColorSelectorNgDockerWidget(QWidget *parent) : QWidget(parent), m_colorHistoryAction(0), @@ -93,11 +95,13 @@ m_colorHistoryAction = new QAction("Show color history", this); m_colorHistoryAction->setShortcut(QKeySequence(tr("H"))); + // m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_color_history", this, "Color Selector"); connect(m_colorHistoryAction, SIGNAL(triggered()), m_colorHistoryWidget, SLOT(showPopup()), Qt::UniqueConnection); m_commonColorsAction = new QAction("Show common colors", this); m_commonColorsAction->setShortcut(QKeySequence(tr("U"))); + // m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_common_colors", this, "Color Selector"); connect(m_commonColorsAction, SIGNAL(triggered()), m_commonColorsWidget, SLOT(showPopup()), Qt::UniqueConnection); } diff --git a/krita/ui/KisPart.cpp b/krita/ui/KisPart.cpp --- a/krita/ui/KisPart.cpp +++ b/krita/ui/KisPart.cpp @@ -40,7 +40,6 @@ #include "KisViewManager.h" #include "KisOpenPane.h" #include "KisImportExportManager.h" -#include #include #include @@ -126,44 +125,10 @@ foreach (const QString &name, actionNames) { - QDomElement actionXml = actionRegistry->getActionXml(name); - // Convenience macros to extract text of a child node. - auto getChildContent = [=](QString node){return actionXml.firstChildElement(node).text();}; - // i18n requires converting format from QString. - auto getChildContent_i18n = [=](QString node) { - if (getChildContent(node).isEmpty()) { - // dbgAction << "Found empty string to translate for property" << node; - return QString(); - } - return i18n(getChildContent(node).toUtf8().constData()); - }; + KisAction *a = new KisAction(); + actionRegistry->propertizeAction(name, a); - QString icon = getChildContent("icon"); - QString text = getChildContent("text"); - - // Note: these fields in the .action definitions are marked for translation. - QString whatsthis = getChildContent_i18n("whatsThis"); - QString toolTip = getChildContent_i18n("toolTip"); - QString statusTip = getChildContent_i18n("statusTip"); - QString iconText = getChildContent_i18n("iconText"); - - bool isCheckable = getChildContent("isCheckable") == QString("true"); - QKeySequence shortcut = QKeySequence(getChildContent("shortcut")); - QKeySequence defaultShortcut = QKeySequence(getChildContent("defaultShortcut")); - - KisAction *a = new KisAction(KisIconUtils::loadIcon(icon.toLatin1()), text); - a->setObjectName(name); - a->setWhatsThis(whatsthis); - a->setToolTip(toolTip); - a->setStatusTip(statusTip); - a->setIconText(iconText); - a->setDefaultShortcut(shortcut); - a->setShortcut(shortcut); //TODO: Make this configurable from custom settings - a->setCheckable(isCheckable); - - - // XXX: these checks may become important again after refactoring if (!actionCollection->action(name)) { actionCollection->addAction(name, a); } @@ -174,30 +139,7 @@ } - // TODO: check for colliding shortcuts - // - // Ultimately we want to have more than one KActionCollection, so we can - // have things like Ctrl+I be italics in the text editor widget, while not - // complaining about conflicts elsewhere. Right now, we use only one - // collection, and we don't make things like the text editor configurable, - // so duplicate shortcuts are handled mostly automatically by the shortcut - // editor. - // - // QMap existingShortcuts; - // foreach(QAction* action, actionCollection->actions()) { - // if(action->shortcut() == QKeySequence(0)) { - // continue; - // } - // if (existingShortcuts.contains(action->shortcut())) { - // dbgAction << QString("Actions %1 and %2 have the same shortcut: %3") \ - // .arg(action->text()) \ - // .arg(existingShortcuts[action->shortcut()]->text()) \ - // .arg(action->shortcut()); - // } - // else { - // existingShortcuts[action->shortcut()] = action; - // } - // } + }; @@ -501,9 +443,8 @@ // planned anyway. // WidgetAction + WindowAction + ApplicationAction leaves only GlobalAction excluded - KisShortcutsDialog dlg; - dlg.addCollection(d->actionCollection); - dlg.configure(); // Show the dialog. + + KisActionRegistry::instance()->configureShortcuts(d->actionCollection); // Now update the widget tooltips in the UI. diff --git a/krita/ui/tool/kis_tool.cc b/krita/ui/tool/kis_tool.cc --- a/krita/ui/tool/kis_tool.cc +++ b/krita/ui/tool/kis_tool.cc @@ -65,7 +65,7 @@ #include #include "kis_resources_snapshot.h" #include - +#include "kis_action_registry.h" struct KisTool::Private { @@ -104,15 +104,13 @@ KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { - QAction *toggleFgBg = new QAction(i18n("Swap Foreground and Background Color"), collection); - toggleFgBg->setShortcut(QKeySequence(Qt::Key_X)); + QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection, "Canvas"); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { - QAction *resetFgBg = new QAction(i18n("Reset Foreground and Background Color"), collection); - resetFgBg->setShortcut(QKeySequence(Qt::Key_D)); - collection->addAction("reset_fg_bg", resetFgBg); + QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection, "Canvas"); + collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); @@ -696,5 +694,3 @@ { return false; } - - diff --git a/krita/ui/tool/kis_tool_polyline_base.cpp b/krita/ui/tool/kis_tool_polyline_base.cpp --- a/krita/ui/tool/kis_tool_polyline_base.cpp +++ b/krita/ui/tool/kis_tool_polyline_base.cpp @@ -32,6 +32,8 @@ #include #include +#include "kis_action_registry.h" + #define SNAPPING_THRESHOLD 10 #define SNAPPING_HANDLE_RADIUS 8 #define PREVIEW_LINE_WIDTH 1 diff --git a/libs/widgetutils/kis_action_registry.h b/libs/widgetutils/kis_action_registry.h --- a/libs/widgetutils/kis_action_registry.h +++ b/libs/widgetutils/kis_action_registry.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "kritawidgetutils_export.h" @@ -68,6 +69,27 @@ /** + * Saves action in a category. Note that this grabs ownership of the action. + */ + void addAction(QString name, QAction *a, QString category = "Krita"); + + + /** + * Produces a new QAction based on the .action data files. + */ + QAction * makeQAction(QString name, QObject *parent, QString category = QString()); + + KActionCollection * getDefaultCollection(); + + /** + * Fills the standard QAction properties of an action. + * + * @return true if the action was loaded successfully. + */ + bool propertizeAction(QString name, QAction *a); + + + /** * @return list of actions with data available. */ QStringList allActions(); @@ -79,6 +101,11 @@ /** + * Run shortcuts dialog. + */ + void configureShortcuts(KActionCollection *ac); + + /** * Constructor. Please don't touch! */ KisActionRegistry(); diff --git a/libs/widgetutils/kis_action_registry.cpp b/libs/widgetutils/kis_action_registry.cpp --- a/libs/widgetutils/kis_action_registry.cpp +++ b/libs/widgetutils/kis_action_registry.cpp @@ -19,16 +19,17 @@ #include #include -#include -#include #include +#include #include -#include -#include +#include +#include +#include + #include "kis_debug.h" -#include -#include - +#include "KoResourcePaths.h" +#include "kis_icon_utils.h" +#include "kactioncollection.h" #include "kis_action_registry.h" @@ -54,6 +55,8 @@ { public: + Private(KisActionRegistry *_q) : q(_q) {}; + /** * We associate three pieces of information with each shortcut name. The * first piece of information is a QDomElement, containing the raw data from @@ -65,10 +68,15 @@ */ QHash actionInfoList; KSharedConfigPtr cfg{0}; - void loadActions(); + void loadActionFiles(); + void loadActionCollections(); actionInfoItem actionInfo(QString name) { return actionInfoList.value(name, emptyActionInfo); }; + + KisActionRegistry *q; + KActionCollection * defaultActionCollection; + QHash actionCollections; }; @@ -81,10 +89,15 @@ KisActionRegistry::KisActionRegistry() - : d(new KisActionRegistry::Private()) + : d(new KisActionRegistry::Private(this)) { - d->cfg = KSharedConfig::openConfig("kritashortcutsrc"); - d->loadActions(); + d->cfg = KSharedConfig::openConfig("krbitashortcutsrc"); + d->loadActionFiles(); + + // Should change to , then translate + d->defaultActionCollection = new KActionCollection(this, "Krita"); + d->actionCollections.insert("Krita", d->defaultActionCollection); + } // No this isn't the most efficient logic, but it's nice and readable. @@ -120,7 +133,152 @@ return d->actionInfo(name).xmlData; }; -void KisActionRegistry::Private::loadActions() +KActionCollection * KisActionRegistry::getDefaultCollection() +{ + return d->defaultActionCollection; +}; + +void KisActionRegistry::addAction(QString name, QAction *a, QString category) +{ + KActionCollection *ac; + if (d->actionCollections.contains(category)) { + ac = d->actionCollections.value(category); + } else { + ac = new KActionCollection(this, category); + d->actionCollections.insert(category, ac); + dbgAction << "Adding a new KActionCollection - " << category; + } + + if (!ac->action(name)) { + ac->addAction(name, a); + } + else { + dbgAction << "duplicate action" << name << a << "in collection" << ac->componentName(); + } + + // TODO: look into the loading/saving mechanism + ac->readSettings(); +}; + + + +QAction * KisActionRegistry::makeQAction(QString name, QObject *parent, QString category) +{ + + QAction * a = new QAction(parent); + if (!d->actionInfoList.contains(name)) { + dbgAction << "Warning: requested data for unknown action" << name; + return a; + } + + propertizeAction(name, a); + addAction(name, a, category); + + return a; +}; + + +void KisActionRegistry::configureShortcuts(KActionCollection *ac) +{ + + KisShortcutsDialog dlg; + dlg.addCollection(ac); + for (auto i = d->actionCollections.constBegin(); i != d->actionCollections.constEnd(); i++ ) { + dlg.addCollection(i.value(), i.key()); + } + + dlg.configure(); // Show the dialog. +} + + + +bool KisActionRegistry::propertizeAction(QString name, QAction * a) +{ + + QStringList actionNames = allActions(); + QDomElement actionXml = getActionXml(name); + + + // Convenience macros to extract text of a child node. + auto getChildContent = [=](QString node){return actionXml.firstChildElement(node).text();}; + // i18n requires converting format from QString. + auto getChildContent_i18n = [=](QString node) { + if (getChildContent(node).isEmpty()) { + dbgAction << "Found empty string to translate for property" << node; + return QString(); + } + return i18n(getChildContent(node).toUtf8().constData()); + }; + + + QString icon = getChildContent("icon"); + QString text = getChildContent("text"); + + // Note: these fields in the .action definitions are marked for translation. + QString whatsthis = getChildContent_i18n("whatsThis"); + QString toolTip = getChildContent_i18n("toolTip"); + QString statusTip = getChildContent_i18n("statusTip"); + QString iconText = getChildContent_i18n("iconText"); + + bool isCheckable = getChildContent("isCheckable") == QString("true"); + QKeySequence shortcut = QKeySequence(getChildContent("shortcut")); + QKeySequence defaultShortcut = QKeySequence(getChildContent("defaultShortcut")); + + + a->setObjectName(name); // This is helpful!! + a->setIcon(KisIconUtils::loadIcon(icon.toLatin1())); + a->setText(text); + a->setObjectName(name); + a->setWhatsThis(whatsthis); + a->setToolTip(toolTip); + a->setStatusTip(statusTip); + a->setIconText(iconText); + a->setShortcut(shortcut); + a->setCheckable(isCheckable); + + + // XXX: this totally duplicates KisAction::setDefaultShortcut + QList listifiedShortcut; + listifiedShortcut.append(shortcut); + setProperty("defaultShortcuts", qVariantFromValue(listifiedShortcut)); + + + + + // TODO: check for colliding shortcuts, or make sure it happens smartly inside kactioncollection + // + // Ultimately we want to have more than one KActionCollection, so we can + // have things like Ctrl+I be italics in the text editor widget, while not + // complaining about conflicts elsewhere. Right now, we use only one + // collection, and we don't make things like the text editor configurable, + // so duplicate shortcuts are handled mostly automatically by the shortcut + // editor. + // + // QMap existingShortcuts; + // foreach(QAction* action, actionCollection->actions()) { + // if(action->shortcut() == QKeySequence(0)) { + // continue; + // } + // if (existingShortcuts.contains(action->shortcut())) { + // dbgAction << QString("Actions %1 and %2 have the same shortcut: %3") \ + // .arg(action->text()) \ + // .arg(existingShortcuts[action->shortcut()]->text()) \ + // .arg(action->shortcut()); + // } + // else { + // existingShortcuts[action->shortcut()] = action; + // } + // } + + + return true; +} + + + + + +void KisActionRegistry::Private::loadActionFiles() { KoResourcePaths::addResourceType("kis_actions", "data", "krita/actions"); diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog.h b/libs/widgetutils/xmlgui/KisShortcutsDialog.h --- a/libs/widgetutils/xmlgui/KisShortcutsDialog.h +++ b/libs/widgetutils/xmlgui/KisShortcutsDialog.h @@ -44,8 +44,6 @@ // dialogs/KisShortcutsDialog_p.h <- kshortcutsdialog_p.h, kshortcutseditor_p.h // forms/KisShortcutsDialog.ui <- kshortcutsdialog.ui // -// These files were forked from KF5 WidgetViews -// // // Changes that have been done to the files: // * Adapt of includes