Mostly finished button selection code, in ComposeWindow.
@ -58,8 +58,8 @@ ComposeWindow extends JFrame {
|
||||
private AttachmentsComponent
|
||||
attachmentsDisplay;
|
||||
|
||||
private JTabbedPane
|
||||
tabs;
|
||||
private JTabbedPane
|
||||
tabs;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
@ -178,15 +178,15 @@ ComposeWindow extends JFrame {
|
||||
attachmentsDisplay = new AttachmentsComponent(this);
|
||||
newComposition();
|
||||
|
||||
tabs = new JTabbedPane();
|
||||
contentsDisplay.setPreferredSize(sz);
|
||||
tabs.addTab("Text", contentsDisplay);
|
||||
tabs.addTab("Media", attachmentsDisplay);
|
||||
|
||||
setBackground((Color)UIManager.get("TabbedPane.tabAreaBackground"));
|
||||
setContentPane(tabs);
|
||||
pack();
|
||||
setIconImage(primaire.getProgramIcon());
|
||||
tabs = new JTabbedPane();
|
||||
contentsDisplay.setPreferredSize(sz);
|
||||
tabs.addTab("Text", contentsDisplay);
|
||||
tabs.addTab("Media", attachmentsDisplay);
|
||||
|
||||
setBackground((Color)UIManager.get("TabbedPane.tabAreaBackground"));
|
||||
setContentPane(tabs);
|
||||
pack();
|
||||
setIconImage(primaire.getProgramIcon());
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
@ -334,7 +334,7 @@ implements ActionListener, CaretListener, KeyListener {
|
||||
{
|
||||
if (eA.getSource() == showAttachmentsPage)
|
||||
//primaire.showAttachmentsPage();
|
||||
;
|
||||
;
|
||||
|
||||
if (eA.getSource() == submit)
|
||||
primaire.submit();
|
||||
@ -346,14 +346,14 @@ implements ActionListener, CaretListener, KeyListener {
|
||||
public void
|
||||
keyPressed(KeyEvent eK)
|
||||
{
|
||||
boolean esc = eK.getKeyCode() == KeyEvent.VK_ESCAPE;
|
||||
boolean esc = eK.getKeyCode() == KeyEvent.VK_ESCAPE;
|
||||
if (esc)
|
||||
{
|
||||
Container fcr = getFocusCycleRootAncestor();
|
||||
fcr.getFocusTraversalPolicy()
|
||||
.getComponentAfter(fcr, text)
|
||||
.requestFocusInWindow();
|
||||
}
|
||||
{
|
||||
Container fcr = getFocusCycleRootAncestor();
|
||||
fcr.getFocusTraversalPolicy()
|
||||
.getComponentAfter(fcr, text)
|
||||
.requestFocusInWindow();
|
||||
}
|
||||
else updateTextLength();
|
||||
}
|
||||
|
||||
@ -429,7 +429,7 @@ implements ActionListener, CaretListener, KeyListener {
|
||||
submit = new JButton("Submit");
|
||||
submit.addActionListener(this);
|
||||
|
||||
showAttachmentsPage = new JButton("Media");
|
||||
showAttachmentsPage = new JButton("Media");
|
||||
showAttachmentsPage.addActionListener(this);
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
@ -468,17 +468,23 @@ implements ActionListener {
|
||||
|
||||
// - -%- -
|
||||
|
||||
private List<Attachment>
|
||||
working;
|
||||
private List<Attachment>
|
||||
working;
|
||||
|
||||
private JPanel
|
||||
selections;
|
||||
private JPanel
|
||||
selections;
|
||||
|
||||
private ButtonGroup
|
||||
selectionsGroup;
|
||||
private ButtonGroup
|
||||
selectionsGroup;
|
||||
|
||||
private JButton
|
||||
add;
|
||||
private JToggleButton
|
||||
attachment1,
|
||||
attachment2,
|
||||
attachment3,
|
||||
attachment4;
|
||||
|
||||
private JButton
|
||||
add;
|
||||
|
||||
private JButton
|
||||
delete,
|
||||
@ -492,54 +498,81 @@ implements ActionListener {
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
private void
|
||||
updateButtons()
|
||||
{
|
||||
selections.removeAll();
|
||||
private void
|
||||
updateButtons()
|
||||
{
|
||||
Dimension sz = add.getPreferredSize();
|
||||
|
||||
Dimension sz = add.getPreferredSize();
|
||||
selections.removeAll();
|
||||
if (working.size() > 0) selections.add(attachment1);
|
||||
if (working.size() > 1) selections.add(attachment2);
|
||||
if (working.size() > 2) selections.add(attachment3);
|
||||
if (working.size() > 3) selections.add(attachment4);
|
||||
if (working.size() < 4) selections.add(add);
|
||||
|
||||
int i = 1;
|
||||
for (Attachment attachment: working)
|
||||
{
|
||||
JToggleButton button = new JToggleButton();
|
||||
button.setPreferredSize(sz);
|
||||
button.setMargin(add.getMargin());
|
||||
button.setText(Integer.toString(i++));
|
||||
selections.add(button);
|
||||
}
|
||||
if (working.size() < 4) selections.add(add);
|
||||
if (working.size() > 3) attachment4.doClick();
|
||||
else if (working.size() > 2) attachment3.doClick();
|
||||
else if (working.size() > 1) attachment2.doClick();
|
||||
else if (working.size() > 0) attachment1.doClick();
|
||||
else selectionsGroup.clearSelection();
|
||||
|
||||
int bw = sz.width;
|
||||
int hgap = 4;
|
||||
int count = Math.min(1 + working.size(), 4);
|
||||
int w = count * bw + (count - 1) * hgap;
|
||||
int h = bw;
|
||||
selections.setPreferredSize(new Dimension(w, h));
|
||||
selections.setMaximumSize(new Dimension(w, h));
|
||||
}
|
||||
int bw = sz.width;
|
||||
int hgap = 4;
|
||||
int count = selections.getComponents().length;
|
||||
int w = count * bw + (count - 1) * hgap;
|
||||
int h = bw;
|
||||
selections.setPreferredSize(new Dimension(w, h));
|
||||
selections.setMaximumSize(new Dimension(w, h));
|
||||
|
||||
selections.revalidate();
|
||||
}
|
||||
|
||||
public void
|
||||
actionPerformed(ActionEvent eA)
|
||||
{
|
||||
Object src = eA.getSource();
|
||||
|
||||
if (false)
|
||||
{
|
||||
// Clicked on filled attachment button.
|
||||
}
|
||||
|
||||
if (src == add)
|
||||
{
|
||||
// Invoke file picker. Try to get file.
|
||||
// Try to upload file. So on.
|
||||
// Then add to working.
|
||||
working.add(new Attachment());
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
if (src == delete)
|
||||
if (false)
|
||||
{
|
||||
// Clicked on filled attachment button.
|
||||
}
|
||||
|
||||
if (src == add)
|
||||
{
|
||||
// Invoke file picker. Try to get file.
|
||||
// Try to upload file. So on.
|
||||
// Then add to working.
|
||||
working.add(new Attachment());
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
if (src == delete)
|
||||
{
|
||||
Object sm = selectionsGroup.getSelection();
|
||||
if (sm == attachment1.getModel())
|
||||
{
|
||||
assert working.size() > 0;
|
||||
working.remove(0);
|
||||
updateButtons();
|
||||
}
|
||||
if (sm == attachment2.getModel())
|
||||
{
|
||||
assert working.size() > 1;
|
||||
working.remove(1);
|
||||
updateButtons();
|
||||
}
|
||||
if (sm == attachment3.getModel())
|
||||
{
|
||||
assert working.size() > 2;
|
||||
working.remove(2);
|
||||
updateButtons();
|
||||
}
|
||||
if (sm == attachment4.getModel())
|
||||
{
|
||||
assert working.size() > 3;
|
||||
working.remove(3);
|
||||
updateButtons();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -558,58 +591,71 @@ implements ActionListener {
|
||||
Border b1 = BorderFactory.createEmptyBorder(8, 8, 8, 8);
|
||||
Border b2 = BorderFactory.createEmptyBorder(4, 4, 4, 4);
|
||||
Border b3 = BorderFactory.createLineBorder(Color.GRAY);
|
||||
Border b4 = BorderFactory.createEmptyBorder(4, 8, 8, 8);
|
||||
Border b5 = BorderFactory.createEtchedBorder();
|
||||
Border b4 = BorderFactory.createEmptyBorder(4, 8, 8, 8);
|
||||
Border b5 = BorderFactory.createEtchedBorder();
|
||||
Border bc1 = BorderFactory.createCompoundBorder(b3, b2);
|
||||
Border bc2 = BorderFactory.createCompoundBorder(b4, b2);
|
||||
Border bc2 = BorderFactory.createCompoundBorder(b4, b2);
|
||||
|
||||
add = new JButton("+");
|
||||
add.setPreferredSize(new Dimension(32, 32));
|
||||
add.setMargin(new Insets(0, 0, 0, 0));
|
||||
add.addActionListener(this);
|
||||
add = new JButton("+");
|
||||
add.setPreferredSize(new Dimension(32, 32));
|
||||
add.setMargin(new Insets(0, 0, 0, 0));
|
||||
add.addActionListener(this);
|
||||
attachment1 = new JToggleButton("1");
|
||||
attachment2 = new JToggleButton("2");
|
||||
attachment3 = new JToggleButton("3");
|
||||
attachment4 = new JToggleButton("4");
|
||||
attachment1.setMargin(add.getMargin());
|
||||
attachment2.setMargin(add.getMargin());
|
||||
attachment3.setMargin(add.getMargin());
|
||||
attachment4.setMargin(add.getMargin());
|
||||
|
||||
selections = new JPanel();
|
||||
selections.setOpaque(false);
|
||||
selections.setLayout(new GridLayout(1, 0, 4, 0));
|
||||
|
||||
working = new ArrayList<Attachment>();
|
||||
updateButtons();
|
||||
selections = new JPanel();
|
||||
selections.setOpaque(false);
|
||||
selections.setLayout(new GridLayout(1, 0, 4, 0));
|
||||
working = new ArrayList<Attachment>();
|
||||
selectionsGroup = new ButtonGroup();
|
||||
selectionsGroup.add(attachment1);
|
||||
selectionsGroup.add(attachment2);
|
||||
selectionsGroup.add(attachment3);
|
||||
selectionsGroup.add(attachment4);
|
||||
// Have to add selection listener to button group
|
||||
updateButtons();
|
||||
|
||||
JButton del = new JButton("D");
|
||||
JButton rev = new JButton("R");
|
||||
JButton ml = new JButton("←");
|
||||
JButton mr = new JButton("→");
|
||||
del.setMargin(new Insets(0, 0, 0, 0));
|
||||
//ml.setMargin(new Insets(0, 0, 0, 0));
|
||||
//mr.setMargin(new Insets(0, 0, 0, 0));
|
||||
rev.setMargin(new Insets(0, 0, 0, 0));
|
||||
JButton del = new JButton("D");
|
||||
JButton rev = new JButton("R");
|
||||
JButton ml = new JButton("←");
|
||||
JButton mr = new JButton("→");
|
||||
del.setMargin(new Insets(0, 0, 0, 0));
|
||||
//ml.setMargin(new Insets(0, 0, 0, 0));
|
||||
//mr.setMargin(new Insets(0, 0, 0, 0));
|
||||
rev.setMargin(new Insets(0, 0, 0, 0));
|
||||
|
||||
JPanel actions = new JPanel();
|
||||
actions.setOpaque(false);
|
||||
actions.setLayout(new GridLayout(1, 4, 4, 4));
|
||||
actions.add(del);
|
||||
actions.add(rev);
|
||||
actions.add(ml);
|
||||
actions.add(mr);
|
||||
actions.setPreferredSize(new Dimension(108, 24));
|
||||
actions.setMaximumSize(new Dimension(108, 24));
|
||||
|
||||
JPanel actions = new JPanel();
|
||||
actions.setOpaque(false);
|
||||
actions.setLayout(new GridLayout(1, 4, 4, 4));
|
||||
actions.add(del);
|
||||
actions.add(rev);
|
||||
actions.add(ml);
|
||||
actions.add(mr);
|
||||
actions.setPreferredSize(new Dimension(108, 24));
|
||||
actions.setMaximumSize(new Dimension(108, 24));
|
||||
|
||||
delete = new JButton("Delete");
|
||||
revert = new JButton("Revert");
|
||||
delete.addActionListener(this);
|
||||
revert.addActionListener(this);
|
||||
|
||||
Box top = Box.createHorizontalBox();
|
||||
top.add(selections);
|
||||
top.add(Box.createGlue());
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
Box top = Box.createHorizontalBox();
|
||||
top.add(selections);
|
||||
top.add(Box.createGlue());
|
||||
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
bottom.add(ml);
|
||||
bottom.add(mr);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(delete);
|
||||
bottom.add(Box.createGlue());
|
||||
bottom.add(revert);
|
||||
bottom.add(mr);
|
||||
bottom.add(Box.createHorizontalStrut(8));
|
||||
bottom.add(delete);
|
||||
bottom.add(Box.createGlue());
|
||||
bottom.add(revert);
|
||||
|
||||
description = new JTextArea();
|
||||
description.setLineWrap(true);
|
||||
@ -622,21 +668,21 @@ implements ActionListener {
|
||||
|
||||
JPanel row1 = new JPanel();
|
||||
row1.setOpaque(false);
|
||||
row1.setLayout(new BorderLayout());
|
||||
row1.setLayout(new BorderLayout());
|
||||
row1.add(descriptionLabel, BorderLayout.NORTH);
|
||||
row1.add(description, BorderLayout.CENTER);
|
||||
|
||||
/*
|
||||
Box row2 = Box.createHorizontalBox();
|
||||
row2.add(Box.createGlue());
|
||||
row2.add(delete);
|
||||
row2.add(Box.createHorizontalStrut(8));
|
||||
row2.add(revert);
|
||||
*/
|
||||
/*
|
||||
Box row2 = Box.createHorizontalBox();
|
||||
row2.add(Box.createGlue());
|
||||
row2.add(delete);
|
||||
row2.add(Box.createHorizontalStrut(8));
|
||||
row2.add(revert);
|
||||
*/
|
||||
|
||||
Box centre = Box.createVerticalBox();
|
||||
centre.setBorder(b4);
|
||||
centre.add(row1);
|
||||
Box centre = Box.createVerticalBox();
|
||||
centre.setBorder(b4);
|
||||
centre.add(row1);
|
||||
|
||||
setLayout(new BorderLayout(8, 8));
|
||||
add(centre, BorderLayout.CENTER);
|
||||
|
108
JKomasto.java
@ -289,10 +289,6 @@ Post {
|
||||
text,
|
||||
approximateText;
|
||||
|
||||
public List<RichTextPane.Segment>
|
||||
formattedText,
|
||||
laidoutText;
|
||||
|
||||
public String
|
||||
contentWarning;
|
||||
|
||||
@ -315,6 +311,9 @@ Post {
|
||||
public String[][]
|
||||
emojiUrls;
|
||||
|
||||
public String[]
|
||||
mentions;
|
||||
|
||||
// - -%- -
|
||||
|
||||
private static final DateTimeFormatter
|
||||
@ -360,80 +359,6 @@ Post {
|
||||
approximateText = b.toString();
|
||||
}
|
||||
|
||||
public void
|
||||
resolveFormattedText()
|
||||
{
|
||||
assert text != null;
|
||||
assert emojiUrls != null;
|
||||
if (formattedText != null) return;
|
||||
|
||||
RichTextPane.Builder b = new RichTextPane.Builder();
|
||||
Tree<String> nodes;
|
||||
nodes = RudimentaryHTMLParser.depthlessRead(text);
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
String tagName = node.get(0).key;
|
||||
Tree<String> href = node.get("href");
|
||||
|
||||
if (tagName.equals("br"))
|
||||
b = b.spacer("\n");
|
||||
if (tagName.equals("/p"))
|
||||
b = b.spacer("\n").spacer("\n");
|
||||
if (tagName.equals("a"))
|
||||
b = b.link(href.value, null).spacer(" ");
|
||||
}
|
||||
if (node.key.equals("text"))
|
||||
{
|
||||
BreakIterator it;
|
||||
it = BreakIterator.getWordInstance(Locale.ROOT);
|
||||
it.setText(node.value);
|
||||
int start = it.first(), end = it.next();
|
||||
while (end != BreakIterator.DONE)
|
||||
{
|
||||
String word = text.substring(start, end);
|
||||
char c = word.isEmpty() ? ' ' : word.charAt(0);
|
||||
boolean w = Character.isWhitespace(c);
|
||||
b = w ? b.spacer(word) : b.text(word);
|
||||
start = end;
|
||||
end = it.next();
|
||||
}
|
||||
}
|
||||
if (node.key.equals("emoji"))
|
||||
{
|
||||
String shortcode = node.value;
|
||||
|
||||
String url = null;
|
||||
for (String[] mapping: emojiUrls)
|
||||
{
|
||||
String ms = mapping[0];
|
||||
String mu = mapping[1];
|
||||
if (ms.equals(shortcode)) url = mu;
|
||||
}
|
||||
|
||||
ImageIcon icon = ImageApi.iconRemote(url);
|
||||
if (icon != null) b = b.image(icon, node.value);
|
||||
else b = b.text(":" + node.value + ":");
|
||||
}
|
||||
}
|
||||
formattedText = b.finish();
|
||||
}
|
||||
|
||||
public int
|
||||
resolveLaidoutText(int width, FontMetrics fm)
|
||||
{
|
||||
assert formattedText != null;
|
||||
|
||||
laidoutText = RichTextPane.layout(formattedText, fm, width);
|
||||
|
||||
int maxY = 0;
|
||||
for (RichTextPane.Segment segment: laidoutText)
|
||||
if (segment.y > maxY) maxY = segment.y;
|
||||
|
||||
return maxY;
|
||||
}
|
||||
|
||||
public void
|
||||
resolveRelativeTime()
|
||||
{
|
||||
@ -509,6 +434,14 @@ Post {
|
||||
Tree<String> boostedPost = entity.get("reblog");
|
||||
if (boostedPost.size() > 0)
|
||||
this.boostedPost = new Post(boostedPost);
|
||||
|
||||
Tree<String> mentions = entity.get("mentions");
|
||||
this.mentions = new String[mentions.size()];
|
||||
for (int o = 0; o < mentions.size(); ++o)
|
||||
{
|
||||
String acct = mentions.get(o).get("acct").value;
|
||||
this.mentions[o] = acct;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -748,7 +681,7 @@ Composition {
|
||||
}
|
||||
|
||||
public static Composition
|
||||
reply(Post post, String ownNumId)
|
||||
reply(Post post, String ownId)
|
||||
{
|
||||
if (post.boostedPost != null) post = post.boostedPost;
|
||||
|
||||
@ -756,9 +689,20 @@ Composition {
|
||||
c.replyToPostId = post.id;
|
||||
c.visibility = post.visibility;
|
||||
c.contentWarning = post.contentWarning;
|
||||
c.text = "";
|
||||
if (!post.author.numId.equals(ownNumId))
|
||||
c.text = "@" + post.author.id + " ";
|
||||
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String id: post.mentions)
|
||||
{
|
||||
if (id.equals(ownId)) continue;
|
||||
text.append("@" + id);
|
||||
}
|
||||
if (!post.author.id.equals(ownId))
|
||||
{
|
||||
text.append("@" + post.author.id);
|
||||
}
|
||||
|
||||
c.text = text.toString();
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ PostWindow extends JFrame {
|
||||
public synchronized void
|
||||
reply()
|
||||
{
|
||||
String ownId = api.getAccountDetails().get("id").value;
|
||||
String ownId = api.getAccountDetails().get("acct").value;
|
||||
Composition c = Composition.reply(this.post, ownId);
|
||||
ComposeWindow w = primaire.getComposeWindow();
|
||||
w.setComposition(c);
|
||||
|
0
graphics/Federated.xcf
Executable file → Normal file
0
graphics/Flags.xcf
Executable file → Normal file
0
graphics/Home.xcf
Executable file → Normal file
0
graphics/Hourglass.xcf
Executable file → Normal file
0
graphics/Kettle.xcf
Executable file → Normal file
0
graphics/boostToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
0
graphics/boostUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/button.png
Executable file → Normal file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
0
graphics/disabledOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
graphics/favouriteToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
0
graphics/favouriteUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
0
graphics/federated.png
Executable file → Normal file
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
0
graphics/home.png
Executable file → Normal file
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
0
graphics/kettle.png
Executable file → Normal file
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
0
graphics/miscToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/miscUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
graphics/nextToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
graphics/nextUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
graphics/postWindow.png
Executable file → Normal file
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 978 B |
0
graphics/prevToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
graphics/prevUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
graphics/ref1.png
Executable file → Normal file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
0
graphics/replyToggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/replyUntoggled.png
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
graphics/selectedOverlay.png
Executable file → Normal file
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 313 B |
0
graphics/test1.png
Executable file → Normal file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
graphics/test2.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test3.png
Executable file → Normal file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
0
graphics/test4.png
Executable file → Normal file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |