Version 1: Basic functionality #1

Merged
andrewlalis merged 10 commits from development into master 2017-06-21 14:48:08 +00:00
8 changed files with 107 additions and 45 deletions
Showing only changes of commit c77540934e - Show all commits

View File

@ -21,9 +21,9 @@ public class HandieBot {
public static final String APPLICATION_NAME = "HandieBot"; public static final String APPLICATION_NAME = "HandieBot";
private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
public static IDiscordClient client; private static IDiscordClient client;
public static View view; public static View view;
public static BotWindow window; private static BotWindow window;
public static BotLog log; public static BotLog log;
private CommandHandler commandHandler; private CommandHandler commandHandler;
@ -58,10 +58,11 @@ public class HandieBot {
* Safely shuts down the bot on all guilds. * Safely shuts down the bot on all guilds.
*/ */
public static void quit(){ public static void quit(){
log.log(BotLog.TYPE.INFO, "Shutting down the bot.");
musicPlayer.quitAll(); musicPlayer.quitAll();
client.logout(); client.logout();
window.dispose(); window.dispose();
System.exit(0);
} }
} }

View File

@ -39,28 +39,28 @@ public class CommandHandler {
if (command.equals("play")){ if (command.equals("play")){
//Play or queue a song. //Play or queue a song.
if (args.length == 1) { if (args.length == 1) {
this.bot.musicPlayer.loadToQueue(guild, args[0]); HandieBot.musicPlayer.loadToQueue(guild, args[0]);
} else if (args.length == 0){ } else if (args.length == 0){
this.bot.musicPlayer.playQueue(guild); HandieBot.musicPlayer.playQueue(guild);
} }
} else if (command.equals("skip") && args.length == 0){ } else if (command.equals("skip") && args.length == 0){
//Skip the current song. //Skip the current song.
this.bot.musicPlayer.skipTrack(guild); HandieBot.musicPlayer.skipTrack(guild);
} else if (command.equals("help")){ } else if (command.equals("help")){
//Send a PM to the user with help info. //Send a PM to the user with help info.
this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time.
} else if (command.equals("queue") && args.length == 0){ } else if (command.equals("queue") && args.length == 0){
//Display the first few items of the queue. //Display the first few items of the queue.
this.bot.musicPlayer.showQueueList(guild); HandieBot.musicPlayer.showQueueList(guild);
} else if (command.equals("repeat")){ } else if (command.equals("repeat")){
//Toggle repeat. //Toggle repeat.
//TODO implement repeat command. HandieBot.musicPlayer.toggleRepeat(guild);
} else if (command.equals("clear")){ } else if (command.equals("clear")){
//TODO clear command. //TODO clear command.
} else if (command.equals("quit")){ } else if (command.equals("quit")){
//Quit the application. //Quit the application.
channel.sendMessage("Quitting HandieBot functions."); channel.sendMessage("Quitting HandieBot functions.");
this.bot.musicPlayer.quit(guild); HandieBot.musicPlayer.quit(guild);
} else if (command.equals("playlist")){ } else if (command.equals("playlist")){
//Do playlist actions. //Do playlist actions.
//TODO perform actions! //TODO perform actions!

View File

@ -16,7 +16,7 @@ public class GuildMusicManager {
public GuildMusicManager(AudioPlayerManager manager, IGuild guild){ public GuildMusicManager(AudioPlayerManager manager, IGuild guild){
this.player = manager.createPlayer(); this.player = manager.createPlayer();
this.scheduler = new TrackScheduler(this.player, guild); this.scheduler = new TrackScheduler(this.player, guild, manager);
this.player.addListener(this.scheduler); this.player.addListener(this.scheduler);
} }

View File

@ -61,7 +61,7 @@ public class MusicPlayer {
*/ */
private GuildMusicManager getMusicManager(IGuild guild){ private GuildMusicManager getMusicManager(IGuild guild){
if (!this.musicManagers.containsKey(guild)){ if (!this.musicManagers.containsKey(guild)){
log.log(BotLog.TYPE.MUSIC, "Creating new music manager and audio provider for guild: "+guild.getName()); log.log(BotLog.TYPE.MUSIC, guild, "Creating new music manager and audio provider for guild: "+guild.getName());
this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild)); this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild));
guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider()); guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider());
} }
@ -78,7 +78,7 @@ public class MusicPlayer {
if (!this.chatChannels.containsKey(guild)){ if (!this.chatChannels.containsKey(guild)){
List<IChannel> channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); List<IChannel> channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase());
if (channels.isEmpty()){ if (channels.isEmpty()){
log.log(BotLog.TYPE.MUSIC, "No chat channel found, creating a new one."); log.log(BotLog.TYPE.MUSIC, guild, "No chat channel found, creating a new one.");
this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase())); this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase()));
} else { } else {
this.chatChannels.put(guild, channels.get(0)); this.chatChannels.put(guild, channels.get(0));
@ -97,7 +97,7 @@ public class MusicPlayer {
if (!this.voiceChannels.containsKey(guild)){ if (!this.voiceChannels.containsKey(guild)){
List<IVoiceChannel> channels = guild.getVoiceChannelsByName(CHANNEL_NAME); List<IVoiceChannel> channels = guild.getVoiceChannelsByName(CHANNEL_NAME);
if (channels.isEmpty()){ if (channels.isEmpty()){
log.log(BotLog.TYPE.MUSIC, "No voice channel found, creating a new one."); log.log(BotLog.TYPE.MUSIC, guild, "No voice channel found, creating a new one.");
this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME)); this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME));
} else { } else {
this.voiceChannels.put(guild, channels.get(0)); this.voiceChannels.put(guild, channels.get(0));
@ -106,6 +106,16 @@ public class MusicPlayer {
return this.voiceChannels.get(guild); return this.voiceChannels.get(guild);
} }
/**
* Toggles the repeating of songs for a particular guild.
* @param guild The guild to repeat for.
*/
public void toggleRepeat(IGuild guild){
GuildMusicManager musicManager = this.getMusicManager(guild);
musicManager.scheduler.setRepeat(!musicManager.scheduler.isRepeating());
}
/** /**
* Sends a formatted message to the guild about the first few items in a queue. * Sends a formatted message to the guild about the first few items in a queue.
*/ */
@ -124,13 +134,12 @@ public class MusicPlayer {
sb.append(tracks.get(i).getInfo().title); sb.append(tracks.get(i).getInfo().title);
sb.append("]("); sb.append("](");
sb.append(tracks.get(i).getInfo().uri); sb.append(tracks.get(i).getInfo().uri);
sb.append(") ["); sb.append(")");
int seconds = (int) (tracks.get(i).getInfo().length/1000); int seconds = (int) (tracks.get(i).getInfo().length/1000);
int minutes = seconds / 60; int minutes = seconds / 60;
sb.append(minutes); seconds = seconds % 60;
sb.append(":"); String time = String.format(" [%d:%02d]\n", minutes, seconds);
sb.append(seconds % 60); sb.append(time);
sb.append("]\n");
} }
builder.withTimestamp(System.currentTimeMillis()); builder.withTimestamp(System.currentTimeMillis());
builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false); builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false);
@ -147,7 +156,6 @@ public class MusicPlayer {
this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() { this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() {
@Override @Override
public void trackLoaded(AudioTrack audioTrack) { public void trackLoaded(AudioTrack audioTrack) {
log.log(BotLog.TYPE.MUSIC, "Track successfully loaded: "+audioTrack.getInfo().title);
addToQueue(guild, audioTrack); addToQueue(guild, audioTrack);
} }
@ -164,11 +172,13 @@ public class MusicPlayer {
@Override @Override
public void noMatches() { public void noMatches() {
log.log(BotLog.TYPE.ERROR, guild, "No matches found for: "+trackURL);
new DisappearingMessage(getChatChannel(guild), "Unable to find a result for: "+trackURL, 3000); new DisappearingMessage(getChatChannel(guild), "Unable to find a result for: "+trackURL, 3000);
} }
@Override @Override
public void loadFailed(FriendlyException e) { public void loadFailed(FriendlyException e) {
log.log(BotLog.TYPE.ERROR, guild, "Unable to load song: "+trackURL+". "+e.getMessage());
new DisappearingMessage(getChatChannel(guild), "Unable to load. "+e.getMessage(), 3000); new DisappearingMessage(getChatChannel(guild), "Unable to load. "+e.getMessage(), 3000);
} }
}); });
@ -220,7 +230,7 @@ public class MusicPlayer {
*/ */
public void skipTrack(IGuild guild){ public void skipTrack(IGuild guild){
getMusicManager(guild).scheduler.nextTrack(); getMusicManager(guild).scheduler.nextTrack();
log.log(BotLog.TYPE.MUSIC, "Skipping the current track. "); log.log(BotLog.TYPE.MUSIC, guild, "Skipping the current track. ");
new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000); new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000);
} }

