Waveform: Commit a hack-ey for the a/b loop to get updated.
[stretchplayer:stretchplayer.git] / src / PlayerWidget.cpp
1 /*
2  * Copyright(c) 2009 by Gabriel M. Beddingfield <gabriel@teuton.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY, without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  *
18  */
19
20 #include "PlayerWidget.hpp"
21 #include "Engine.hpp"
22 #include "StatusWidget.hpp"
23 #include "Configuration.hpp"
24
25 #include <QWidget>
26 #include <QToolButton>
27 #include <QLabel>
28 #include <QVBoxLayout>
29 #include <QHBoxLayout>
30 #include <QSlider>
31 #include <QFont>
32 #include <QTimer>
33 #include <QSpinBox>
34 #include <QFileDialog>
35 #include <QPainter>
36 #include <QBitmap>
37 #include <QAction>
38 #include <QResizeEvent>
39 #include <QCoreApplication>
40
41 #include <cmath>
42 #include "config.h"
43
44 #include <iostream>
45 using namespace std;
46
47 namespace StretchPlayer
48 {
49     namespace Details
50     {
51
52         static QString slider_stylesheet(int grid_pixels)
53         {
54             static const char css[] =
55                 "QSlider {\n"
56                 "    border-radius: 6px;\n"
57                 "}\n"
58                 "\n"
59                 "QSlider::groove {\n"
60                 "    border: 0px solid #000000;\n"
61                 "    background: #000000;\n"
62                 "}\n"
63                 "\n"
64                 "QSlider::groove:horizontal {\n"
65                 "    height: 2px;\n"
66                 "    margin-left: 10px;\n"
67                 "    margin-right: 10px;\n"
68                 "}\n"
69                 "\n"
70                 "QSlider::groove:vertical {\n"
71                 "    width: 2px;\n"
72                 "    margin-top: 10px;\n"
73                 "    margin-bottom: 10px;\n"
74                 "}\n"
75                 "\n"
76                 "QSlider::handle {\n"
77                 "    image: url(:img/circle.png);\n"
78                 "    width: %1px;\n"
79                 "    height: %1px;\n"
80                 "}\n"
81                 "QSlider::handle:horizontal {\n"
82                 "    margin-top: -%2px;\n"
83                 "    margin-bottom: -%2px;\n"
84                 "}\n"
85                 "QSlider::handle:vertical {\n"
86                 "    margin-left: -%2px;\n"
87                 "    margin-right: -%2px;\n"
88                 "}\n";
89             int size, margin;
90             size = float(grid_pixels)*2.0/3.0;
91             margin = float(size)*.44;
92
93             return QString(css).arg(size).arg(margin);
94         }
95
96         class PlayerWidgetMessageCallback : public EngineMessageCallback
97         {
98         public:
99             PlayerWidgetMessageCallback(PlayerWidget* w) : _widget(w)
100                 {}
101             virtual ~PlayerWidgetMessageCallback() {}
102
103             virtual void operator()(const QString& msg) {
104                 _widget->status_message(msg);
105                 QCoreApplication::processEvents();
106             }
107         private:
108             PlayerWidget* _widget;
109         };
110
111     }
112
113     PlayerWidget::PlayerWidget(Configuration *config, QWidget *parent)
114         : QMainWindow(parent),
115           _config(config)
116           
117     {
118         setWindowFlags( Qt::Window
119                         | Qt::FramelessWindowHint
120             );
121
122         if( _config->compositing() && (QT_VERSION >= 0x040500) ) {
123             setAttribute( Qt::WA_TranslucentBackground );
124             _compositing = true;
125         } else {
126             _compositing = false;
127         }
128
129         setMinimumSize(_sizes.preferred_width()*2/3, _sizes.preferred_height()*2/3);
130
131         QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Fixed);
132         policy.setHeightForWidth(true);
133         setSizePolicy(policy);
134
135         setMouseTracking(true);
136
137         resize(_sizes.preferred_width(), _sizes.preferred_height());
138
139         _setup_color_scheme(0);
140         _load_icons();
141         _setup_actions();
142         _setup_widgets();
143         _setup_signals_and_slots();
144         _layout_widgets();
145     }
146
147     PlayerWidget::~PlayerWidget()
148     {
149     }
150
151     void PlayerWidget::load_song(const QString& filename)
152     {
153         QString name = _engine->load_song(filename);
154         if(name.isEmpty()) {
155             _status->song_name("No song loaded.");
156         } else {
157             _status->song_name(name);
158         }
159     }
160
161     int PlayerWidget::heightForWidth(int w)
162     {
163         return _sizes.height_for_width(w);
164     }
165
166     void PlayerWidget::play_pause()
167     {
168         _engine->play_pause();
169     }
170
171     void PlayerWidget::stop()
172     {
173         _engine->stop();
174         _engine->locate(0);
175     }
176
177     void PlayerWidget::ab()
178     {
179         _engine->loop_ab();
180     }
181
182     void PlayerWidget::open_file()
183     {
184         _engine->stop();
185         QString filename = QFileDialog::getOpenFileName(
186             this,
187             "Open song file..."
188             );
189         if( ! filename.isNull() ) {
190             load_song(filename);
191         }
192     }
193
194     void PlayerWidget::status_message(const QString& msg) {
195         _status->message(msg);
196     }
197
198     void PlayerWidget::locate(float pos)
199     {
200         _engine->locate(pos * _engine->get_length());
201     }
202
203     void PlayerWidget::pitch_inc()
204     {
205         _engine->set_pitch( _engine->get_pitch() + 1 );
206     }
207
208     void PlayerWidget::pitch_dec()
209     {
210         _engine->set_pitch( _engine->get_pitch() - 1);
211     }
212
213     void PlayerWidget::speed_inc()
214     {
215         _engine->set_stretch( _engine->get_stretch() + .05 );
216     }
217
218     void PlayerWidget::speed_dec()
219     {
220         _engine->set_stretch( _engine->get_stretch() - .05 );
221     }
222
223     void PlayerWidget::volume_inc()
224     {
225         float gain = _from_fader(_volume->value() + 50);
226         _engine->set_volume( gain );
227     }
228
229     void PlayerWidget::volume_dec()
230     {
231         float gain = _from_fader(_volume->value() - 50);
232         _engine->set_volume( gain );
233     }
234
235     void PlayerWidget::stretch(int pos)
236     {
237         _engine->set_stretch( 0.5 + double(pos)/1000.0 );
238     }
239
240     void PlayerWidget::volume(int vol)
241     {
242         _engine->set_volume( _from_fader(vol) );
243     }
244
245     /**
246      * "Traditional" fader mapping
247      */
248     float PlayerWidget::_from_fader(int p_val)
249     {
250         float fader = float(p_val)/1000.0f;
251         float gain;
252         float db;
253
254         if(fader == 0) {
255             gain = 0.0f;
256         } else if(fader < .04) {
257             gain = fader * 1e-6f / .04f;
258         } else if(fader < .16) {
259             db = -60.0 + 20.0f * (fader-.04) / .12f;
260             gain = exp10(db/10.0);
261         } else if(fader < .52) {
262             db = -40.0 + 10.0f * (fader-.16) / .12f;
263             gain = exp10(db/10.0);
264         } else {
265             db = -10.0 + 15.0f * (fader-.52) / .48f;
266             gain = exp10(db/10.0);
267         }
268
269         return gain;
270     }
271
272     /**
273      * "Traditional" fader mapping
274      */
275     int PlayerWidget::_to_fader(float gain)
276     {
277         if(gain == 0.0f) return 0;
278
279         float fader;
280         float db = 10.0 * log10(gain);
281
282         if(db < -60.0) {
283             fader = gain * .04f / 1e-6f;
284         } else if(db < -40.0) {
285             fader = .04f + ((db + 60.0f) * .12f / 20.0f);
286         } else if(db < -10.0) {
287             fader = .16f + ((db + 40.0f) * .12f / 10.0f);
288         } else {
289             fader = .52f + ((db + 10.0f) * .48f / 15.0f);
290         }
291
292         if( fader > 1.0 ) fader = 1.0f;
293
294         return ::round(fader * 1000.0);
295     }
296
297     void PlayerWidget::reset()
298     {
299         stop();
300         _engine->set_pitch(0);
301         _engine->set_stretch(1.0);
302     }
303
304     void PlayerWidget::update_time()
305     {
306         float pos = _engine->get_position();
307         _status->time(pos);
308
309         float len = _engine->get_length();
310         _status->position(pos/len);
311
312         float loop_a = _engine->get_loop_a();
313         float loop_b = _engine->get_loop_b();
314
315         if( loop_b && loop_a && (loop_b > loop_a) ) {
316             const float *left, *right;
317             unsigned long count, off_a, off_b, clip_offset;
318             float margin = float(loop_b - loop_a) * .10f;
319
320             _engine->get_audio_range(loop_a - margin,
321                                      loop_b + margin,
322                                      &left,
323                                      &right,
324                                      &count,
325                                      &clip_offset);
326             off_a = float(count) * (margin / (2*margin + loop_b - loop_a)) + 0.5;
327             off_b = count - off_a;
328             _status->set_loop_audio(left, right, count, off_a, off_b, clip_offset, _engine.get());
329         }
330         _status->loop_ab(loop_a/len, loop_b/len);
331
332         float sch = _engine->get_stretch();
333         _status->speed(sch);
334
335         int pit = _engine->get_pitch();
336         _status->pitch(pit);
337
338         float cpu = _engine->get_cpu_load();
339         _status->cpu(cpu);
340
341         float vol = _engine->get_volume();
342         _volume->setValue( _to_fader(vol) );
343         _status->volume( _volume->value() / 1000.0 );
344
345         _stretch->setValue( (sch-0.5) * 1000 );
346         _status->update();
347     }
348
349     void PlayerWidget::resizeEvent(QResizeEvent * /*event*/)
350     {
351         QString css;
352         _sizes.set_scale_from(width(), height());
353         css = Details::slider_stylesheet(_sizes.widget_grid_size());
354         _stretch->setStyleSheet(css);
355         _volume->setStyleSheet(css);
356         _layout_widgets();
357     }
358
359     void PlayerWidget::paintEvent(QPaintEvent * event)
360     {
361         QPainter painter(this);
362         painter.setRenderHints(QPainter::Antialiasing);
363
364         const QPalette& pal = palette();
365
366         float thickline = _sizes.thicker_line();
367         float border_rad = thickline * 4.0;
368
369         float w = width();
370         float h = height();
371
372         if(_compositing) {
373             QImage mask_img(width(), height(), QImage::Format_Mono);
374             mask_img.fill(0xff);
375             QPainter mask_ptr(&mask_img);
376             mask_ptr.setBrush( QBrush( QColor(0, 0, 0) ) );
377             mask_ptr.drawRoundedRect( QRectF( 0, 0, w, h),
378                                       border_rad+thickline/2.0,
379                                       border_rad+thickline/2.0 );
380             QBitmap bmp = QBitmap::fromImage(mask_img);
381             setMask( bmp );
382         }
383
384         QBrush bg_brush( pal.color(QPalette::Active, QPalette::Window) );
385         QPen border_pen( pal.color(QPalette::Active, QPalette::Dark) );
386
387         border_pen.setWidthF(thickline);
388         border_pen.setJoinStyle(Qt::RoundJoin);
389         painter.setPen(border_pen);
390         QRectF bg_rect = QRectF( thickline/2.0,
391                                  thickline/2.0,
392                                  w-thickline,
393                                  h-thickline );
394         if(!_compositing) {
395             painter.setBrush( pal.color(QPalette::Active, QPalette::Dark) );
396             painter.drawRect( 0, 0, width(), height() );
397         }
398         painter.setBrush(bg_brush);
399         painter.drawRoundedRect( bg_rect,
400                                  border_rad,
401                                  border_rad );
402
403         QWidget::paintEvent(event);
404     }
405
406     void PlayerWidget::mousePressEvent(QMouseEvent *event)
407     {
408         Qt::CursorShape loc = _which_cursor(event->pos());
409         Qt::CursorShape cur = cursor().shape();
410         if( cur != loc ) {
411             QCursor new_cur(loc);
412             setCursor(new_cur);
413             cur = loc;
414             assert( cur == cursor().shape() );
415         }
416         if(event->button() & Qt::LeftButton) {
417             switch(cur) {
418             case Qt::SizeAllCursor:
419                 _anchor = event->globalPos();
420             case Qt::SizeHorCursor:
421             case Qt::SizeVerCursor:
422             case Qt::SizeFDiagCursor:
423             case Qt::SizeBDiagCursor:
424                 event->accept();
425                 _drag_resize(cur, event);
426                 break;
427             default:
428                 event->ignore();
429             }
430         } else {
431             event->ignore();
432         }
433     }
434
435     void PlayerWidget::mouseMoveEvent(QMouseEvent *event)
436     {
437         Qt::CursorShape cur = cursor().shape();
438         Qt::CursorShape loc = _which_cursor(event->pos());
439         if( (event->buttons() & Qt::LeftButton)
440             && (cur != Qt::ArrowCursor) ) {
441             _drag_resize(cur, event);
442         } else if( cur != loc ) {
443             event->accept();
444             QCursor new_cur(loc);
445             setCursor(new_cur);
446         } else {
447             event->ignore();
448         }
449     }
450
451     void PlayerWidget::_setup_color_scheme(int profile)
452     {
453         QPalette p;
454         QColor base, bright, light, mid, dark;
455
456         switch(profile) {
457         // case 0: // default
458         case 1: // Blue
459             bright.setRgb(0xff, 0xff, 0xff, 0xff); // white
460             light.setRgb(0x76, 0xc6, 0xf5, 0xff); // light blue
461             mid.setRgb(68, 141, 189, 0xff); // average
462             dark.setRgb(0x12, 0x55, 0x85, 0xff); // dark blue
463             base = light;
464             break;
465         default: // Yellow
466             bright.setRgb(0xff, 0xff, 0xff, 0xff); //white
467             light.setRgb(0xe5, 0xd7, 0x3a, 0xff); // yellow
468             mid.setRgb(114, 107, 29, 0xff); // average
469             dark.setRgb(0, 0, 0, 0xff); // black
470             base = light;
471         }
472
473         p.setColorGroup(QPalette::Active,
474                         dark, // Window Text
475                         light, // button
476                         light, // light
477                         dark, // dark
478                         mid, // mid
479                         dark, // text
480                         bright, // bright text
481                         base, // base
482                         light // window
483             );
484
485         setPalette(p);
486     }
487
488     void PlayerWidget::_load_icons()
489     {
490         _ico.play.addFile(":img/play.png");
491         _ico.stop.addFile(":img/stop.png");
492         _ico.ab.addFile(":img/ab.png");
493         _ico.help.addFile(":img/help.png");
494         _ico.quit.addFile(":img/quit.png");
495         _ico.plus.addFile(":img/plus.png");
496         _ico.minus.addFile(":img/minus.png");
497         _ico.open.addFile(":img/file.png");
498     }
499
500     void PlayerWidget::_setup_actions()
501     {
502         memset(&_act, 0, sizeof(_act));
503
504         _act.play_pause = new QAction("P", this);
505         _act.play_pause->setToolTip("Play/Pause [Space]");
506         _act.play_pause->setShortcut(Qt::Key_Space);
507         _act.play_pause->setShortcutContext(Qt::ApplicationShortcut);
508         _act.play_pause->setIcon( _ico.play );
509         addAction(_act.play_pause);
510         connect(_act.play_pause, SIGNAL(triggered()),
511                 this, SLOT(play_pause()));
512
513         _act.stop = new QAction("S", this);
514         _act.stop->setToolTip("Stop [S]");
515         _act.stop->setShortcut(Qt::Key_S);
516         _act.stop->setShortcutContext(Qt::ApplicationShortcut);
517         _act.stop->setIcon( _ico.stop );
518         addAction(_act.stop);
519         connect(_act.stop, SIGNAL(triggered()),
520                 this, SLOT(stop()));
521
522         QList<QKeySequence> ab_shortcuts;
523         ab_shortcuts << Qt::Key_Enter;
524         ab_shortcuts << Qt::Key_Return;
525         _act.ab = new QAction("AB", this);
526         _act.ab->setToolTip("AB Repeat [Enter]");
527         _act.ab->setShortcuts(ab_shortcuts);
528         _act.ab->setShortcutContext(Qt::ApplicationShortcut);
529         _act.ab->setIcon( _ico.ab );
530         addAction(_act.ab);
531         connect(_act.ab, SIGNAL(triggered()),
532                 this, SLOT(ab()));
533
534         _act.open = new QAction("O", this);
535         _act.open->setToolTip("Open [O]");
536         _act.open->setShortcut(Qt::Key_O);
537         _act.open->setShortcutContext(Qt::ApplicationShortcut);
538         _act.open->setIcon( _ico.open );
539         addAction(_act.open);
540         connect(_act.open, SIGNAL(triggered()),
541                 this, SLOT(open_file()));
542
543         _act.quit = new QAction("X", this);
544         _act.quit->setToolTip("Quit [Esc]");
545         _act.quit->setShortcut(Qt::Key_Escape);
546         _act.quit->setShortcutContext(Qt::ApplicationShortcut);
547         _act.quit->setIcon( _ico.quit );
548         addAction(_act.quit);
549         connect(_act.quit, SIGNAL(triggered()),
550                 this, SLOT(close()));
551
552         QList<QKeySequence> inc_shortcuts;
553         inc_shortcuts << Qt::Key_Plus;
554         inc_shortcuts << Qt::Key_Equal;
555         _act.pitch_inc = new QAction("+", this);
556         _act.pitch_inc->setToolTip("Pitch Increase [+]");
557         _act.pitch_inc->setShortcuts(inc_shortcuts);
558         _act.pitch_inc->setShortcutContext(Qt::ApplicationShortcut);
559         _act.pitch_inc->setIcon( _ico.plus );
560         addAction(_act.pitch_inc);
561         connect(_act.pitch_inc, SIGNAL(triggered()),
562                 this, SLOT(pitch_inc()));
563
564         _act.pitch_dec = new QAction("-", this);
565         _act.pitch_dec->setToolTip("Pitch Decrease [-]");
566         _act.pitch_dec->setShortcut(Qt::Key_Minus);
567         _act.pitch_dec->setShortcutContext(Qt::ApplicationShortcut);
568         _act.pitch_dec->setIcon( _ico.minus );
569         addAction(_act.pitch_dec);
570         connect(_act.pitch_dec, SIGNAL(triggered()),
571                 this, SLOT(pitch_dec()));
572
573         _act.speed_inc = new QAction("Faster", this);
574         _act.speed_inc->setToolTip("Play faster [Right Arrow]");
575         _act.speed_inc->setShortcut(Qt::Key_Right);
576         _act.speed_inc->setShortcutContext(Qt::ApplicationShortcut);
577         addAction(_act.speed_inc);
578         connect(_act.speed_inc, SIGNAL(triggered()),
579                 this, SLOT(speed_inc()));
580
581         _act.speed_dec = new QAction("Slower", this);
582         _act.speed_dec->setToolTip("Play slower [Left Arrow]");
583         _act.speed_dec->setShortcut(Qt::Key_Left);
584         _act.speed_dec->setShortcutContext(Qt::ApplicationShortcut);
585         addAction(_act.speed_dec);
586         connect(_act.speed_dec, SIGNAL(triggered()),
587                 this, SLOT(speed_dec()));
588
589         _act.vol_inc = new QAction("Louder", this);
590         _act.vol_inc->setToolTip("Louder [Up]");
591         _act.vol_inc->setShortcut(Qt::Key_Up);
592         _act.vol_inc->setShortcutContext(Qt::ApplicationShortcut);
593         addAction(_act.vol_inc);
594         connect(_act.vol_inc, SIGNAL(triggered()),
595                 this, SLOT(volume_inc()));
596
597         _act.vol_dec = new QAction("Louder", this);
598         _act.vol_dec->setToolTip("Quieter [Down]");
599         _act.vol_dec->setShortcut(Qt::Key_Down);
600         _act.vol_dec->setShortcutContext(Qt::ApplicationShortcut);
601         addAction(_act.vol_dec);
602         connect(_act.vol_dec, SIGNAL(triggered()),
603                 this, SLOT(volume_dec()));
604
605         _act.reset = new QAction("Reset", this);
606         _act.reset->setToolTip("Reset all settings [Home]");
607         _act.reset->setShortcut(Qt::Key_Home);
608         _act.reset->setShortcutContext(Qt::ApplicationShortcut);
609         addAction(_act.reset);
610         connect(_act.reset, SIGNAL(triggered()),
611                 this, SLOT(reset()));
612     }
613
614     void PlayerWidget::_setup_widgets()
615     {
616         _btn.play = new QToolButton(this);
617         _btn.play->setDefaultAction(_act.play_pause);
618         _btn.play->setAutoRaise(true);
619         _btn.play->setContentsMargins(0, 0, 0, 0);
620         _btn.play->setMaximumSize(256, 256);
621
622         _btn.stop = new QToolButton(this);
623         _btn.stop->setDefaultAction(_act.stop);
624         _btn.stop->setAutoRaise(true);
625         _btn.stop->setContentsMargins(0, 0, 0, 0);
626
627         _btn.ab = new QToolButton(this);
628         _btn.ab->setDefaultAction(_act.ab);
629         _btn.ab->setAutoRaise(true);
630         _btn.ab->setContentsMargins(0, 0, 0, 0);
631
632         _btn.open = new QToolButton(this);
633         _btn.open->setDefaultAction(_act.open);
634         _btn.open->setAutoRaise(true);
635         _btn.open->setContentsMargins(0, 0, 0, 0);
636
637         _btn.quit = new QToolButton(this);
638         _btn.quit->setDefaultAction(_act.quit);
639         _btn.quit->setAutoRaise(true);
640         _btn.quit->setContentsMargins(0, 0, 0, 0);
641
642         _btn.pitch_inc = new QToolButton(this);
643         _btn.pitch_inc->setDefaultAction(_act.pitch_inc);
644         _btn.pitch_inc->setAutoRaise(true);
645
646         _btn.pitch_dec = new QToolButton(this);
647         _btn.pitch_dec->setDefaultAction(_act.pitch_dec);
648         _btn.pitch_dec->setAutoRaise(true);
649
650         _status = new StatusWidget(this, &_sizes);
651
652         QString css = Details::slider_stylesheet(_sizes.widget_grid_size());
653         _stretch = new QSlider(Qt::Horizontal, this);
654         _stretch->setMinimum(0);
655         _stretch->setMaximum(1000);
656         _stretch->setToolTip("Playback Speed [Left/Right Arrow]");
657         _stretch->setStyleSheet(css);
658
659         _volume = new QSlider(Qt::Vertical, this);
660         _volume->setMinimum(0);
661         _volume->setMaximum(1000);
662         _volume->setToolTip("Volume [Up/Down]");
663         _volume->setStyleSheet(css);
664     }
665
666     void PlayerWidget::_layout_widgets()
667     {
668         int h, w;
669         int margin;
670         int grid;
671
672         h = height();
673         w = width();
674         grid = _sizes.widget_grid_size() + .5;
675         margin = _margin();
676
677         QSize grid_size(grid, grid);
678         int n_ctrl_btns = 6;
679
680         int btn_y = h - margin - grid;
681         int btn_x = margin;
682
683         // CONTROL BAR (BOTTOM)
684         _btn.play->setIconSize(grid_size);
685         _btn.play->setGeometry( btn_x, btn_y, grid, grid );
686         btn_x += grid;
687
688         _btn.stop->setIconSize(grid_size);
689         _btn.stop->setGeometry( btn_x, btn_y, grid, grid );
690         btn_x += grid;
691
692         _btn.ab->setIconSize(grid_size);
693         _btn.ab->setGeometry( btn_x, btn_y, grid, grid );
694         btn_x += grid;
695
696         _stretch->setGeometry( btn_x,
697                                btn_y,
698                                w - 2*margin - n_ctrl_btns*grid,
699                                grid );
700         btn_x += _stretch->width();
701
702         _btn.pitch_dec->setIconSize(grid_size);
703         _btn.pitch_dec->setGeometry( btn_x, btn_y, grid, grid );
704         btn_x += grid;
705
706         _btn.pitch_inc->setIconSize(grid_size);
707         _btn.pitch_inc->setGeometry( btn_x, btn_y, grid, grid );
708         btn_x += grid;
709
710         _btn.open->setIconSize(grid_size);
711         _btn.open->setGeometry( btn_x, btn_y, grid, grid );
712         btn_x += grid;
713
714         // STATUS AND VERT CONTROLS
715         _status->setGeometry( margin,
716                               margin,
717                               w - 2*margin - margin/2 - grid,
718                               h - 2*margin - margin/2 - grid );
719
720         _btn.quit->setIconSize(grid_size);
721         _btn.quit->setGeometry( w - margin - grid,
722                                 margin,
723                                 grid,
724                                 grid );
725         _volume->setGeometry( w - margin - grid,
726                               margin + grid,
727                               grid,
728                               h - 2*margin - 2*grid );
729     }
730
731     void PlayerWidget::_setup_signals_and_slots()
732     {
733         _engine_callback.reset(new Details::PlayerWidgetMessageCallback(this));
734         _engine.reset(new Engine(_config) );
735         _engine->subscribe_errors(_engine_callback.get());
736         _engine->subscribe_messages(_engine_callback.get());
737
738         connect(_stretch, SIGNAL(sliderMoved(int)),
739                 this, SLOT(stretch(int)));
740         connect(_status, SIGNAL(locate(float)),
741                 this, SLOT(locate(float)));
742         connect(_volume, SIGNAL(sliderMoved(int)),
743                 this, SLOT(volume(int)));
744
745         QTimer* timer = new QTimer(this);
746         timer->setSingleShot(false);
747         timer->setInterval(200);
748         connect(timer, SIGNAL(timeout()),
749                 this, SLOT(update_time()));
750         timer->start();
751     }
752
753     float PlayerWidget::_margin()
754     {
755         return _sizes.thicker_line() * 3.0;
756     }
757
758     /**
759      * \selects correct cursor.
760      *
761      * Along left and top edges:  move window.
762      * Along bottom and right edges: resize window.
763      *
764      */
765     Qt::CursorShape PlayerWidget::_which_cursor(const QPoint& pos)
766     {
767         bool left = false;
768         bool right = false;
769         bool top = false;
770         bool bottom = false;
771         Qt::CursorShape rv = Qt::ArrowCursor;
772
773         int margin = _sizes.thicker_line() * 3 / 2;
774
775         if( pos.x() <= margin ) left = true;
776         if( pos.x() >= width()-margin ) right = true;
777         if( pos.y() <= margin ) top = true;
778         if( pos.y() >= height()-margin ) bottom = true;
779
780         // Check for corners
781         int rad = 2*margin;
782         if( pos.x() <= rad ) {
783             if( pos.y() <= rad ) {
784                 left = true;
785                 top = true;
786             } else if( pos.y() >= height()-rad ) {
787                 left = true;
788                 bottom = true;
789             }
790         } else if( pos.x() >= width()-rad ) {
791             if( pos.y() <= rad ) {
792                 right = true;
793                 top = true;
794             } else if( pos.y() >= height()-rad ) {
795                 right = true;
796                 bottom = true;
797             }
798         }
799
800         if( left ) {
801             rv = Qt::SizeAllCursor;
802         } else if( right ) {
803             if( top ) {
804                 rv = Qt::SizeAllCursor;
805             } else if(bottom) {
806                 rv = Qt::SizeFDiagCursor;
807             } else {
808                 rv = Qt::SizeHorCursor;
809             }
810         } else if( top ) {
811             rv = Qt::SizeAllCursor;
812         } else if( bottom ) {
813             rv = Qt::SizeVerCursor;
814         }
815
816         return rv;
817     }
818
819     /**
820      * Evokes resize events based on cursor type.
821      *
822      * Note the assumptions made with _which_cursor();
823      */
824     void PlayerWidget::_drag_resize(Qt::CursorShape cur, QMouseEvent* ev)
825     {
826         QRect win(pos().x(), pos().y(), width(), height());
827         QPoint gps = ev->globalPos();
828
829         switch(cur) {
830         case Qt::SizeAllCursor:
831             win.translate(gps - _anchor);
832             _anchor = gps;
833             break;
834         case Qt::SizeHorCursor:
835             // Right edge
836             win.setRight( gps.x() );
837             break;
838         case Qt::SizeVerCursor:
839             win.setBottom( gps.y() );
840             break;
841         case Qt::SizeFDiagCursor:
842             win.setBottomRight( gps );
843             break;
844         case Qt::SizeBDiagCursor:
845             // Not used
846             break;
847         default:
848             break;
849         }
850
851         setGeometry(win);
852     }
853
854 } // namespace StretchPlayer