C++ only uses NULL because C does. The C++ way is to use 0.
[shopper.git] / src / ui / mainwindow.cc
blobed17d34234aa535304b3a7bed094c3403e856ac3
1 /* Shopper
2 * Copyright (C) 2008 David Greaves <david@dgreaves.com>
4 * This software is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2.1 of
7 * the License, or (at your option) any later version.
9 * This software is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this software; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA
21 #define DEBUG_SHOPPER 1
22 #include "mainwindow.h"
23 #include "shopper.h" // automake, i8n, gettext
25 #include "CatListModel.h"
26 #include "CategoryDialog.h"
27 #include "ItemDialog.h"
28 #include "PreferencesDialog.h"
29 #include "notify.h"
30 #include <QSettings>
32 // Global variable
33 Shopper::Notify *notify;
35 // class MainWindow : public QMainWindow
36 MainWindow::MainWindow(QString file) :
37 mylist(0),
38 catCombo(0),
39 lv(0),
40 filename(file),
41 fontsize(4),
42 full_screen(false),
43 has_rotation(false)
45 // The constructor handles only 'one off' stuff - main menu,
46 // buttons etc. Anything List specific is separated off to
47 // handle_list() so it can be reused when opening new lists
49 // xrandr detection from adv-backlight...
50 #ifdef XRANDR
51 Display *dpy;
52 int xrr_event_base, xrr_error_base;
53 int xrr_major, xrr_minor;
54 dpy = XOpenDisplay (0);
55 if (dpy == 0)
57 DEBUG("Couldn't open display\n");
58 has_rotation = false;
59 } else {
60 if (XRRQueryExtension (dpy, &xrr_event_base, &xrr_error_base) == false
61 || XRRQueryVersion (dpy, &xrr_major, &xrr_minor) == 0 || xrr_major != 1 || xrr_minor < 1) {
62 DEBUG("Display has no rotation\n");
63 has_rotation = false;
64 } else {
65 DEBUG("Display has rotation\n");
66 has_rotation = true;
68 XCloseDisplay (dpy);
70 #endif
71 // Actions
72 // UI components
73 QAction *addItemAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/add.png"),
74 tr("Add Item"), this);
75 addItemAct->setStatusTip(tr("Add an item to the list"));
76 connect(addItemAct, SIGNAL(triggered()), this, SLOT(on_action_add_item()));
78 QAction *manageCatAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/categories.png"),
79 tr("Manage Categories"), this);
80 manageCatAct->setStatusTip(tr("Manage the list of categories"));
81 connect(manageCatAct, SIGNAL(triggered()), this, SLOT(on_action_manage_category()));
83 QAction *clearWantedAct = new QAction(qApp->style()->standardIcon(QStyle::SP_FileDialogListView),
84 tr("Clear Wanted"), this);
85 clearWantedAct->setStatusTip(tr("Clear the items marked as 'wanted'"));
86 connect(clearWantedAct, SIGNAL(triggered()), this, SLOT(on_action_clear_wanted()));
88 QAction *emptyBasketAct = new QAction(qApp->style()->standardIcon(QStyle::SP_FileDialogListView),
89 tr("Empty Basket"), this);
90 emptyBasketAct->setStatusTip(tr("Clear the items marked as 'in the basket'"));
91 connect(emptyBasketAct, SIGNAL(triggered()), this, SLOT(on_action_clear_bought()));
93 QAction *fullListAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/full-list.png"),
94 tr("Out Shopping: Full List"), this);
95 fullListAct->setStatusTip(tr("Show the full list (items in the basket will be visible and ticked)"));
96 connect(fullListAct, SIGNAL(triggered()), this, SLOT(on_action_fullList()));
98 QAction *whatsLeftAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/whats-left.png"),
99 tr("Out Shopping: What's Left"), this);
100 whatsLeftAct->setStatusTip(tr("Show what's left to get"));
101 connect(whatsLeftAct, SIGNAL(triggered()), this, SLOT(on_action_whatsLeft()));
103 QAction *makingListAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/making-list.png"),
104 tr("Making List"), this);
105 makingListAct->setStatusTip(tr("Pick items to get when you go shopping"));
106 connect(makingListAct, SIGNAL(triggered()), this, SLOT(on_action_makingList()));
108 QAction *newAct = new QAction(qApp->style()->standardIcon(QStyle::SP_FileDialogListView),
109 tr("New List"), this);
110 newAct->setStatusTip(tr("Add an item to the list"));
111 connect(newAct, SIGNAL(triggered()), this, SLOT(on_action_file_new()));
113 QAction *openAct = new QAction(qApp->style()->standardIcon(QStyle::SP_DialogOpenButton),
114 tr("Open List"), this);
115 openAct->setStatusTip(tr("Open a new list"));
116 connect(openAct, SIGNAL(triggered()), this, SLOT(on_action_file_open()));
118 QAction *saveAct = new QAction(qApp->style()->standardIcon(QStyle::SP_DialogSaveButton),
119 tr("Save List"), this);
120 saveAct->setStatusTip(tr("Save the list"));
121 connect(saveAct, SIGNAL(triggered()), this, SLOT(on_action_file_save()));
123 QAction *saveAsAct = new QAction(qApp->style()->standardIcon(QStyle::SP_DialogSaveButton),
124 tr("Save List As..."), this);
125 saveAsAct->setStatusTip(tr("Save the list with a new name"));
126 connect(saveAsAct, SIGNAL(triggered()), this, SLOT(on_action_file_saveas()));
128 QAction *prefsAct = new QAction(qApp->style()->standardIcon(QStyle::SP_DialogSaveButton),
129 tr("Preferences..."), this);
130 prefsAct->setStatusTip(tr("Application preferences"));
131 connect(prefsAct, SIGNAL(triggered()), this, SLOT(on_action_preferences()));
133 QAction *rotateAct = new QAction(QIcon("/usr/share/icons/hicolor/64x64/apps/shopper/rotate.png"),
134 tr("Rotate"), this);
135 prefsAct->setStatusTip(tr("Rotate Application"));
136 connect(rotateAct, SIGNAL(triggered()), this, SLOT(on_action_rotate()));
138 QAction *quitAct = new QAction(qApp->style()->standardIcon(QStyle::SP_BrowserStop),
139 tr("Quit"), this);
140 quitAct->setStatusTip(tr("Finish"));
141 connect(quitAct, SIGNAL(triggered()), this, SLOT(on_action_file_quit()));
143 QAction *aboutAct = new QAction(qApp->style()->standardIcon(QStyle::SP_MessageBoxInformation),
144 tr("About..."), this);
145 aboutAct->setStatusTip(tr("About Shopper..."));
146 connect(aboutAct, SIGNAL(triggered()), this, SLOT(on_action_about()));
148 // Defined in class to be available elsewhere
149 nextAct = new QAction(qApp->style()->standardIcon(QStyle::SP_MediaSkipForward),
150 tr("Next Category"), this);
151 nextAct->setStatusTip(tr("View the next category"));
152 connect(nextAct, SIGNAL(triggered()), this, SLOT(on_action_next()));
154 QAction *prevAct = new QAction(qApp->style()->standardIcon(QStyle::SP_MediaSkipBackward),
155 tr("Prev Category"), this);
156 prevAct->setStatusTip(tr("View the previous category"));
157 connect(prevAct, SIGNAL(triggered()), this, SLOT(on_action_prev()));
159 // Menus
160 menuBar()->addAction(addItemAct);
161 menuBar()->addAction(manageCatAct);
163 QMenu *clearMenu = menuBar()->addMenu(tr("Clear List"));
164 clearMenu->addAction(clearWantedAct);
165 clearMenu->addAction(emptyBasketAct);
167 QMenu *modeMenu = menuBar()->addMenu(tr("Mode"));
168 modeMenu->addAction(fullListAct);
169 modeMenu->addAction(whatsLeftAct);
170 modeMenu->addAction(makingListAct);
172 menuBar()->addSeparator();
174 QMenu *fileMenu = menuBar()->addMenu(tr("File"));
175 fileMenu->addAction(newAct);
176 fileMenu->addAction(openAct);
177 fileMenu->addAction(saveAct);
178 fileMenu->addAction(saveAsAct);
180 menuBar()->addAction(prefsAct);
182 QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
183 helpMenu->addAction(aboutAct);
185 menuBar()->addAction(quitAct);
187 // Toolbar
188 buttonBar = addToolBar(tr("Buttons"));
189 buttonBar2 = addToolBar(tr("Navigation"));
190 buttonBar->setIconSize(QSize(40,40));
191 buttonBar2->setIconSize(QSize(40,40));
192 addToolBar(Qt::BottomToolBarArea, buttonBar); // move it to the right area
193 addToolBar(Qt::BottomToolBarArea, buttonBar2); // move it to the right area
194 buttonBar->addAction(addItemAct);
195 buttonBar->addAction(manageCatAct);
196 buttonBar->addSeparator();
197 buttonBar->addAction(fullListAct);
198 buttonBar->addAction(whatsLeftAct);
199 buttonBar->addAction(makingListAct);
200 buttonBar2->addAction(prevAct);
201 buttonBar2->addAction(nextAct);
202 buttonBar2->addSeparator();
203 buttonBar2->addAction(rotateAct);
204 // Status
205 statusBar()->showMessage(tr("Ready"));
207 // Notify is a SystemTrayIcon used for notify messages
208 // Note this is a globally accessible object - nothing to do with MainWin except we parent it
209 QIcon shopper_icon("/usr/share/icons/hicolor/64x64/apps/shopper/shopper.png");
210 notify=new Shopper::Notify(shopper_icon);
211 notify->show();
212 setWindowIcon(shopper_icon);
214 // Prepare our Settings
215 QCoreApplication::setOrganizationName("dgreaves.com");
216 QCoreApplication::setOrganizationDomain("dgreaves.com");
217 QCoreApplication::setApplicationName("Shopper");
219 // size and position
220 readSettings();
221 timerId = startTimer(0);
223 QLabel *notice = new QLabel("List Loading. Wait just a moment...", this);
224 notice->show();
225 setCentralWidget(notice);
226 _LEAVE;
229 void MainWindow::timerEvent(QTimerEvent *event)
231 killTimer(event->timerId());
232 qApp->processEvents(); // ensure we're quiet
233 qApp->processEvents(); // ensure we're quiet
234 loadList();
237 void MainWindow::loadList()
239 Shopper::List *l;
240 if (mylist == 0) {
241 // notify->showMessage("Shopper", "Loading shopping list");
242 l = Shopper::List::from_file(filename);
243 if (l) { // If filename valid
244 create_list_view(l);
245 } else { // If filename wasn't read
246 on_action_file_new();
247 while (!mylist) { // mylist and list_view are done in a file_open()
248 DEBUG("Couldn't open " << filename << " for reading\n");
249 on_action_file_open();
250 // FIXME :: Popup dialog box and allow new/open
256 void MainWindow::create_list_view(Shopper::List *list)
258 // Remove the old listview... and any data
259 if (mylist) mylist->deleteLater();
260 if (lv) lv->deleteLater();
261 if (catCombo)
263 if (catCombo->model()) delete catCombo->model();
264 delete catCombo;
266 // and use a list and a new main shopperList widget
267 mylist = list;
268 lv = new Shopper::ListView(*list, this);
269 setCentralWidget(lv);
270 // GUI: set title, add the view, update the menu
271 setWindowTitle("Shopper:"+mylist->modeText());
272 // Make sure there's a menu to show to enable the popup functionality
274 catCombo = new QComboBox();
275 Shopper::CatListModel *clmodel = new Shopper::CatListModel(*mylist);
276 catCombo->setModel(clmodel);
277 catCombo->setModelColumn(1);
278 catCombo->setCurrentIndex(clmodel->currentIndex());
279 connect(catCombo, SIGNAL(activated(int)),
280 this, SLOT(cat_selected(int)));
281 connect(clmodel, SIGNAL(currentChanged(int)),
282 catCombo, SLOT(setCurrentIndex(int)));
283 buttonBar2->insertWidget(nextAct, catCombo);
285 // Update UI when the state changes
286 connect(mylist, SIGNAL(state_changed()),
287 this, SLOT(stateChanged()));
289 _LEAVE;
292 void MainWindow::cat_selected(int i)
294 Q_UNUSED(i)
295 _ENTER;
296 QVariant v = catCombo->itemData(catCombo->currentIndex());
297 Shopper::Category *c = v.value<Shopper::Category*>();
298 mylist->make_category_active(*c);
302 void MainWindow::closeEvent(QCloseEvent *event)
304 _ENTER;
305 event->accept();
306 on_action_file_quit();
309 void MainWindow::on_action_about()
311 QMessageBox::about(this, tr("About Shopper"),
312 tr("<b>Shopper</b> was written by David Greaves (lbt)<br>"
313 "It is Free Software and licensed under the GPL.<br>"
314 "I hope you enjoy using it.<br>"
315 "Please let me know of any problems or suggestions."));
319 void MainWindow::readSettings()
321 QSettings settings;
322 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
323 QSize size = settings.value("size", QSize(400, 400)).toSize();
324 filename = settings.value("filename", filename).toString();
325 resize(size);
326 move(pos);
329 void MainWindow::writeSettings()
331 QSettings settings;
332 settings.setValue("pos", pos());
333 settings.setValue("size", size());
334 settings.setValue("filename", filename);
335 DEBUG("Wrote Settings");
338 // Action handlers
339 // Add item
340 void MainWindow::on_action_add_item()
342 Shopper::ItemAdd itemD(this, mylist);
343 if (itemD.exec() == QDialog::Accepted) {
344 DEBUG("\nADDED:\n");
348 void MainWindow::on_action_manage_category()
350 Shopper::CategoryManage catD(this, mylist);
351 catD.exec();
354 // Category cycle
355 void MainWindow::on_action_next(){ mylist->make_next_category_active(); }
356 void MainWindow::on_action_prev(){ mylist->make_prev_category_active(); }
358 // Clear List
359 void MainWindow::on_action_clear_wanted() { mylist->clear(Shopper::CLEAR_WANTED); }
360 void MainWindow::on_action_clear_bought() { mylist->clear(Shopper::CLEAR_BOUGHT); }
362 // Shopping mode
363 void MainWindow::on_action_fullList() { mylist->set_state(Shopper::OUT_SHOPPING); }
364 void MainWindow::on_action_whatsLeft() { mylist->set_state(Shopper::WHATS_LEFT); }
365 void MainWindow::on_action_makingList() { mylist->set_state(Shopper::MAKING_LIST); }
367 void MainWindow::stateChanged()
369 setWindowTitle("Shopper:" + mylist->modeText());
371 ////////////////////////////////////////////////////////////////
372 // File Handling
373 void MainWindow::on_action_file_new()
375 // FIXME: This really ought to check if the user *WANTS* to save...
376 if (mylist)
377 on_action_file_save();
378 filename="";
379 create_list_view(new Shopper::List(
380 QString(
381 "<list name='Supermarket' state='0'>"
382 "<category name='Aisle 1 - Dairy'>"
383 "<item desc='Cheese' note='note, no smelly cheese' wanted='1' bought='0'/>"
384 "<item desc='Bacon' note='Extra needed for Gary&apos;s butties' wanted='1' bought='0'/>"
385 "</category>"
386 "<category name='Frozen'>"
387 "<item desc='Peas' note='' wanted='1' bought='0'/>"
388 "<item desc='Prawns' note='' wanted='1' bought='0'/>"
389 "</category>"
390 "<category name='Veg'>"
391 "<item desc='Potatoes' note='' wanted='0' bought='0'/>"
392 "<item desc='Brocolli' note='' wanted='1' bought='0'/>"
393 "</category>"
394 "<category name='Shoes'>"
395 "<item desc='Versace' note='For Tracy' wanted='0' bought='0'/>"
396 "</category>"
397 "</list>"
398 )));
399 notify->showMessage("Shopper", "Created a sample shopping list");
401 void MainWindow::on_action_file_open()
403 // Get a filename
404 QFileDialog dialog(this);
405 QString dir = HOME_DIR+(filename==""?DEFAULT_LIST:filename);
406 //Handle the response:
407 if (!(filename = dialog.getOpenFileName(this, "Select a list",dir, tr("Lists (*)"))).isEmpty()) {
408 // Open file and create a view from it.
409 create_list_view(Shopper::List::from_file(filename));
410 writeSettings(); // FIXME this should be on success
411 } else {
412 if (mylist == 0) on_action_file_new(); // If CANCEL pressed and there's no list, make a new one
416 ////////////////////////////////////////////////////////////////
417 void MainWindow::on_action_file_save()
419 DEBUG("Saving....\n");
420 if (filename.isEmpty()) {
421 on_action_file_saveas();
422 } else {
423 mylist->to_file(filename);
426 void MainWindow::on_action_file_saveas()
428 // Get a filename
429 QFileDialog dialog(this);
430 QString dir = HOME_DIR+(filename==""?DEFAULT_LIST:filename);
431 //Handle the response:
432 if (!(filename = dialog.getSaveFileName(this, "Save list as", dir, tr("Lists (*.xml)"))).isEmpty()) {
433 // Open file and create a view from it.
434 writeSettings();
435 mylist->to_file(filename);
438 void MainWindow::on_action_preferences()
440 Shopper::PreferencesDialog prefsD(this);
441 prefsD.exec();
442 lv->update();
444 void MainWindow::on_action_file_quit()
446 _ENTER;
447 QSettings settings;
448 if (settings.value("data/SaveOnExit").toBool())
449 on_action_file_save();
450 writeSettings();
451 if (has_rotation and settings.value("ui/RotateSafe").toBool()) {
452 XRRScreenConfiguration *scr_config;
453 int size;
454 Display* dpy = XOpenDisplay (NULL);
455 int screen = DefaultScreen (dpy);
456 scr_config = XRRGetScreenInfo (dpy, RootWindow (dpy, screen));
457 size = XRRConfigCurrentConfiguration (scr_config, &rotation);
458 XRRSetScreenConfig (dpy, scr_config, RootWindow (dpy, screen), size, RR_Rotate_0, CurrentTime);
459 XRRFreeScreenConfigInfo (scr_config);
460 XCloseDisplay (dpy);
462 close(); //Closes the main window to quit
465 bool MainWindow::on_signal_window_closed()
467 _ENTER;
468 on_action_file_quit();
469 return true; // propagate signal so hide() happens
472 void MainWindow::keyPressEvent ( QKeyEvent * event )
474 switch (event->key())
476 case Qt::Key_F7: // Zoom in
477 lv->zoomIn();
478 break;
479 case Qt::Key_F8: // Zoom out
480 lv->zoomOut();
481 break;
482 default:
483 QMainWindow::keyPressEvent(event);
484 return;
485 break;
487 return;
490 void MainWindow::on_action_rotate()
492 if (! has_rotation) return;
494 XRRScreenConfiguration *scr_config;
495 // Rotation current_rotation;
496 int size;
497 // int i;
498 Display *dpy;
499 int screen;
501 dpy = XOpenDisplay (0);
502 screen = DefaultScreen (dpy);
503 scr_config = XRRGetScreenInfo (dpy, RootWindow (dpy, screen));
504 size = XRRConfigCurrentConfiguration (scr_config, &rotation);
506 if (rotation == RR_Rotate_0) {
507 QSettings settings;
508 if (settings.value("ui/RotateOtherWay",0).toBool()) {
509 rotation = RR_Rotate_270;
510 } else {
511 rotation = RR_Rotate_90;
513 insertToolBarBreak(buttonBar2);
514 } else {
515 rotation = RR_Rotate_0; // always rotate back to 0
516 removeToolBarBreak(buttonBar2);
519 XRRSetScreenConfig (dpy, scr_config, RootWindow (dpy, screen), size, rotation, CurrentTime);
520 XRRFreeScreenConfigInfo (scr_config);
521 XCloseDisplay (dpy);
525 void MainWindow::on_action_help()