View File

@ -2,10 +2,10 @@ package handiebot.lavaplayer;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import handiebot.view.BotLog;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
@ -13,6 +13,9 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ExecutionException;
import static handiebot.HandieBot.log;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
@ -24,7 +27,7 @@ public class Playlist {
private String name; private String name;
private long creatorUID; private long creatorUID;
List<AudioTrack> tracks; private List<AudioTrack> tracks;
/** /**
* Creates an empty playlist template. * Creates an empty playlist template.
@ -41,9 +44,9 @@ public class Playlist {
* Creates a playlist from a file with the given name. * Creates a playlist from a file with the given name.
* @param name The name of the file. * @param name The name of the file.
*/ */
public Playlist(String name){ public Playlist(String name, AudioPlayerManager playerManager){
this.name = name; this.name = name;
this.load(); this.load(playerManager);
} }
public String getName(){ public String getName(){
@ -74,6 +77,15 @@ public class Playlist {
this.tracks.remove(track); this.tracks.remove(track);
} }
/**
* Copies all tracks from a specified playlist to this one.
* @param other The other playlist to make a copy of.
*/
public void copyFrom(Playlist other){
this.tracks.clear();
other.getTracks().forEach(track -> this.tracks.add(track.makeClone()));
}
/** /**
* Returns the next track, i.e. the first one in the list, and removes it from the internal list. * Returns the next track, i.e. the first one in the list, and removes it from the internal list.
* @return The AudioTrack that should be played next. * @return The AudioTrack that should be played next.
@ -87,7 +99,7 @@ public class Playlist {
/** /**
* Returns the next track to be played, and re-adds it to the end of the playlist, as it would do in a loop. * Returns the next track to be played, and re-adds it to the end of the playlist, as it would do in a loop.
* @return * @return The next track to be played.
*/ */
public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){ public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){
if (this.tracks.isEmpty()){ if (this.tracks.isEmpty()){
@ -122,12 +134,12 @@ public class Playlist {
File playlistDir = new File(homeDir+"/.handiebot/playlist"); File playlistDir = new File(homeDir+"/.handiebot/playlist");
if (!playlistDir.exists()){ if (!playlistDir.exists()){
if (!playlistDir.mkdirs()){ if (!playlistDir.mkdirs()){
System.out.println("Unable to make directory: "+playlistDir.getPath()); log.log(BotLog.TYPE.ERROR, "Unable to make directory: "+playlistDir.getPath());
return; return;
} }
} }
File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt"); File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt");
System.out.println("Saving playlist to: "+playlistFile.getAbsolutePath()); log.log(BotLog.TYPE.INFO, "Saving playlist to: "+playlistFile.getAbsolutePath());
try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){ try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){
writer.write(this.name+'\n'); writer.write(this.name+'\n');
writer.write(Long.toString(this.creatorUID)+'\n'); writer.write(Long.toString(this.creatorUID)+'\n');
@ -137,6 +149,7 @@ public class Playlist {
writer.write('\n'); writer.write('\n');
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.log(BotLog.TYPE.ERROR, "Unable to find file to write playlist: "+this.name);
e.printStackTrace(); e.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -146,8 +159,9 @@ public class Playlist {
/** /**
* Loads the playlist from a file with the playlist's name. * Loads the playlist from a file with the playlist's name.
*/ */
public void load(){//TODO Make load work!!! public void load(AudioPlayerManager playerManager){
String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt"; String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt";
log.log(BotLog.TYPE.INFO, "Loading playlist from: "+path);
File playlistFile = new File(path); File playlistFile = new File(path);
if (playlistFile.exists()){ if (playlistFile.exists()){
try { try {
@ -156,20 +170,16 @@ public class Playlist {
this.creatorUID = Long.parseLong(lines.remove(0)); this.creatorUID = Long.parseLong(lines.remove(0));
int trackCount = Integer.parseInt(lines.remove(0)); int trackCount = Integer.parseInt(lines.remove(0));
this.tracks = new ArrayList<>(trackCount); this.tracks = new ArrayList<>(trackCount);
AudioPlayerManager pm = new DefaultAudioPlayerManager();
for (int i = 0; i < trackCount; i++){ for (int i = 0; i < trackCount; i++){
System.out.println("Loading item "+i);
String url = lines.remove(0); String url = lines.remove(0);
pm.loadItem(url, new AudioLoadResultHandler() { playerManager.loadItem(url, new AudioLoadResultHandler() {
@Override @Override
public void trackLoaded(AudioTrack audioTrack) { public void trackLoaded(AudioTrack audioTrack) {
System.out.println("Added track");
tracks.add(audioTrack); tracks.add(audioTrack);
} }
@Override @Override
public void playlistLoaded(AudioPlaylist audioPlaylist) { public void playlistLoaded(AudioPlaylist audioPlaylist) {
System.out.println("Playlist loaded.");
//Do nothing. This should not happen. //Do nothing. This should not happen.
} }
@ -184,9 +194,16 @@ public class Playlist {
System.out.println("Load failed: "+e.getMessage()); System.out.println("Load failed: "+e.getMessage());
//Do nothing. This should not happen. //Do nothing. This should not happen.
} }
}); }).get();
} }
} catch (IOException e) { } catch (IOException e) {
log.log(BotLog.TYPE.ERROR, "IOException while loading playlist ["+this.name+"]. "+e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
log.log(BotLog.TYPE.ERROR, "Loading of playlist ["+this.name+"] interrupted. "+e.getMessage());
e.printStackTrace();
} catch (ExecutionException e) {
log.log(BotLog.TYPE.ERROR, "Execution exception while loading playlist ["+this.name+"]. "+e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }
} }

View File

@ -1,6 +1,7 @@
package handiebot.lavaplayer; package handiebot.lavaplayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
@ -24,7 +25,7 @@ public class TrackScheduler extends AudioEventAdapter {
private Playlist activePlaylist; private Playlist activePlaylist;
private boolean repeat = false; private boolean repeat = true;
private boolean shuffle = false; private boolean shuffle = false;
private IGuild guild; private IGuild guild;
@ -33,11 +34,11 @@ public class TrackScheduler extends AudioEventAdapter {
* Constructs a new track scheduler with the given player. * Constructs a new track scheduler with the given player.
* @param player The audio player this scheduler uses. * @param player The audio player this scheduler uses.
*/ */
public TrackScheduler(AudioPlayer player, IGuild guild){ public TrackScheduler(AudioPlayer player, IGuild guild, AudioPlayerManager playerManager){
this.player = player; this.player = player;
this.guild = guild; this.guild = guild;
this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L); //this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L);
//this.activePlaylist = new Playlist("HandieBot Active Playlist"); this.activePlaylist = new Playlist("HandieBot Active Playlist", playerManager);
} }
/** /**
@ -130,7 +131,7 @@ public class TrackScheduler extends AudioEventAdapter {
@Override @Override
public void onTrackStart(AudioPlayer player, AudioTrack track) { public void onTrackStart(AudioPlayer player, AudioTrack track) {
log.log(BotLog.TYPE.MUSIC, "Started audio track: "+track.getInfo().title); log.log(BotLog.TYPE.MUSIC, this.guild, "Started audio track: "+track.getInfo().title);
List<IChannel> channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase()); List<IChannel> channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase());
if (channels.size() > 0){ if (channels.size() > 0){
IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**.");
@ -146,7 +147,7 @@ public class TrackScheduler extends AudioEventAdapter {
System.out.println("Moving to next track."); System.out.println("Moving to next track.");
nextTrack(); nextTrack();
} else { } else {
log.log(BotLog.TYPE.ERROR, "Unable to go to the next track. Reason: "+endReason.name()); log.log(BotLog.TYPE.ERROR, this.guild, "Unable to go to the next track. Reason: "+endReason.name());
System.out.println(endReason.toString()); System.out.println(endReason.toString());
} }
} }

View File

@ -1,5 +1,7 @@
package handiebot.view; package handiebot.view;
import sx.blah.discord.handle.obj.IGuild;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.BadLocationException; import javax.swing.text.BadLocationException;
import javax.swing.text.Style; import javax.swing.text.Style;
@ -21,10 +23,19 @@ public class BotLog {
public enum TYPE { public enum TYPE {
INFO, INFO,
MUSIC, MUSIC,
ERROR ERROR,
COMMAND
} }
//Styles for output to the console.
private Map<TYPE, Style> logStyles; private Map<TYPE, Style> logStyles;
private static Map<TYPE, Color> logStyleColors = new HashMap<TYPE, Color>(){{
put(INFO, new Color(22, 63, 160));
put(MUSIC, new Color(51, 175, 66));
put(ERROR, new Color(255, 0, 0));
put(COMMAND, new Color(255, 123, 0));
}};
private Style defaultStyle; private Style defaultStyle;
private JTextPane outputArea; private JTextPane outputArea;
@ -46,10 +57,8 @@ public class BotLog {
//Define each type's color. //Define each type's color.
for (TYPE type : TYPE.values()) { for (TYPE type : TYPE.values()) {
this.logStyles.put(type, outputArea.addStyle(type.name(), this.defaultStyle)); this.logStyles.put(type, outputArea.addStyle(type.name(), this.defaultStyle));
this.logStyles.get(type).addAttribute(StyleConstants.Foreground, logStyleColors.get(type));
} }
this.logStyles.get(INFO).addAttribute(StyleConstants.Foreground, Color.blue);
this.logStyles.get(MUSIC).addAttribute(StyleConstants.Foreground, new Color(51, 175, 66));
this.logStyles.get(ERROR).addAttribute(StyleConstants.Foreground, Color.red);
} }
/** /**
@ -70,4 +79,24 @@ public class BotLog {
} }
} }
/**
* Writes a string to the output window with the given tag, guild name, and text.
* @param type The type of message to write.
* @param guild The guild to get the name of.
* @param message The content of the message.
*/
public void log(TYPE type, IGuild guild, String message){
Date date = new Date(System.currentTimeMillis());
DateFormat formatter = new SimpleDateFormat("HH:mm:ss:SSS");
String dateFormatted = formatter.format(date);
try {
this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), dateFormatted, this.defaultStyle);
this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), '['+type.name()+']', this.logStyles.get(type));
this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), '['+guild.getName()+"] ", this.defaultStyle);
this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), message+'\n', this.defaultStyle);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
} }

View File

@ -43,7 +43,11 @@
<preferred-size width="150" height="-1"/> <preferred-size width="150" height="-1"/>
</grid> </grid>
</constraints> </constraints>
<properties/> <properties>
<font name="DialogInput" size="16"/>
<foreground color="-16118999"/>
<margin top="0" left="0" bottom="0" right="0"/>
</properties>
</component> </component>
</children> </children>
</grid> </grid>