Retooled the GUI for the app - much easier on the eyes, fits in better with MediaPlayer (and looks better ;-) and flicker is no longer a problem.
Header has been added to files Style updates to source files, also. I may have missed a bit, but it sure is much closer than before git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@20104 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
+251
-402
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2007, Haiku, Inc.
|
||||
* Distributed under the terms of the MIT license.
|
||||
*
|
||||
* Author:
|
||||
* DarkWyrm <[email protected]>
|
||||
*/
|
||||
#include <Alert.h>
|
||||
#include <Application.h>
|
||||
#include <Bitmap.h>
|
||||
@@ -15,6 +22,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ButtonBitmaps.h"
|
||||
#include "CDPlayer.h"
|
||||
#include "DrawButton.h"
|
||||
#include "DoubleShotDrawButton.h"
|
||||
@@ -24,11 +32,9 @@
|
||||
#include <TranslatorRoster.h>
|
||||
#include <BitmapStream.h>
|
||||
|
||||
enum
|
||||
{
|
||||
M_STOP='mstp',
|
||||
enum {
|
||||
M_STOP = 'mstp',
|
||||
M_PLAY,
|
||||
M_SELECT_TRACK,
|
||||
M_NEXT_TRACK,
|
||||
M_PREV_TRACK,
|
||||
M_FFWD,
|
||||
@@ -41,36 +47,53 @@ enum
|
||||
M_SET_CD_TITLE
|
||||
};
|
||||
|
||||
CDPlayer::CDPlayer(BRect frame, const char *name, uint32 resizeMask, uint32 flags)
|
||||
: BView(frame, name, resizeMask, flags | B_FRAME_EVENTS),
|
||||
fCDQuery("freedb.freedb.org")
|
||||
class CDPlayerWindow : public BWindow
|
||||
{
|
||||
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
|
||||
public:
|
||||
CDPlayerWindow(void);
|
||||
bool QuitRequested(void);
|
||||
};
|
||||
|
||||
inline void
|
||||
SetLabel(BStringView *label, const char *text)
|
||||
{
|
||||
if (strcmp(label->Text(),text) != 0)
|
||||
label->SetText(text);
|
||||
}
|
||||
|
||||
|
||||
CDPlayer::CDPlayer(BRect frame, const char *name, uint32 resizeMask, uint32 flags)
|
||||
: BView(frame, name, resizeMask, flags | B_FRAME_EVENTS),
|
||||
fCDQuery("freedb.freedb.org")
|
||||
{
|
||||
SetViewColor(216,216,216);
|
||||
|
||||
fVolume=255;
|
||||
fVolume = 255;
|
||||
|
||||
BuildGUI();
|
||||
|
||||
if(fCDDrive.CountDrives()<1)
|
||||
{
|
||||
BAlert *alert = new BAlert("CDPlayer","It appears that there are no CD drives on your"
|
||||
"computer or there is no system software to support one."
|
||||
" Sorry.","OK");
|
||||
if (fCDDrive.CountDrives() < 1) {
|
||||
BAlert *alert = new BAlert("CDPlayer","It appears that there are no CD drives"
|
||||
" on your computer or there is no system software"
|
||||
" to support one. Sorry.","OK");
|
||||
alert->Go();
|
||||
be_app->PostMessage(B_QUIT_REQUESTED);
|
||||
}
|
||||
|
||||
fWindowState=fCDDrive.GetState();
|
||||
fWindowState = fCDDrive.GetState();
|
||||
fVolumeSlider->SetValue(fCDDrive.GetVolume());
|
||||
WatchCDState();
|
||||
}
|
||||
|
||||
|
||||
CDPlayer::~CDPlayer()
|
||||
{
|
||||
fCDDrive.Stop();
|
||||
}
|
||||
|
||||
void CDPlayer::BuildGUI(void)
|
||||
|
||||
void
|
||||
CDPlayer::BuildGUI(void)
|
||||
{
|
||||
fStopColor.red = 80;
|
||||
fStopColor.green = 164;
|
||||
@@ -82,152 +105,100 @@ void CDPlayer::BuildGUI(void)
|
||||
fPlayColor.blue = 40;
|
||||
fPlayColor.alpha = 255;
|
||||
|
||||
BRect r;
|
||||
r.left = 5;
|
||||
r.top = 5;
|
||||
r.right = (Bounds().Width()/2) - 7;
|
||||
BRect r(Bounds().InsetByCopy(10,10));
|
||||
BBox *box = new BBox(r,"",B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
|
||||
AddChild(box);
|
||||
|
||||
r = box->Bounds().InsetByCopy(10,10);
|
||||
r.bottom = 25;
|
||||
|
||||
float labelWidth, labelHeight;
|
||||
fCDTitle = new BStringView(r,"CDTitle","CD drive is empty", B_FOLLOW_LEFT_RIGHT);
|
||||
fCDTitle->GetPreferredSize(&labelWidth, &labelHeight);
|
||||
fCDTitle->ResizeTo(r.Width(), labelHeight);
|
||||
box->AddChild(fCDTitle);
|
||||
|
||||
// Assemble the CD Title box
|
||||
fCDBox = new BBox(r,"TrackBox");
|
||||
AddChild(fCDBox);
|
||||
r.bottom = r.top + labelHeight;
|
||||
r.OffsetBy(0, r.Height() + 5);
|
||||
|
||||
BView *view = new BView( fCDBox->Bounds().InsetByCopy(2,2), "view",B_FOLLOW_ALL,B_WILL_DRAW);
|
||||
view->SetViewColor(20,20,20);
|
||||
fCDBox->AddChild(view);
|
||||
|
||||
fCDTitle = new BStringView(view->Bounds(),"CDTitle","", B_FOLLOW_ALL);
|
||||
view->AddChild(fCDTitle);
|
||||
fCDTitle->SetHighColor(200,200,200);
|
||||
fCDTitle->SetFont(be_bold_font);
|
||||
fCurrentTrack = new BStringView(r,"TrackNumber","",B_FOLLOW_LEFT_RIGHT);
|
||||
box->AddChild(fCurrentTrack);
|
||||
|
||||
r.OffsetBy(0, r.Height() + 5);
|
||||
fTrackMenu = new TrackMenu(r, "TrackMenu", new BMessage(M_SELECT_TRACK));
|
||||
AddChild(fTrackMenu);
|
||||
r.right = r.left + (r.Width() / 2);
|
||||
fTrackTime = new BStringView(r,"TrackTime","Track: --:-- / --:--",B_FOLLOW_LEFT_RIGHT);
|
||||
box->AddChild(fTrackTime);
|
||||
|
||||
r.Set(fCDTitle->Frame().right + 15,5,Bounds().right - 5,25);
|
||||
fTrackBox = new BBox(r,"TrackBox",B_FOLLOW_TOP);
|
||||
AddChild(fTrackBox);
|
||||
|
||||
view = new BView( fTrackBox->Bounds().InsetByCopy(2,2), "view",B_FOLLOW_ALL,B_WILL_DRAW);
|
||||
view->SetViewColor(0,34,7);
|
||||
fTrackBox->AddChild(view);
|
||||
r.OffsetTo(box->Bounds().right / 2, r.top);
|
||||
fDiscTime = new BStringView(r,"DiscTime","Disc: --:-- / --:--",B_FOLLOW_RIGHT);
|
||||
box->AddChild(fDiscTime);
|
||||
|
||||
fCurrentTrack = new BStringView( view->Bounds(),"TrackNumber","",B_FOLLOW_ALL);
|
||||
view->AddChild(fCurrentTrack);
|
||||
fCurrentTrack->SetHighColor(fStopColor);
|
||||
fCurrentTrack->SetFont(be_bold_font);
|
||||
box->ResizeTo(box->Bounds().Width(), fDiscTime->Frame().bottom + 10);
|
||||
|
||||
r.OffsetBy(0, r.Height() + 5);
|
||||
fTimeBox = new BBox(r,"TimeBox",B_FOLLOW_LEFT_RIGHT);
|
||||
AddChild(fTimeBox);
|
||||
|
||||
view = new BView( fTimeBox->Bounds().InsetByCopy(2,2), "view",B_FOLLOW_ALL,B_WILL_DRAW);
|
||||
view->SetViewColor(0,7,34);
|
||||
fTimeBox->AddChild(view);
|
||||
|
||||
r = view->Bounds();
|
||||
r.right /= 2;
|
||||
|
||||
fTrackTime = new BStringView(r,"TrackTime","Track --:-- / --:--",B_FOLLOW_LEFT_RIGHT);
|
||||
view->AddChild(fTrackTime);
|
||||
fTrackTime->SetHighColor(120,120,255);
|
||||
fTrackTime->SetFont(be_bold_font);
|
||||
|
||||
r.right = view->Bounds().right;
|
||||
r.left = fTrackTime->Frame().right + 1;
|
||||
|
||||
fDiscTime = new BStringView(r,"DiscTime","Disc --:-- / --:--",B_FOLLOW_RIGHT);
|
||||
view->AddChild(fDiscTime);
|
||||
fDiscTime->SetHighColor(120,120,255);
|
||||
fDiscTime->SetFont(be_bold_font);
|
||||
|
||||
fVolumeSlider = new BSlider( BRect(0,0,75,30), "VolumeSlider", "Volume", new BMessage(M_SET_VOLUME),0,255);
|
||||
fVolumeSlider->MoveTo(5, Bounds().bottom - 10 - fVolumeSlider->Frame().Height());
|
||||
AddChild(fVolumeSlider);
|
||||
|
||||
fStop = new DrawButton( BRect(0,0,1,1), "Stop", BTranslationUtils::GetBitmap(B_PNG_FORMAT,"stop_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"stop_down"), new BMessage(M_STOP),
|
||||
B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fStop = new DrawButton(BRect(0,0,1,1), "Stop",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"stop_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"stop_down"),
|
||||
new BMessage(M_STOP), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fStop->ResizeToPreferred();
|
||||
fStop->MoveTo(fVolumeSlider->Frame().right + 10, Bounds().bottom - 5 - fStop->Frame().Height());
|
||||
fStop->MoveTo(10, box->Frame().bottom + 15);
|
||||
fStop->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"stop_disabled"));
|
||||
AddChild(fStop);
|
||||
float stopTop = fStop->Frame().top;
|
||||
|
||||
fPlay = new TwoStateDrawButton( BRect(0,0,1,1), "Play",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_down"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"pause_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"pause_down"),
|
||||
new BMessage(M_PLAY), B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
|
||||
fPlay = new TwoStateDrawButton(BRect(0,0,1,1), "Play",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_down"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_up_on"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_down_on"),
|
||||
new BMessage(M_PLAY), B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
|
||||
fPlay->ResizeToPreferred();
|
||||
fPlay->MoveTo(fStop->Frame().right + 2, Bounds().bottom - 5 - fPlay->Frame().Height());
|
||||
fPlay->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_disabled"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"pause_disabled"));
|
||||
fPlay->MoveTo(fStop->Frame().right + 2, stopTop);
|
||||
AddChild(fPlay);
|
||||
|
||||
fPrevTrack = new DrawButton( BRect(0,0,1,1), "PrevTrack", BTranslationUtils::GetBitmap(B_PNG_FORMAT,"prev_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"prev_down"), new BMessage(M_PREV_TRACK),
|
||||
B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fPrevTrack = new DrawButton(BRect(0,0,1,1), "PrevTrack",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"prev_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"prev_down"),
|
||||
new BMessage(M_PREV_TRACK), B_FOLLOW_BOTTOM,
|
||||
B_WILL_DRAW);
|
||||
fPrevTrack->ResizeToPreferred();
|
||||
fPrevTrack->MoveTo(fPlay->Frame().right + 10, Bounds().bottom - 5 - fPrevTrack->Frame().Height());
|
||||
fPrevTrack->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"prev_disabled"));
|
||||
fPrevTrack->MoveTo(fPlay->Frame().right + 10, stopTop);
|
||||
AddChild(fPrevTrack);
|
||||
|
||||
fNextTrack = new DrawButton( BRect(0,0,1,1), "NextTrack", BTranslationUtils::GetBitmap(B_PNG_FORMAT,"next_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"next_down"), new BMessage(M_NEXT_TRACK),
|
||||
B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fNextTrack = new DrawButton(BRect(0,0,1,1), "NextTrack",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"next_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"next_down"),
|
||||
new BMessage(M_NEXT_TRACK), B_FOLLOW_BOTTOM,
|
||||
B_WILL_DRAW);
|
||||
fNextTrack->ResizeToPreferred();
|
||||
fNextTrack->MoveTo(fPrevTrack->Frame().right + 2, Bounds().bottom - 5 - fNextTrack->Frame().Height());
|
||||
fNextTrack->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"next_disabled"));
|
||||
fNextTrack->MoveTo(fPrevTrack->Frame().right + 2, stopTop);
|
||||
AddChild(fNextTrack);
|
||||
|
||||
fRewind = new DoubleShotDrawButton( BRect(0,0,1,1), "Rewind",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"rew_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"rew_down"),
|
||||
new BMessage(M_REWIND), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fRewind = new DoubleShotDrawButton(BRect(0,0,1,1), "Rewind",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"rew_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"rew_down"),
|
||||
new BMessage(M_REWIND), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fRewind->ResizeToPreferred();
|
||||
fRewind->MoveTo(fNextTrack->Frame().right + 10, Bounds().bottom - 5 - fRewind->Frame().Height());
|
||||
fRewind->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"rew_disabled"));
|
||||
fRewind->MoveTo(fNextTrack->Frame().right + 10, stopTop);
|
||||
AddChild(fRewind);
|
||||
|
||||
fFastFwd = new DoubleShotDrawButton( BRect(0,0,1,1), "FastFwd",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"ffwd_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"ffwd_down"),
|
||||
new BMessage(M_FFWD), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fFastFwd = new DoubleShotDrawButton(BRect(0,0,1,1), "FastFwd",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"ffwd_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"ffwd_down"),
|
||||
new BMessage(M_FFWD), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fFastFwd->ResizeToPreferred();
|
||||
fFastFwd->MoveTo(fRewind->Frame().right + 2, Bounds().bottom - 5 - fFastFwd->Frame().Height());
|
||||
fFastFwd->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"ffwd_disabled"));
|
||||
fFastFwd->MoveTo(fRewind->Frame().right + 2, stopTop);
|
||||
AddChild(fFastFwd);
|
||||
|
||||
fEject = new DrawButton( BRect(0,0,1,1), "Eject", BTranslationUtils::GetBitmap(B_PNG_FORMAT,"eject_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"eject_down"), new BMessage(M_EJECT),
|
||||
B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fEject->ResizeToPreferred();
|
||||
fEject->MoveTo(fFastFwd->Frame().right + 20, Bounds().bottom - 5 - fEject->Frame().Height());
|
||||
fEject->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"eject_disabled"));
|
||||
AddChild(fEject);
|
||||
|
||||
fSave = new DrawButton( BRect(0,0,1,1), "Save", BTranslationUtils::GetBitmap(B_PNG_FORMAT,"save_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"save_down"), new BMessage(M_SAVE),
|
||||
B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
fSave->ResizeToPreferred();
|
||||
fSave->MoveTo(fEject->Frame().right + 20, Bounds().bottom - 5 - fSave->Frame().Height());
|
||||
fSave->SetDisabled(BTranslationUtils::GetBitmap(B_PNG_FORMAT,"save_disabled"));
|
||||
AddChild(fSave);
|
||||
fSave->SetEnabled(false);
|
||||
|
||||
fShuffle = new TwoStateDrawButton( BRect(0,0,1,1), "Shuffle",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_down"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_up_on"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_down_on"),
|
||||
new BMessage(M_SHUFFLE), B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
fShuffle->ResizeToPreferred();
|
||||
fShuffle->MoveTo(fSave->Frame().right + 2, Bounds().bottom - 5 - fShuffle->Frame().Height());
|
||||
AddChild(fShuffle);
|
||||
r.left = 10;
|
||||
r.right = fPlay->Frame().right;
|
||||
r.top = fStop->Frame().bottom + 14;
|
||||
r.bottom = r.top + kVolumeSliderBitmapHeight - 1.0;
|
||||
fVolumeSlider = new VolumeSlider(r,"VolumeSlider",0,255, new BMessage(M_SET_VOLUME),
|
||||
this);
|
||||
fVolumeSlider->ResizeToPreferred();
|
||||
AddChild(fVolumeSlider);
|
||||
|
||||
fRepeat = new TwoStateDrawButton( BRect(0,0,1,1), "Repeat",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"repeat_up"),
|
||||
@@ -236,99 +207,100 @@ void CDPlayer::BuildGUI(void)
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"repeat_down_on"),
|
||||
new BMessage(M_REPEAT), B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
fRepeat->ResizeToPreferred();
|
||||
fRepeat->MoveTo(fShuffle->Frame().right + 2, Bounds().bottom - 5 - fRepeat->Frame().Height());
|
||||
fRepeat->MoveTo(fPrevTrack->Frame().left,
|
||||
fVolumeSlider->Frame().top -
|
||||
((fRepeat->Frame().Height() - fVolumeSlider->Frame().Height()) / 2));
|
||||
AddChild(fRepeat);
|
||||
|
||||
fShuffle = new TwoStateDrawButton(BRect(0,0,1,1), "Shuffle",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_down"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_up_on"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"shuffle_down_on"),
|
||||
new BMessage(M_SHUFFLE), B_FOLLOW_NONE, B_WILL_DRAW);
|
||||
fShuffle->ResizeToPreferred();
|
||||
fShuffle->MoveTo(fNextTrack->Frame().left + 2,fRepeat->Frame().top);
|
||||
AddChild(fShuffle);
|
||||
|
||||
fEject = new DrawButton(BRect(0,0,1,1), "Eject",
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"eject_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"eject_down"),
|
||||
new BMessage(M_EJECT), B_FOLLOW_BOTTOM, B_WILL_DRAW);
|
||||
fEject->ResizeToPreferred();
|
||||
fEject->MoveTo(fFastFwd->Frame().left, fShuffle->Frame().top);
|
||||
AddChild(fEject);
|
||||
|
||||
ResizeTo(fFastFwd->Frame().right + 10, fVolumeSlider->Frame().bottom + 10);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CDPlayer::MessageReceived(BMessage *msg)
|
||||
{
|
||||
switch (msg->what)
|
||||
{
|
||||
case M_SET_VOLUME:
|
||||
{
|
||||
switch (msg->what) {
|
||||
case M_SET_VOLUME: {
|
||||
fCDDrive.SetVolume(fVolumeSlider->Value());
|
||||
break;
|
||||
}
|
||||
case M_STOP:
|
||||
{
|
||||
fWindowState=kStopped;
|
||||
case M_STOP: {
|
||||
if (fWindowState == kPaused) {
|
||||
fPlay->SetBitmaps(0, BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_down"));
|
||||
fPlay->SetState(1);
|
||||
}
|
||||
fWindowState = kStopped;
|
||||
fCDDrive.Stop();
|
||||
break;
|
||||
}
|
||||
case M_PLAY:
|
||||
{
|
||||
case M_PLAY: {
|
||||
// If we are currently playing, then we will be showing
|
||||
// the pause images and will want to switch back to the play images
|
||||
if(fWindowState==kPlaying)
|
||||
{
|
||||
fWindowState=kPaused;
|
||||
if (fWindowState == kPlaying) {
|
||||
fWindowState = kPaused;
|
||||
fCDDrive.Pause();
|
||||
}
|
||||
else
|
||||
if(fWindowState==kPaused)
|
||||
{
|
||||
fWindowState=kPlaying;
|
||||
fPlay->SetBitmaps(0, BTranslationUtils::GetBitmap(B_PNG_FORMAT,"paused_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"paused_down"));
|
||||
} else if (fWindowState == kPaused) {
|
||||
fWindowState = kPlaying;
|
||||
fCDDrive.Resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
fWindowState=kPlaying;
|
||||
fPlay->SetBitmaps(0, BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_up"),
|
||||
BTranslationUtils::GetBitmap(B_PNG_FORMAT,"play_down"));
|
||||
} else {
|
||||
fWindowState = kPlaying;
|
||||
fCDDrive.Play(fPlayList.GetCurrentTrack());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M_SELECT_TRACK:
|
||||
{
|
||||
fPlayList.SetCurrentTrack(fTrackMenu->Value()+1);
|
||||
fWindowState=kPlaying;
|
||||
if(!fCDDrive.Play(fPlayList.GetCurrentTrack()))
|
||||
{
|
||||
fWindowState=kStopped;
|
||||
fCDDrive.Stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M_EJECT:
|
||||
{
|
||||
case M_EJECT: {
|
||||
fCDDrive.Eject();
|
||||
break;
|
||||
}
|
||||
case M_NEXT_TRACK:
|
||||
{
|
||||
case M_NEXT_TRACK: {
|
||||
int16 next = fPlayList.GetNextTrack();
|
||||
if(next <= 0)
|
||||
{
|
||||
if (next <= 0) {
|
||||
// force a "wrap around" when possible. This makes it
|
||||
// possible for the user to be able to, for example, jump
|
||||
// back to the first track from the last one with 1 button push
|
||||
next = fPlayList.GetFirstTrack();
|
||||
}
|
||||
|
||||
if(next > 0)
|
||||
{
|
||||
if (next > 0) {
|
||||
CDState state = fCDDrive.GetState();
|
||||
if(state == kPlaying)
|
||||
{
|
||||
while(!fCDDrive.Play(next))
|
||||
{
|
||||
if (state == kPlaying) {
|
||||
while(!fCDDrive.Play(next)) {
|
||||
next = fPlayList.GetNextTrack();
|
||||
if(next<1)
|
||||
{
|
||||
fWindowState=kStopped;
|
||||
if (next < 1) {
|
||||
fWindowState = kStopped;
|
||||
fCDDrive.Stop();
|
||||
fPlayList.Rewind();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if(state == kPaused)
|
||||
{
|
||||
else if (state == kPaused) {
|
||||
fCDDrive.Play(next);
|
||||
fCDDrive.Pause();
|
||||
}
|
||||
}
|
||||
else
|
||||
fPlayList.SetCurrentTrack(next);
|
||||
|
||||
@@ -337,41 +309,31 @@ CDPlayer::MessageReceived(BMessage *msg)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M_PREV_TRACK:
|
||||
{
|
||||
case M_PREV_TRACK: {
|
||||
int16 prev = fPlayList.GetPreviousTrack();
|
||||
if(prev <= 0)
|
||||
{
|
||||
if (prev <= 0) {
|
||||
// force a "wrap around" when possible. This makes it
|
||||
// possible for the user to be able to, for example, jump
|
||||
// back to the first track from the last one with 1 button push
|
||||
prev = fPlayList.GetLastTrack();
|
||||
}
|
||||
|
||||
if(prev > 0)
|
||||
{
|
||||
if (prev > 0) {
|
||||
CDState state = fCDDrive.GetState();
|
||||
if(state == kPlaying)
|
||||
{
|
||||
while(!fCDDrive.Play(prev))
|
||||
{
|
||||
if (state == kPlaying) {
|
||||
while(!fCDDrive.Play(prev)) {
|
||||
prev = fPlayList.GetPreviousTrack();
|
||||
if(prev < 1)
|
||||
{
|
||||
fWindowState=kStopped;
|
||||
if (prev < 1) {
|
||||
fWindowState = kStopped;
|
||||
fCDDrive.Stop();
|
||||
fPlayList.Rewind();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if(state == kPaused)
|
||||
{
|
||||
} else if (state == kPaused) {
|
||||
fCDDrive.Play(prev);
|
||||
fCDDrive.Pause();
|
||||
}
|
||||
else
|
||||
} else
|
||||
fPlayList.SetCurrentTrack(prev);
|
||||
|
||||
// Force an update for better responsiveness
|
||||
@@ -379,72 +341,55 @@ CDPlayer::MessageReceived(BMessage *msg)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M_FFWD:
|
||||
{
|
||||
if(fFastFwd->Value() == B_CONTROL_ON)
|
||||
case M_FFWD: {
|
||||
if (fFastFwd->Value() == B_CONTROL_ON)
|
||||
fCDDrive.StartFastFwd();
|
||||
else
|
||||
fCDDrive.StopFastFwd();
|
||||
break;
|
||||
}
|
||||
case M_REWIND:
|
||||
{
|
||||
if(fRewind->Value() == B_CONTROL_ON)
|
||||
case M_REWIND: {
|
||||
if (fRewind->Value() == B_CONTROL_ON)
|
||||
fCDDrive.StartRewind();
|
||||
else
|
||||
fCDDrive.StopRewind();
|
||||
break;
|
||||
}
|
||||
case M_SAVE:
|
||||
{
|
||||
// TODO: Implement
|
||||
break;
|
||||
}
|
||||
|
||||
case M_SHUFFLE:
|
||||
{
|
||||
if(fPlayList.IsShuffled())
|
||||
{
|
||||
case M_SHUFFLE: {
|
||||
if (fPlayList.IsShuffled()) {
|
||||
int16 track = fPlayList.GetCurrentTrack();
|
||||
fPlayList.SetShuffle(false);
|
||||
fPlayList.SetStartingTrack(track);
|
||||
fPlayList.SetTrackCount(fCDDrive.CountTracks());
|
||||
fShuffle->SetState(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
fPlayList.SetTrackCount(fCDDrive.CountTracks());
|
||||
fPlayList.SetShuffle(true);
|
||||
fShuffle->SetState(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M_REPEAT:
|
||||
{
|
||||
if(fPlayList.IsLoop())
|
||||
{
|
||||
case M_REPEAT: {
|
||||
if (fPlayList.IsLoop()) {
|
||||
fPlayList.SetLoop(false);
|
||||
fRepeat->SetState(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
fPlayList.SetLoop(true);
|
||||
fRepeat->SetState(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
default: {
|
||||
BView::MessageReceived(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CDPlayer::AttachedToWindow()
|
||||
{
|
||||
fVolumeSlider->SetTarget(this);
|
||||
fStop->SetTarget(this);
|
||||
fPlay->SetTarget(this);
|
||||
fNextTrack->SetTarget(this);
|
||||
@@ -452,38 +397,10 @@ CDPlayer::AttachedToWindow()
|
||||
fFastFwd->SetTarget(this);
|
||||
fRewind->SetTarget(this);
|
||||
fEject->SetTarget(this);
|
||||
fSave->SetTarget(this);
|
||||
fShuffle->SetTarget(this);
|
||||
fRepeat->SetTarget(this);
|
||||
fTrackMenu->SetTarget(this);
|
||||
}
|
||||
|
||||
void
|
||||
CDPlayer::FrameResized(float new_width, float new_height)
|
||||
{
|
||||
// We implement this method because there is no resizing mode to split the window's
|
||||
// width into two and have a box fill each half
|
||||
|
||||
// The boxes are laid out with 5 pixels between the window edge and each box.
|
||||
// Additionally, 15 pixels of padding are between the two boxes themselves
|
||||
|
||||
float half = (new_width / 2);
|
||||
|
||||
fCDBox->ResizeTo( half - 7, fCDBox->Bounds().Height() );
|
||||
fTrackMenu->ResizeTo( half - 7, fTrackMenu->Bounds().Height() );
|
||||
fTrackMenu->Invalidate();
|
||||
|
||||
fTrackBox->MoveTo(half + 8, fTrackBox->Frame().top);
|
||||
fTrackBox->ResizeTo( Bounds().right - (half + 8) - 5, fTrackBox->Bounds().Height() );
|
||||
|
||||
fTimeBox->MoveTo(half + 8, fTimeBox->Frame().top);
|
||||
fTimeBox->ResizeTo( Bounds().right - (half + 8) - 5, fTimeBox->Bounds().Height() );
|
||||
|
||||
fRepeat->MoveTo(new_width - fRepeat->Bounds().right - 5, fRepeat->Frame().top);
|
||||
|
||||
fShuffle->MoveTo(fRepeat->Frame().left - fShuffle->Bounds().Width() - 2, fShuffle->Frame().top);
|
||||
fSave->MoveTo(fShuffle->Frame().left - fSave->Bounds().Width() - 2, fSave->Frame().top);
|
||||
}
|
||||
|
||||
void
|
||||
CDPlayer::Pulse()
|
||||
@@ -491,127 +408,95 @@ CDPlayer::Pulse()
|
||||
WatchCDState();
|
||||
}
|
||||
|
||||
void CDPlayer::WatchCDState(void)
|
||||
|
||||
void
|
||||
CDPlayer::WatchCDState(void)
|
||||
{
|
||||
// One watcher function to rule them all
|
||||
|
||||
// first, watch the one setting independent of having a CD: volume
|
||||
uint8 drivevolume = fCDDrive.GetVolume();
|
||||
if(fVolume == drivevolume)
|
||||
{
|
||||
fVolume=drivevolume;
|
||||
if (fVolume == drivevolume) {
|
||||
fVolume = drivevolume;
|
||||
fVolumeSlider->SetValue(fVolume);
|
||||
}
|
||||
|
||||
// Second, establish whether or not we have a CD in the drive
|
||||
CDState playstate = fCDDrive.GetState();
|
||||
bool internal_track_change=false;
|
||||
bool internal_track_change = false;
|
||||
|
||||
if(playstate == kNoCD)
|
||||
{
|
||||
if (playstate == kNoCD) {
|
||||
// Yes, we have no bananas!
|
||||
|
||||
if(fWindowState != kNoCD)
|
||||
{
|
||||
// Do something only if there is a change in the app's play state
|
||||
if (fWindowState != kNoCD) {
|
||||
// We have just discovered that we have no bananas
|
||||
fWindowState = kNoCD;
|
||||
|
||||
// Because we are changing play states, we will need to update the GUI
|
||||
|
||||
fCDData.SetDiscID(-1);
|
||||
fCDTitle->SetText("No CD");
|
||||
SetLabel(fCDTitle,"CD drive is empty");
|
||||
|
||||
fCurrentTrack->SetText("");
|
||||
fCurrentTrack->SetHighColor(fStopColor);
|
||||
fCurrentTrack->Invalidate();
|
||||
fTrackMenu->SetItemCount(0);
|
||||
fTrackTime->SetText("Track --:-- / --:--");
|
||||
fDiscTime->SetText("Disc --:-- / --:--");
|
||||
SetLabel(fCurrentTrack,"");
|
||||
SetLabel(fTrackTime,"Track: --:-- / --:--");
|
||||
SetLabel(fDiscTime,"Disc: --:-- / --:--");
|
||||
fPlayList.SetTrackCount(0);
|
||||
fPlayList.SetStartingTrack(1);
|
||||
fPlayList.SetCurrentTrack(1);
|
||||
|
||||
if(fPlay->GetState()==1)
|
||||
if (fPlay->GetState() == 1)
|
||||
fPlay->SetState(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No change in the app's play state, so do nothing
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Now otherwise handle the play state
|
||||
if(playstate == kStopped)
|
||||
{
|
||||
if(fWindowState == kPlaying)
|
||||
{
|
||||
internal_track_change=true;
|
||||
if (playstate == kStopped) {
|
||||
if (fWindowState == kPlaying) {
|
||||
internal_track_change = true;
|
||||
|
||||
// This means that the drive finished playing the song, so get the next one
|
||||
// from the list and play it
|
||||
int16 next = fPlayList.GetNextTrack();
|
||||
if(next > 0)
|
||||
if (next > 0)
|
||||
fCDDrive.Play(next);
|
||||
}
|
||||
|
||||
if(fPlay->GetState()==1)
|
||||
{
|
||||
if (fPlay->GetState() == 1)
|
||||
fPlay->SetState(0);
|
||||
fCurrentTrack->SetHighColor(fStopColor);
|
||||
fCurrentTrack->Invalidate();
|
||||
}
|
||||
}
|
||||
else
|
||||
if(playstate == kPlaying)
|
||||
{
|
||||
if(fPlay->GetState()==0)
|
||||
} else if (playstate == kPlaying) {
|
||||
if (fPlay->GetState() == 0)
|
||||
fPlay->SetState(1);
|
||||
fCurrentTrack->SetHighColor(fPlayColor);
|
||||
fCurrentTrack->Invalidate();
|
||||
}
|
||||
else
|
||||
if(playstate == kPaused)
|
||||
{
|
||||
} else if (playstate == kPaused) {
|
||||
fPlay->SetState(0);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// If we got this far, then there must be a CD in the drive. The next order on the agenda
|
||||
// is to find out which CD it is
|
||||
int32 discid = fCDDrive.GetDiscID();
|
||||
bool update_track_gui=false;
|
||||
bool update_track_gui = false;
|
||||
|
||||
if(discid != fCDData.DiscID())
|
||||
{
|
||||
if (discid != fCDData.DiscID()) {
|
||||
update_track_gui = true;
|
||||
|
||||
// Apparently the disc has changed since we last looked.
|
||||
if(fCDQuery.CurrentDiscID()!=discid)
|
||||
{
|
||||
if (fCDQuery.CurrentDiscID() != discid)
|
||||
fCDQuery.SetToCD(fCDDrive.GetDrivePath());
|
||||
}
|
||||
|
||||
if(fCDQuery.Ready())
|
||||
{
|
||||
if (fCDQuery.Ready()) {
|
||||
// Note that we only update the CD title for now. We still need a track number
|
||||
// in order to update the display for the selected track
|
||||
if(fCDQuery.GetData(&fCDData, 1000000))
|
||||
{
|
||||
if (fCDQuery.GetData(&fCDData, 1000000)) {
|
||||
BString display(fCDData.Artist());
|
||||
display << " - " << fCDData.Album();
|
||||
fCDTitle->SetText(display.String());
|
||||
}
|
||||
else
|
||||
{
|
||||
fCDTitle->SetText("Audio CD");
|
||||
SetLabel(fCDTitle,display.String());
|
||||
} else {
|
||||
SetLabel(fCDTitle,"Audio CD");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Now that we know which CD it is, update the track info
|
||||
int16 drivecount = fCDDrive.CountTracks();
|
||||
int16 drivetrack = fCDDrive.GetTrack();
|
||||
@@ -619,14 +504,11 @@ void CDPlayer::WatchCDState(void)
|
||||
int16 playlisttrack = fPlayList.GetCurrentTrack();
|
||||
int16 playlistcount = fPlayList.TrackCount();
|
||||
|
||||
if(playstate == kPlaying)
|
||||
{
|
||||
if(playlisttrack != drivetrack)
|
||||
{
|
||||
if (playstate == kPlaying) {
|
||||
if (playlisttrack != drivetrack) {
|
||||
playlisttrack = drivetrack;
|
||||
|
||||
if(!internal_track_change)
|
||||
{
|
||||
if (!internal_track_change) {
|
||||
// The main thing is that we need to make sure that the playlist and the drive's track
|
||||
// stay in sync. The CD's track may have been changed by an outside source, so if
|
||||
// the drive is playing, check for playlist sync.
|
||||
@@ -634,75 +516,51 @@ void CDPlayer::WatchCDState(void)
|
||||
fPlayList.SetCurrentTrack(drivetrack);
|
||||
}
|
||||
}
|
||||
update_track_gui=true;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if(playlistcount != drivecount)
|
||||
{
|
||||
update_track_gui = true;
|
||||
} else {
|
||||
if (playlistcount != drivecount) {
|
||||
// This happens only when CDs are changed
|
||||
if(drivecount<0)
|
||||
{
|
||||
if (drivecount < 0) {
|
||||
// There is no CD in the drive. The playlist needs to have its track
|
||||
// count set to 0 and it also needs to be rewound.
|
||||
fPlayList.SetStartingTrack(1);
|
||||
fPlayList.SetTrackCount(0);
|
||||
playlisttrack=1;
|
||||
playlistcount=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
playlisttrack = 1;
|
||||
playlistcount = 0;
|
||||
} else {
|
||||
// Two possible cases here: playlist is empty or playlist has a different
|
||||
// number of tracks. In either case, the playlist needs to be reinitialized
|
||||
// to the current track data
|
||||
fPlayList.SetStartingTrack(1);
|
||||
fPlayList.SetTrackCount(drivecount);
|
||||
playlisttrack=fPlayList.GetCurrentTrack();
|
||||
playlistcount=drivecount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// CD has not changed, so check for change in tracks
|
||||
if(playlisttrack != drivetrack)
|
||||
{
|
||||
update_track_gui=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// do nothing. Everything is hunky-dory
|
||||
playlisttrack = fPlayList.GetCurrentTrack();
|
||||
playlistcount = drivecount;
|
||||
}
|
||||
} else {
|
||||
// update only with a track change
|
||||
if (playlisttrack != drivetrack)
|
||||
update_track_gui = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(update_track_gui)
|
||||
{
|
||||
if (update_track_gui) {
|
||||
BString currentTrackName;
|
||||
|
||||
if(playlisttrack >= 0)
|
||||
{
|
||||
if (playlisttrack >= 0) {
|
||||
int16 whichtrack = playlisttrack;
|
||||
|
||||
if(whichtrack == 0)
|
||||
if (whichtrack == 0)
|
||||
whichtrack++;
|
||||
|
||||
currentTrackName << "Track " << whichtrack << ": " << fCDData.TrackAt(whichtrack-1);
|
||||
|
||||
fCurrentTrack->SetText(currentTrackName.String());
|
||||
SetLabel(fCurrentTrack,currentTrackName.String());
|
||||
|
||||
fTrackMenu->SetItemCount(playlistcount);
|
||||
fTrackMenu->SetValue(playlisttrack-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
fCurrentTrack->SetText("");
|
||||
fTrackMenu->SetItemCount(0);
|
||||
fTrackMenu->SetValue(1);
|
||||
} else {
|
||||
SetLabel(fCurrentTrack,"");
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// Now update the time info
|
||||
cdaudio_time tracktime;
|
||||
cdaudio_time disctime;
|
||||
@@ -710,63 +568,54 @@ void CDPlayer::WatchCDState(void)
|
||||
cdaudio_time disctotal;
|
||||
char timestring[1024];
|
||||
|
||||
if(fCDDrive.GetTime(tracktime, disctime))
|
||||
{
|
||||
if (fCDDrive.GetTime(tracktime, disctime)) {
|
||||
fCDDrive.GetTimeForDisc(disctotal);
|
||||
sprintf(timestring,"Disc %ld:%.2ld / %ld:%.2ld",disctime.minutes,disctime.seconds,
|
||||
sprintf(timestring,"Disc: %ld:%.2ld / %ld:%.2ld",disctime.minutes,disctime.seconds,
|
||||
disctotal.minutes,disctotal.seconds);
|
||||
fDiscTime->SetText(timestring);
|
||||
SetLabel(fDiscTime,timestring);
|
||||
|
||||
fCDDrive.GetTimeForTrack(playlisttrack,tracktotal);
|
||||
sprintf(timestring,"Track %ld:%.2ld / %ld:%.2ld",tracktime.minutes,tracktime.seconds,
|
||||
sprintf(timestring,"Track: %ld:%.2ld / %ld:%.2ld",tracktime.minutes,tracktime.seconds,
|
||||
tracktotal.minutes,tracktotal.seconds);
|
||||
fTrackTime->SetText(timestring);
|
||||
}
|
||||
else
|
||||
{
|
||||
fTrackTime->SetText("Track --:-- / --:--");
|
||||
fDiscTime->SetText("Disc --:-- / --:--");
|
||||
SetLabel(fTrackTime,timestring);
|
||||
} else {
|
||||
SetLabel(fTrackTime,"Track: --:-- / --:--");
|
||||
SetLabel(fDiscTime,"Disc: --:-- / --:--");
|
||||
}
|
||||
}
|
||||
|
||||
class CDPlayerWindow : public BWindow
|
||||
{
|
||||
public:
|
||||
CDPlayerWindow(void);
|
||||
bool QuitRequested(void);
|
||||
};
|
||||
|
||||
CDPlayerWindow::CDPlayerWindow(void)
|
||||
: BWindow(BRect (100, 100, 610, 200), "CD Player", B_TITLED_WINDOW, B_NOT_V_RESIZABLE |
|
||||
B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS)
|
||||
: BWindow(BRect (100, 100, 405, 280), "CD Player", B_TITLED_WINDOW, B_NOT_RESIZABLE |
|
||||
B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS)
|
||||
{
|
||||
float wmin,wmax,hmin,hmax;
|
||||
|
||||
GetSizeLimits(&wmin,&wmax,&hmin,&hmax);
|
||||
wmin=510;
|
||||
hmin=100;
|
||||
SetSizeLimits(wmin,wmax,hmin,hmax);
|
||||
}
|
||||
|
||||
bool CDPlayerWindow::QuitRequested(void)
|
||||
|
||||
bool
|
||||
CDPlayerWindow::QuitRequested(void)
|
||||
{
|
||||
be_app->PostMessage(B_QUIT_REQUESTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
CDPlayerApplication::CDPlayerApplication()
|
||||
: BApplication("application/x-vnd.Haiku-CDPlayer")
|
||||
: BApplication("application/x-vnd.Haiku-CDPlayer")
|
||||
{
|
||||
BWindow *window = new CDPlayerWindow();
|
||||
BView *button = new CDPlayer(window->Bounds(), "CD");
|
||||
window->AddChild(button);
|
||||
BView *view = new CDPlayer(window->Bounds(), "CD");
|
||||
window->ResizeTo(view->Bounds().Width(), view->Bounds().Height());
|
||||
window->AddChild(view);
|
||||
window->Show();
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int, char **argv)
|
||||
{
|
||||
(new CDPlayerApplication())->Run();
|
||||
CDPlayerApplication app;
|
||||
app.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user