[aseprite] 183/250: Add list of system fonts in PasteTextCommand dialog (on Windows)

Tobias Hansen thansen at moszumanska.debian.org
Sun Dec 20 15:27:28 UTC 2015


This is an automated email from the git hooks/post-receive script.

thansen pushed a commit to branch master
in repository aseprite.

commit 9f8200ea3e5700a33a372f4d51f9a59fbe1dd5e1
Author: David Capello <davidcapello at gmail.com>
Date:   Thu Oct 15 18:16:16 2015 -0300

    Add list of system fonts in PasteTextCommand dialog (on Windows)
---
 data/widgets/font_popup.xml         |  12 +++
 data/widgets/paste_text.xml         |   2 +-
 src/app/CMakeLists.txt              |   2 +
 src/app/commands/cmd_paste_text.cpp | 158 +++++++++--------------------
 src/app/ui/font_popup.cpp           | 192 ++++++++++++++++++++++++++++++++++++
 src/app/ui/font_popup.h             |  50 ++++++++++
 src/app/util/freetype_utils.cpp     | 152 ++++++++++++++++++++++++++++
 src/app/util/freetype_utils.h       |  28 ++++++
 8 files changed, 485 insertions(+), 111 deletions(-)

diff --git a/data/widgets/font_popup.xml b/data/widgets/font_popup.xml
new file mode 100644
index 0000000..b890db8
--- /dev/null
+++ b/data/widgets/font_popup.xml
@@ -0,0 +1,12 @@
+<!-- ASEPRITE -->
+<!-- Copyright (C) 2015 by David Capello -->
+<gui>
+  <vbox id="font_popup">
+    <label text="Available Fonts:" />
+    <view id="view" expansive="true" />
+    <hbox>
+      <boxfiller />
+      <button id="load_font" text="Load" width="80" />
+    </hbox>
+  </vbox>
+</gui>
diff --git a/data/widgets/paste_text.xml b/data/widgets/paste_text.xml
index f76bdb6..bb0eab0 100644
--- a/data/widgets/paste_text.xml
+++ b/data/widgets/paste_text.xml
@@ -7,7 +7,7 @@
     <entry expansive="true" maxsize="256" id="user_text" magnet="true" cell_align="horizontal" />
 
     <label text="Font:" />
