9 #include <QtopiaApplication>
10 #include <QSoftMenuBar>
22 // stubs to allow *not* supporting printing
24 void document_add_puzzle(document
*doc
, const game
*game
, game_params
*par
,
25 game_state
*st
, game_state
*st2
)
30 /* ----------------------------------------------------------------------
31 * Error reporting functions used elsewhere.
34 void fatal(char *fmt
, ...)
38 fprintf(stderr
, "fatal error: ");
41 vfprintf(stderr
, fmt
, ap
);
44 fprintf(stderr
, "\n");
48 /* ----------------------------------------------------------------------
49 * Qt front end to puzzles.
57 static void do_start_draw(frontend
*fe
)
59 bool ok
= fe
->painter
->begin(fe
->pixmap
);
60 fe
->painter
->setRenderHints(QPainter::Antialiasing
, true);
63 static void do_end_draw(frontend
*fe
)
65 bool ok
= fe
->painter
->end();
69 static void setup_backing_store(frontend
*fe
)
71 fe
->pixmap
= new QPixmap(fe
->pw
, fe
->ph
);
74 fe
->painter
->eraseRect(0, 0, fe
->pw
, fe
->ph
);
78 static int backing_store_ok(frontend
*fe
)
80 return (!!fe
->pixmap
);
83 static void teardown_backing_store(frontend
*fe
)
89 static void repaint_rectangle(frontend
*fe
, QPaintDevice
*widget
,
90 int x
, int y
, int w
, int h
)
92 QPainter
painter(widget
);
94 painter
.eraseRect(x
, y
, fe
->ox
- x
, h
);
99 painter
.eraseRect(x
, y
, w
, fe
->oy
- y
);
104 painter
.eraseRect(x
+ fe
->pw
, y
, w
- fe
->pw
, h
);
108 painter
.eraseRect(x
, y
+ fe
->ph
, w
, h
- fe
->ph
);
111 painter
.drawPixmap(QPoint(x
, y
), *fe
->pixmap
,
112 QRect(x
- fe
->ox
, y
- fe
->oy
, w
, h
));
115 Canvas::Canvas(frontend
* fe
): QWidget(), fe(fe
) { }
116 void Canvas::paintEvent (QPaintEvent
*event
)
118 const QRect
& rect
= event
->rect();
119 repaint_rectangle(fe
, fe
->area
,
121 rect
.width(), rect
.height());
124 void Canvas::do_resize(const QSize
& size
)
128 if (backing_store_ok(fe
))
129 teardown_backing_store(fe
);
131 w
= fe
->w
= size
.width();
132 h
= fe
->h
= size
.height();
133 midend_size(fe
->me
, &w
, &h
, TRUE
);
136 fe
->ox
= (fe
->w
- fe
->pw
) / 2;
137 fe
->oy
= (fe
->h
- fe
->ph
) / 2;
139 setup_backing_store(fe
);
140 midend_force_redraw(fe
->me
);
142 void Canvas::resizeEvent (QResizeEvent
*event
)
144 do_resize(event
->size());
147 void Canvas::mousePressEvent (QMouseEvent
*event
)
150 if (!backing_store_ok(fe
))
152 switch(event
->button()) {
153 case Qt::LeftButton
: button
= LEFT_BUTTON
; break;
154 case Qt::RightButton
: button
= RIGHT_BUTTON
; break;
155 case Qt::MidButton
: button
= MIDDLE_BUTTON
; break;
156 default: event
->ignore(); return; /* don't even know what button! */
159 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
160 event
->y() - fe
->oy
, button
))
163 void Canvas::mouseReleaseEvent (QMouseEvent
*event
)
166 if (!backing_store_ok(fe
))
168 switch(event
->button()) {
169 case Qt::LeftButton
: button
= LEFT_RELEASE
; break;
170 case Qt::RightButton
: button
= RIGHT_RELEASE
; break;
171 case Qt::MidButton
: button
= MIDDLE_RELEASE
; break;
172 default: event
->ignore(); return; /* don't even know what button! */
175 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
176 event
->y() - fe
->oy
, button
))
179 void Canvas::mouseMoveEvent (QMouseEvent
*event
)
182 Qt::MouseButtons buttons
= event
->buttons();
183 if (buttons
.testFlag(Qt::MidButton
))
184 button
= MIDDLE_DRAG
;
185 else if (buttons
.testFlag(Qt::LeftButton
))
187 else if (buttons
.testFlag(Qt::RightButton
))
190 event
->ignore(); /* don't even know what button! */
194 if (!midend_process_key(fe
->me
, event
->x() - fe
->ox
,
195 event
->y() - fe
->oy
, button
))
200 void get_random_seed(void **randseed
, int *randseedsize
)
202 struct timeval
*tvp
= snew(struct timeval
);
203 gettimeofday(tvp
, NULL
);
204 *randseed
= (void *)tvp
;
205 *randseedsize
= sizeof(struct timeval
);
208 void frontend_default_colour(frontend
*fe
, float *output
)
210 // FIXME: should probably ask fe->area, but it looks unavailable
212 // FIXME: that is *not* white !?
218 static void snaffle_colours(frontend
*fe
)
220 const float* colours
= midend_colours(fe
->me
, &fe
->ncolours
);
221 fe
->colours
= new QColor
[fe
->ncolours
];
222 for (int i
= 0; i
< fe
->ncolours
; i
+= 1)
223 fe
->colours
[i
].setRgbF(colours
[3*i
], colours
[3*i
+ 1], colours
[3*i
+ 2]);
228 class Timer
: public QTimer
232 struct timeval last_time
;
233 Timer(frontend
* fe
): QTimer() {
237 void timerEvent(QTimerEvent
*e
) {
240 gettimeofday(&now
, NULL
);
241 elapsed
= ((now
.tv_usec
- last_time
.tv_usec
) * 0.000001F
+
242 (now
.tv_sec
- last_time
.tv_sec
));
243 midend_timer(fe
->me
, elapsed
); /* may clear timer_active */
248 void deactivate_timer(frontend
*fe
)
251 return; /* can happen due to --generate */
255 void activate_timer(frontend
*fe
)
258 return; /* can happen due to --generate */
259 if (!fe
->timer
->isActive()) {
260 fe
->timer
->start(20);
261 gettimeofday(&fe
->timer
->last_time
, NULL
);
267 static void qt_start_draw(void *handle
)
269 frontend
*fe
= (frontend
*)handle
;
277 static void qt_end_draw(void *handle
)
279 frontend
*fe
= (frontend
*)handle
;
282 if (fe
->bbox_l
< fe
->bbox_r
&& fe
->bbox_u
< fe
->bbox_d
) {
283 fe
->area
->update(fe
->bbox_l
- 1 + fe
->ox
,
284 fe
->bbox_u
- 1 + fe
->oy
,
285 fe
->bbox_r
- fe
->bbox_l
+ 2,
286 fe
->bbox_d
- fe
->bbox_u
+ 2);
290 static void qt_clip(void *handle
, int x
, int y
, int w
, int h
)
292 frontend
*fe
= (frontend
*)handle
;
293 fe
->painter
->setClipRect(x
, y
, w
, h
);
296 static void qt_unclip(void *handle
)
298 frontend
*fe
= (frontend
*)handle
;
299 fe
->painter
->setClipRect(0, 0, fe
->w
, fe
->h
);
302 static void qt_draw_text(void *handle
, int x
, int y
, int fonttype
, int fontsize
,
303 int align
, int colour
, char *text
)
305 frontend
*fe
= (frontend
*)handle
;
308 font
.setPixelSize(fontsize
);
309 if (fonttype
== FONT_FIXED
)
310 font
.setFamily("Monospace");
311 fe
->painter
->setFont(font
);
313 QFontMetrics fm
= fe
->painter
->fontMetrics();
314 int width
= fm
.width(text
);
315 QRect
rect(0, 0, width
, fontsize
);
318 if (align
& ALIGN_VCENTRE
) {
319 flags
|= Qt::AlignVCenter
;
320 rect
.moveTop(y
- fontsize
/2);
321 } else { // as if (align & ALIGN_VNORMAL)
322 // FIXME implemented as bottom, not baseline...
323 flags
|= Qt::AlignBottom
;
326 if (align
& ALIGN_HCENTRE
) {
327 flags
|= Qt::AlignHCenter
;
328 rect
.moveLeft(x
- width
/2);
329 } else if (align
& ALIGN_HRIGHT
) {
330 flags
|= Qt::AlignRight
;
332 } else { // as if (align & ALIGN_HLEFT)
333 flags
|= Qt::AlignLeft
;
337 fe
->painter
->setPen(fe
->colours
[colour
]);
338 fe
->painter
->drawText(rect
, flags
, text
);
341 static void qt_draw_rect(void *handle
, int x
, int y
, int w
, int h
, int colour
)
343 frontend
*fe
= (frontend
*)handle
;
344 fe
->painter
->fillRect(x
, y
, w
, h
, fe
->colours
[colour
]);
347 static void qt_draw_line(void *handle
, int x1
, int y1
, int x2
, int y2
, int colour
)
349 frontend
*fe
= (frontend
*)handle
;
350 fe
->painter
->setPen(fe
->colours
[colour
]);
351 fe
->painter
->drawLine(x1
, y1
, x2
, y2
);
354 static void qt_draw_polygon(void *handle
, int *coords
, int npoints
,
355 int fillcolour
, int outlinecolour
)
357 frontend
*fe
= (frontend
*)handle
;
358 const QBrush oldbrush
= fe
->painter
->brush();
359 if (fillcolour
!= -1)
360 fe
->painter
->setBrush(fe
->colours
[fillcolour
]);
362 for (int i
=0; i
< npoints
; i
++)
363 polygon
<< QPoint(coords
[i
*2], coords
[i
*2 + 1]);
364 fe
->painter
->setPen(fe
->colours
[outlinecolour
]);
365 fe
->painter
->drawPolygon(polygon
);
366 if (fillcolour
!= -1)
367 fe
->painter
->setBrush(oldbrush
);
370 static void qt_draw_circle(void *handle
, int cx
, int cy
, int radius
,
371 int fillcolour
, int outlinecolour
)
373 frontend
*fe
= (frontend
*)handle
;
375 path
.addEllipse(QPoint(cx
, cy
), radius
, radius
);
376 if (fillcolour
!= -1)
377 fe
->painter
->fillPath(path
, fe
->colours
[fillcolour
]);
378 fe
->painter
->setPen(fe
->colours
[outlinecolour
]);
379 fe
->painter
->drawPath(path
);
382 static blitter
*qt_blitter_new(void *handle
, int w
, int h
)
384 blitter
*bl
= snew(blitter
);
386 * We can't create the pixmap right now, because fe->window
387 * might not yet exist. So we just cache w and h and create it
388 * during the firs call to blitter_save.
396 static void qt_blitter_free(void *handle
, blitter
*bl
)
403 static void qt_blitter_save(void *handle
, blitter
*bl
, int x
, int y
)
405 frontend
*fe
= (frontend
*)handle
;
408 bl
->pixmap
= new QPixmap
;
412 *bl
->pixmap
= fe
->pixmap
->copy(bl
->x
, bl
->y
, bl
->w
, bl
->h
);
415 static void qt_blitter_load(void *handle
, blitter
*bl
, int x
, int y
)
417 frontend
*fe
= (frontend
*)handle
;
418 if (x
== BLITTER_FROMSAVED
&& y
== BLITTER_FROMSAVED
) {
423 fe
->painter
->drawPixmap(QPoint(x
, y
), *bl
->pixmap
);
426 static void qt_draw_update(void *handle
, int x
, int y
, int w
, int h
)
428 frontend
*fe
= (frontend
*)handle
;
429 if (fe
->bbox_l
> x
) fe
->bbox_l
= x
;
430 if (fe
->bbox_r
< x
+w
) fe
->bbox_r
= x
+w
;
431 if (fe
->bbox_u
> y
) fe
->bbox_u
= y
;
432 if (fe
->bbox_d
< y
+h
) fe
->bbox_d
= y
+h
;
435 const struct drawing_api qt_drawing
= {
446 NULL
, //qt_status_bar,
451 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, /* {begin,end}_{doc,page,puzzle} */
452 NULL
, NULL
, /* line_width, line_dotted */
453 NULL
, //text_fallback
454 NULL
, //draw_thick_line,
457 static void maybe_enlarge_window(frontend
*fe
)
459 QSize delta
= fe
->area
->sizeHint() - fe
->area
->size();
460 if (delta
.width() < 0) delta
.rwidth() = 0;
461 if (delta
.height() < 0) delta
.rheight() = 0;
462 if (delta
== QSize(0, 0))
463 fe
->area
->do_resize(fe
->window
->size());
465 fe
->window
->resize(fe
->window
->size() + delta
);
468 static void add_type_submenu(QMenu
*menu
, QWidget
*window
, frontend
*fe
, int npreset
)
470 QActionGroup
*itemgrp
= new QActionGroup(window
);
471 QSignalMapper
*signalMapper
= new QSignalMapper(window
);
472 window
->connect(signalMapper
, SIGNAL(mapped(QObject
*)),
473 window
, SLOT(preset(QObject
*)));
475 for (int i
= 0; i
< npreset
; i
++) {
479 midend_fetch_preset(fe
->me
, i
, &name
, ¶ms
);
481 QAction
*a
= new QAction(name
, window
);
483 window
->connect(a
, SIGNAL(triggered()), signalMapper
, SLOT(map()));
484 GameParams
*p
= new GameParams(params
);
485 signalMapper
->setMapping(a
, p
);
486 itemgrp
->addAction(menu
->menuAction());
490 PuzzleWindow::PuzzleWindow(QWidget
*parent
, Qt::WFlags fl
)
491 : QMainWindow( parent
, fl
)
496 fprintf(stderr
, "failed to allocate frontend\n");
500 fe
->timer
= new Timer(fe
);
502 fe
->me
= midend_new(fe
, &thegame
, &qt_drawing
, fe
);
503 midend_new_game(fe
->me
);
505 setWindowTitle(thegame
.name
);
506 QWidget
*vbox
= new QWidget(this);
507 QVBoxLayout
*vlayout
= new QVBoxLayout(vbox
);
508 vlayout
->setMargin(0);
511 fe
->painter
= new QPainter
;
514 QMenu
*menu
= QSoftMenuBar::menuFor(this);
516 QMenu
*menu
= this->menuBar()->addMenu("&Game");
518 menu
->addAction("New game", this, SLOT(newGame()));
520 if ((npreset
= midend_num_presets(fe
->me
)) > 0 || thegame
.can_configure
) {
522 QMenu
*submenu
= menu
->addMenu("Type");
524 QMenu
*submenu
= this->menuBar()->addMenu("Type");
526 add_type_submenu(submenu
, this, fe
, npreset
);
528 menu
->addAction("Restart", this, SLOT(restart()));
529 menu
->addAction("Undo", this, SLOT(undo()));
530 menu
->addAction("Redo", this, SLOT(redo()));
531 menu
->addAction("Solve", this, SLOT(solve()));
533 menu
->addSeparator();
534 QMenu
*submenu
= menu
;
536 menu
->addAction("Exit", qApp
, SLOT(quit()));
537 QMenu
*submenu
= this->menuBar()->addMenu("Help");
539 submenu
->addAction("About", this, SLOT(about()));
543 fe
->area
= new Canvas(fe
);
544 vlayout
->addWidget(fe
->area
);
545 setCentralWidget(vbox
);
549 QSize
Canvas::sizeHint() const {
553 * Currently I don't want to scale large
554 * puzzles to fit on the screen. This is because X does permit
555 * extremely large windows and many window managers provide a
556 * means of navigating round them, and the users I consulted
557 * before deciding said that they'd rather have enormous puzzle
558 * windows spanning multiple screen pages than have them
559 * shrunk. I could change my mind later or introduce
560 * configurability; this would be the place to do so, by
561 * replacing the initial values of x and y with the screen
566 midend_size(fe
->me
, &x
, &y
, FALSE
);
569 void PuzzleWindow::newGame() {
570 midend_process_key(fe
->me
, 0, 0, 'n');
572 void PuzzleWindow::preset(QObject
*o
) {
573 GameParams
*p
= qobject_cast
<GameParams
*>(o
);
576 midend_set_params(fe
->me
, p
->params
);
577 midend_new_game(fe
->me
);
578 //changed_preset(fe);
580 fe
->area
->do_resize(fe
->window
->size());
582 maybe_enlarge_window(fe
);
585 void PuzzleWindow::restart() {
586 midend_restart_game(fe
->me
);
588 void PuzzleWindow::undo() {
589 midend_process_key(fe
->me
, 0, 0, 'u');
591 void PuzzleWindow::redo() {
592 midend_process_key(fe
->me
, 0, 0, 'r');
594 void PuzzleWindow::solve() {
597 msg
= midend_solve(fe
->me
);
605 void PuzzleWindow::about() {
609 sprintf(titlebuf
, "About %.200s", thegame
.name
);
612 "from Simon Tatham's Portable Puzzle Collection\n\n"
613 "%.500s", thegame
.name
, ver
);
616 mb
.setWindowTitle(titlebuf
);
623 QTOPIA_ADD_APPLICATION(QTOPIA_TARGET
,PuzzleWindow
)
626 int main(int argc
, char **argv
)
628 char *pname
= argv
[0];
629 int doing_opts
= TRUE
;
635 * Command line parsing in this function is rather fiddly,
636 * because GTK wants to have a go at argc/argv _first_ - and
637 * yet we can't let it, because gtk_init() will bomb out if it
638 * can't open an X display, whereas in fact we want to permit
639 * our --generate and --print modes to run without an X
643 * - we parse the command line ourselves, without modifying
645 * - if we encounter an error which might plausibly be the
646 * result of a GTK command line (i.e. not detailed errors in
647 * particular options of ours) we store the error message
648 * and terminate parsing.
649 * - if we got enough out of the command line to know it
650 * specifies a non-X mode of operation, we either display
651 * the stored error and return failure, or if there is no
652 * stored error we do the non-X operation and return
654 * - otherwise, we go straight to gtk_init().
660 if (doing_opts
&& !strcmp(p
, "--version")) {
661 printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
664 } else if (doing_opts
&& !strcmp(p
, "--")) {
667 sprintf(errbuf
, "%.100s: unrecognised option '%.100s'\n",
674 fputs(errbuf
, stderr
);
678 QApplication
app(argc
, argv
);