-    <button minwidth="60" id="font_face" text="Select Font" cell_align="horizontal" />
+    <dropdownbutton minwidth="60" id="font_face" text="Select Font" cell_align="horizontal" />
 
     <label text="Font Size:" />
     <entry id="font_size" maxsize="4" text="32" cell_align="horizontal" />
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 4319e57..30e1b09 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -353,6 +353,7 @@ add_library(app-lib
   ui/editor/zooming_state.cpp
   ui/file_list.cpp
   ui/file_selector.cpp
+  ui/font_popup.cpp
   ui/frame_tag_window.cpp
   ui/hex_color_entry.cpp
   ui/home_view.cpp
@@ -393,6 +394,7 @@ add_library(app-lib
   util/create_cel_copy.cpp
   util/expand_cel_canvas.cpp
   util/filetoks.cpp
+  util/freetype_utils.cpp
   util/msk_file.cpp
   util/new_image_from_mask.cpp
   util/pic_file.cpp
diff --git a/src/app/commands/cmd_paste_text.cpp b/src/app/commands/cmd_paste_text.cpp
index 29fcbb3..2f51569 100644
--- a/src/app/commands/cmd_paste_text.cpp
+++ b/src/app/commands/cmd_paste_text.cpp
@@ -11,22 +11,19 @@
 
 #include "app/app.h"
 #include "app/commands/command.h"
+#include "app/console.h"
 #include "app/context.h"
 #include "app/file_selector.h"
 #include "app/pref/preferences.h"
+#include "app/ui/drop_down_button.h"
+#include "app/ui/font_popup.h"
 #include "app/util/clipboard.h"
+#include "app/util/freetype_utils.h"
 #include "base/bind.h"
 #include "base/path.h"
 #include "base/string.h"
-#include "doc/blend_funcs.h"
-#include "doc/blend_internals.h"
-#include "doc/color.h"
+#include "base/unique_ptr.h"
 #include "doc/image.h"
-#include "doc/primitives.h"
-
-#include "freetype/ftglyph.h"
-#include "ft2build.h"
-#include FT_FREETYPE_H
 
 #include "paste_text.xml.h"
 
@@ -34,37 +31,6 @@ namespace app {
 
 static std::string last_text_used;
 
-template<typename Iterator, typename Func>
-static void for_each_glyph(FT_Face face, Iterator first, Iterator end, Func callback)
-{
-  bool use_kerning = (FT_HAS_KERNING(face) ? true: false);
-
-  // Calculate size
-  FT_UInt prev_glyph = 0;
-  int x = 0;
-  for (; first != end; ++first) {
-    FT_UInt glyph_index = FT_Get_Char_Index(face, *first);
-
-    if (use_kerning && prev_glyph && glyph_index) {
-      FT_Vector kerning;
-      FT_Get_Kerning(face, prev_glyph, glyph_index,
-                     FT_KERNING_DEFAULT, &kerning);
-      x += kerning.x >> 6;
-    }
-
-    FT_Error err = FT_Load_Glyph(
-      face, glyph_index,
-      FT_LOAD_RENDER | FT_LOAD_NO_BITMAP);
-
-    if (!err) {
-      callback(x, face->glyph);
-      x += face->glyph->advance.x >> 6;
-    }
-
-    prev_glyph = glyph_index;
-  }
-}
-
 class PasteTextCommand : public Command {
 public:
   PasteTextCommand();
@@ -97,7 +63,8 @@ public:
       updateFontFaceButton();
 
     fontSize()->setTextf("%d", size);
-    fontFace()->Click.connect(Bind<void>(&PasteTextWindow::onSelectFont, this));
+    fontFace()->Click.connect(Bind<void>(&PasteTextWindow::onSelectFontFile, this));
+    fontFace()->DropDownClick.connect(Bind<void>(&PasteTextWindow::onSelectSystemFont, this));
     fontColor()->setColor(color);
   }
 
@@ -113,11 +80,12 @@ public:
 
 private:
   void updateFontFaceButton() {
-    fontFace()->setTextf("Select Font: %s",
-                         base::get_file_name(m_face).c_str());
+    fontFace()->mainButton()
+      ->setTextf("Select Font: %s",
+                 base::get_file_title(m_face).c_str());
   }
 
-  void onSelectFont() {
+  void onSelectFontFile() {
     std::string face = show_file_selector(
       "Select a TrueType Font",
       m_face,
@@ -126,13 +94,41 @@ private:
       nullptr);
 
     if (!face.empty()) {
-      m_face = face;
-      ok()->setEnabled(true);
-      updateFontFaceButton();
+      setFontFace(face);
+    }
+  }
+
+  void setFontFace(const std::string& face) {
+    m_face = face;
+    ok()->setEnabled(true);
+    updateFontFaceButton();
+  }
+
+  void onSelectSystemFont() {
+    if (!m_fontPopup) {
+      try {
+        m_fontPopup.reset(new FontPopup());
+        m_fontPopup->Load.connect(&PasteTextWindow::setFontFace, this);
+      }
+      catch (const std::exception& ex) {
+        Console::showException(ex);
+        return;
+      }
+    }
+
+    if (!m_fontPopup->isVisible()) {
+      gfx::Rect bounds = fontFace()->getBounds();
+      m_fontPopup->showPopup(
+        gfx::Rect(bounds.x, bounds.y+bounds.h,
+                  ui::display_w()/2, ui::display_h()/2));
+    }
+    else {
+      m_fontPopup->closeWindow(NULL);
     }
   }
 
   std::string m_face;
+  base::UniquePtr<FontPopup> m_fontPopup;
 };
 
 void PasteTextCommand::onExecute(Context* ctx)
@@ -155,17 +151,7 @@ void PasteTextCommand::onExecute(Context* ctx)
   pref.textTool.fontFace(faceName);
   pref.textTool.fontSize(size);
 
-  FT_Library ft;
-  FT_Init_FreeType(&ft);
-
-  FT_Open_Args args;
-  memset(&args, 0, sizeof(args));
-  args.flags = FT_OPEN_PATHNAME;
-  args.pathname = (FT_String*)faceName.c_str();
-
-  FT_Face face;
-  FT_Error err = FT_Open_Face(ft, &args, 0, &face);
-  if (!err) {
+  try {
     std::string text = window.userText()->getText();
     app::Color appColor = window.fontColor()->getColor();
     doc::color_t color = doc::rgba(appColor.getRed(),
@@ -173,65 +159,17 @@ void PasteTextCommand::onExecute(Context* ctx)
                                    appColor.getBlue(),
                                    appColor.getAlpha());
 
-    // Set font size
-    FT_Set_Pixel_Sizes(face, size, size);
-
-    // Calculate text size
-    base::utf8_iterator begin(text.begin()), end(text.end());
-    gfx::Rect bounds(0, 0, 0, 0);
-    for_each_glyph(
-      face, begin, end,
-      [&bounds](int x, FT_GlyphSlot glyph) {
-        bounds |= gfx::Rect(x + glyph->bitmap_left,
-                            -glyph->bitmap_top,
-                            (int)glyph->bitmap.width,
-                            (int)glyph->bitmap.rows);
-      });
-
-    // Render the image and copy it to the clipboard
-    if (!bounds.isEmpty()) {
-      doc::Image* image = doc::Image::create(IMAGE_RGB, bounds.w, bounds.h);
-      doc::clear_image(image, 0);
-
-      for_each_glyph(
-        face, begin, end,
-        [&bounds, &image, color](int x, FT_GlyphSlot glyph) {
-          int t, yimg = - bounds.y - glyph->bitmap_top;
-          for (int v=0; v<(int)glyph->bitmap.rows; ++v, ++yimg) {
-            const uint8_t* p = glyph->bitmap.buffer + v*glyph->bitmap.pitch;
-            int ximg = x - bounds.x + glyph->bitmap_left;
-            for (int u=0; u<(int)glyph->bitmap.width; ++u, ++p, ++ximg) {
-              int alpha = *p;
-              doc::put_pixel(
-                image, ximg, yimg,
-                doc::rgba_blender_normal(
-                  doc::get_pixel(image, ximg, yimg),
-                  doc::rgba(doc::rgba_getr(color),
-                            doc::rgba_getg(color),
-                            doc::rgba_getb(color),
-                            MUL_UN8(doc::rgba_geta(color), alpha, t)), 255));
-            }
-          }
-        });
-
+    doc::Image* image = render_text(faceName, size, text, color);
+    if (image) {
       clipboard::copy_image(image, nullptr, nullptr);
       clipboard::paste();
     }
-    else {
-      ui::Alert::show(PACKAGE
-                      "<<There is no text"
-                      "||&OK");
-    }
-
-    FT_Done_Face(face);
   }
-  else {
+  catch (const std::exception& ex) {
     ui::Alert::show(PACKAGE
-                    "<<Error loading font face"
-                    "||&OK");
+                    "<<%s"
+                    "||&OK", ex.what());
   }
-
-  FT_Done_FreeType(ft);
 }
 
 Command* CommandFactory::createPasteTextCommand()
diff --git a/src/app/ui/font_popup.cpp b/src/app/ui/font_popup.cpp
new file mode 100644
index 0000000..cabafa5
--- /dev/null
+++ b/src/app/ui/font_popup.cpp
@@ -0,0 +1,192 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/ui/font_popup.h"
+
+#include "app/commands/cmd_set_palette.h"
+#include "app/commands/commands.h"
+#include "app/console.h"
+#include "app/ui/skin/skin_theme.h"
+#include "app/ui_context.h"
+#include "app/util/freetype_utils.h"
+#include "base/bind.h"
+#include "base/fs.h"
+#include "base/path.h"
+#include "base/string.h"
+#include "base/unique_ptr.h"
+#include "doc/conversion_she.h"
+#include "doc/image.h"
+#include "she/surface.h"
+#include "she/system.h"
+#include "ui/box.h"
+#include "ui/button.h"
+#include "ui/preferred_size_event.h"
+#include "ui/theme.h"
+#include "ui/view.h"
+
+#include "font_popup.xml.h"
+
+#ifdef _WIN32
+#include <shlobj.h>
+#include <windows.h>
+#endif
+
+namespace app {
+
+using namespace ui;
+
+class FontItem : public ListItem {
+public:
+  FontItem(std::string& fontsDir, const std::string& fn)
+    : ListItem(fn)
+    , m_fontsDir(fontsDir) {
+  }
+
+private:
+  void onPaint(PaintEvent& ev) override {
+    if (!m_image) {
+      ListItem::onPaint(ev);
+    }
+    else {
+      app::skin::SkinTheme* theme = app::skin::SkinTheme::instance();
+      Graphics* g = ev.getGraphics();
+      she::Surface* sur = she::instance()->createRgbaSurface(m_image->width(),
+                                                             m_image->height());
+
+      convert_image_to_surface(
+        m_image.get(), nullptr, sur,
+        0, 0, 0, 0, m_image->width(), m_image->height());
+
+      gfx::Color bg;
+      if (isSelected())
+        bg = theme->colors.listitemSelectedFace();
+      else
+        bg = theme->colors.listitemNormalFace();
+
+      g->fillRect(bg, getClientBounds());
+      g->drawRgbaSurface(sur, 0, 0);
+    }
+  }
+
+  void onPreferredSize(PreferredSizeEvent& ev) override {
+    if (m_image)
+      ev.setPreferredSize(m_image->width(),
+                          m_image->height());
+    else
+      ListItem::onPreferredSize(ev);
+  }
+
+  void onSelect() override {
+    if (!getParent())
+      return;
+
+    ASSERT(getParent());
+    ASSERT(getParent()->type() == kListBoxWidget);
+
+    std::string filename = base::join_path(m_fontsDir, getText());
+    app::skin::SkinTheme* theme = app::skin::SkinTheme::instance();
+    gfx::Color color = theme->colors.text();
+
+    try {
+      m_image.reset(
+        render_text(
+          filename, 16,
+          getText(),
+          doc::rgba(gfx::getr(color),
+                    gfx::getg(color),
+                    gfx::getb(color),
+                    gfx::geta(color))));
+
+      getParent()->layout();
+      invalidate();
+    }
+    catch (const std::exception& ex) {
+      Console::showException(ex);
+    }
+  }
+
+private:
+  std::string& m_fontsDir;
+  base::UniquePtr<doc::Image> m_image;
+};
+
+FontPopup::FontPopup()
+  : PopupWindow("Fonts", kCloseOnClickInOtherWindow)
+  , m_popup(new gen::FontPopup())
+{
+  setAutoRemap(false);
+  setBorder(gfx::Border(4*guiscale()));
+
+  addChild(m_popup);
+
+  m_popup->loadFont()->Click.connect(Bind<void>(&FontPopup::onLoadFont, this));
+  m_listBox.Change.connect(Bind<void>(&FontPopup::onChangeFont, this));
+  m_listBox.DoubleClickItem.connect(Bind<void>(&FontPopup::onLoadFont, this));
+
+  m_popup->view()->attachToView(&m_listBox);
+
+#if _WIN32
+  {
+    std::vector<wchar_t> buf(MAX_PATH);
+    HRESULT hr = SHGetFolderPath(NULL, CSIDL_FONTS, NULL,
+                                 SHGFP_TYPE_DEFAULT, &buf[0]);
+    if (hr == S_OK) {
+      m_fontsDir = base::to_utf8(&buf[0]);
+    }
+  }
+#endif
+
+  if (!m_fontsDir.empty()) {
+    auto& files = base::list_files(m_fontsDir);
+    std::sort(files.begin(), files.end());
+    for (auto& file : files) {
+      if (base::string_to_lower(base::get_file_extension(file)) == "ttf")
+        m_listBox.addChild(new FontItem(m_fontsDir, file));
+    }
+  }
+
+  if (m_listBox.getChildren().empty())
+    m_listBox.addChild(new ListItem("No system fonts were found"));
+}
+
+void FontPopup::showPopup(const gfx::Rect& bounds)
+{
+  m_popup->loadFont()->setEnabled(false);
+  m_listBox.selectChild(NULL);
+
+  moveWindow(bounds);
+
+  // Setup the hot-region
+  setHotRegion(gfx::Region(gfx::Rect(bounds).enlarge(32 * guiscale())));
+
+  openWindow();
+}
+
+void FontPopup::onChangeFont()
+{
+  m_popup->loadFont()->setEnabled(true);
+}
+
+void FontPopup::onLoadFont()
+{
+  Widget* child = m_listBox.getSelectedChild();
+  if (!child)
+    return;
+
+  std::string filename = base::join_path(m_fontsDir,
+                                         child->getText());
+  if (base::is_file(filename))
+    Load(filename);             // Fire Load signal
+
+  closeWindow(nullptr);
+}
+
+} // namespace app
diff --git a/src/app/ui/font_popup.h b/src/app/ui/font_popup.h
new file mode 100644
index 0000000..3b710e5
--- /dev/null
+++ b/src/app/ui/font_popup.h
@@ -0,0 +1,50 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifndef APP_UI_FONT_POPUP_H_INCLUDED
+#define APP_UI_FONT_POPUP_H_INCLUDED
+#pragma once
+
+#include "ui/listbox.h"
+#include "ui/popup_window.h"
+
+namespace ui {
+  class Button;
+  class View;
+}
+
+namespace app {
+
+  namespace gen {
+    class FontPopup;
+  }
+
+  class FontPopup : public ui::PopupWindow {
+  public:
+    FontPopup();
+
+    void showPopup(const gfx::Rect& bounds);
+
+    const std::string& fontsDir() const {
+      return m_fontsDir;
+    }
+
+    Signal1<void, const std::string&> Load;
+
+  protected:
+    void onChangeFont();
+    void onLoadFont();
+
+  private:
+    gen::FontPopup* m_popup;
+    ui::ListBox m_listBox;
+    std::string m_fontsDir;
+  };
+
+} // namespace app
+
+#endif
diff --git a/src/app/util/freetype_utils.cpp b/src/app/util/freetype_utils.cpp
new file mode 100644
index 0000000..dc4b678
--- /dev/null
+++ b/src/app/util/freetype_utils.cpp
@@ -0,0 +1,152 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/util/freetype_utils.h"
+
+#include "base/string.h"
+#include "base/unique_ptr.h"
+#include "doc/blend_funcs.h"
+#include "doc/blend_internals.h"
+#include "doc/color.h"
+#include "doc/image.h"
+#include "doc/primitives.h"
+
+#include "freetype/ftglyph.h"
+#include "ft2build.h"
+#include FT_FREETYPE_H
+
+namespace app {
+
+template<typename Iterator, typename Func>
+static void for_each_glyph(FT_Face face, Iterator first, Iterator end, Func callback)
+{
+  bool use_kerning = (FT_HAS_KERNING(face) ? true: false);
+
+  // Calculate size
+  FT_UInt prev_glyph = 0;
+  int x = 0;
+  for (; first != end; ++first) {
+    FT_UInt glyph_index = FT_Get_Char_Index(face, *first);
+
+    if (use_kerning && prev_glyph && glyph_index) {
+      FT_Vector kerning;
+      FT_Get_Kerning(face, prev_glyph, glyph_index,
+                     FT_KERNING_DEFAULT, &kerning);
+      x += kerning.x >> 6;
+    }
+
+    FT_Error err = FT_Load_Glyph(
+      face, glyph_index,
+      FT_LOAD_RENDER | FT_LOAD_NO_BITMAP);
+
+    if (!err) {
+      callback(x, face->glyph);
+      x += face->glyph->advance.x >> 6;
+    }
+
+    prev_glyph = glyph_index;
+  }
+}
+
+class ScopedFTLib {
+public:
+  ScopedFTLib(FT_Library& ft) : m_ft(ft) {
+    FT_Init_FreeType(&m_ft);
+  }
+  ~ScopedFTLib() {
+    FT_Done_FreeType(m_ft);
+  }
+private:
+  FT_Library& m_ft;
+};
+
+class ScopedFTFace {
+public:
+  ScopedFTFace(FT_Face& face) : m_face(face) {
+  }
+  ~ScopedFTFace() {
+    FT_Done_Face(m_face);
+   }
+private:
+  FT_Face& m_face;
+};
+
+doc::Image* render_text(const std::string& fontfile, int fontsize,
+                        const std::string& text,
+                        doc::color_t color)
+{
+  base::UniquePtr<doc::Image> image(nullptr);
+  FT_Library ft;
+  ScopedFTLib init_and_done_ft(ft);
+
+  FT_Open_Args args;
+  memset(&args, 0, sizeof(args));
+  args.flags = FT_OPEN_PATHNAME;
+  args.pathname = (FT_String*)fontfile.c_str();
+
+  FT_Face face;
+  FT_Error err = FT_Open_Face(ft, &args, 0, &face);
+  if (!err) {
+    ScopedFTFace done_face(face);
+
+    // Set font size
+    FT_Set_Pixel_Sizes(face, fontsize, fontsize);
+
+    // Calculate text size
+    base::utf8_const_iterator begin(text.begin()), end(text.end());
+    gfx::Rect bounds(0, 0, 0, 0);
+    for_each_glyph(
+      face, begin, end,
+      [&bounds](int x, FT_GlyphSlot glyph) {
+        bounds |= gfx::Rect(x + glyph->bitmap_left,
+                            -glyph->bitmap_top,
+                            (int)glyph->bitmap.width,
+                            (int)glyph->bitmap.rows);
+      });
+
+    // Render the image and copy it to the clipboard
+    if (!bounds.isEmpty()) {
+      image.reset(doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
+      doc::clear_image(image, 0);
+
+      for_each_glyph(
+        face, begin, end,
+        [&bounds, &image, color](int x, FT_GlyphSlot glyph) {
+          int t, yimg = - bounds.y - glyph->bitmap_top;
+          for (int v=0; v<(int)glyph->bitmap.rows; ++v, ++yimg) {
+            const uint8_t* p = glyph->bitmap.buffer + v*glyph->bitmap.pitch;
+            int ximg = x - bounds.x + glyph->bitmap_left;
+            for (int u=0; u<(int)glyph->bitmap.width; ++u, ++p, ++ximg) {
+              int alpha = *p;
+              doc::put_pixel(
+                image, ximg, yimg,
+                doc::rgba_blender_normal(
+                  doc::get_pixel(image, ximg, yimg),
+                  doc::rgba(doc::rgba_getr(color),
+                            doc::rgba_getg(color),
+                            doc::rgba_getb(color),
+                            MUL_UN8(doc::rgba_geta(color), alpha, t)), 255));
+            }
+          }
+        });
+    }
+    else {
+      throw std::runtime_error("There is no text");
+    }
+  }
+  else {
+    throw std::runtime_error("Error loading font face");
+  }
+
+  return (image ? image.release(): nullptr);
+}
+
+} // namespace app
diff --git a/src/app/util/freetype_utils.h b/src/app/util/freetype_utils.h
new file mode 100644
index 0000000..d2589ae
--- /dev/null
+++ b/src/app/util/freetype_utils.h
@@ -0,0 +1,28 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifndef APP_UTIL_FREETYPE_UTILS_H_INCLUDED
+#define APP_UTIL_FREETYPE_UTILS_H_INCLUDED
+#pragma once
+
+#include "doc/color.h"
+
+#include <string>
+
+namespace doc {
+  class Image;
+}
+
+namespace app {
+
+  doc::Image* render_text(const std::string& fontfile, int fontsize,
+                          const std::string& text,
+                          doc::color_t color);
+
+} // namespace app
+
+#endif

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git



More information about the Pkg-games-commits mailing list