Merge master.
[dcpomatic.git] / src / tools / dcpomatic.cc
index cf6be18f63c41d012b5dbb95944cdc298a4dbf1f..d08a11ea9edd84d5e1ae34f3dbdaaf779c69701a 100644 (file)
@@ -30,7 +30,7 @@
 #include <wx/stdpaths.h>
 #include <wx/cmdline.h>
 #include <wx/preferences.h>
-#include <libdcp/exceptions.h>
+#include <dcp/exceptions.h>
 #include "wx/film_viewer.h"
 #include "wx/film_editor.h"
 #include "wx/job_manager_view.h"
@@ -45,6 +45,7 @@
 #include "wx/servers_list_dialog.h"
 #include "wx/hints_dialog.h"
 #include "wx/update_dialog.h"
+#include "wx/content_panel.h"
 #include "lib/film.h"
 #include "lib/config.h"
 #include "lib/util.h"
@@ -63,6 +64,7 @@
 
 using std::cout;
 using std::string;
+using std::vector;
 using std::wstring;
 using std::map;
 using std::make_pair;
@@ -71,8 +73,6 @@ using std::exception;
 using boost::shared_ptr;
 using boost::dynamic_pointer_cast;
 
-// #define DCPOMATIC_WINDOWS_CONSOLE 1
-
 class FilmChangedDialog
 {
 public:
@@ -116,7 +116,9 @@ enum {
        ID_file_open,
        ID_file_save,
        ID_file_properties,
-       ID_content_scale_to_fit_width,
+       ID_file_history,
+       /* Allow spare IDs after _history for the recent files list */
+       ID_content_scale_to_fit_width = 100,
        ID_content_scale_to_fit_height,
        ID_jobs_make_dcp,
        ID_jobs_make_kdms,
@@ -124,7 +126,9 @@ enum {
        ID_jobs_show_dcp,
        ID_tools_hints,
        ID_tools_encoding_servers,
-       ID_tools_check_for_updates
+       ID_tools_check_for_updates,
+       /* IDs for shortcuts (with no associated menu item) */
+       ID_add_file
 };
 
 class Frame : public wxFrame
@@ -135,31 +139,43 @@ public:
                , _hints_dialog (0)
                , _servers_list_dialog (0)
                , _config_dialog (0)
-       {
-#if defined(DCPOMATIC_WINDOWS) && defined(DCPOMATIC_WINDOWS_CONSOLE)
-                AllocConsole();
-               
-               HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
-               int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
-               FILE* hf_out = _fdopen(hCrt, "w");
-               setvbuf(hf_out, NULL, _IONBF, 1);
-               *stdout = *hf_out;
-               
-               HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
-               hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
-               FILE* hf_in = _fdopen(hCrt, "r");
-               setvbuf(hf_in, NULL, _IONBF, 128);
-               *stdin = *hf_in;
+               , _file_menu (0)
+               , _history_items (0)
+               , _history_position (0)
+               , _history_separator (0)
+       {
+#if defined(DCPOMATIC_WINDOWS)
+               if (Config::instance()->win32_console ()) {
+                       AllocConsole();
+                       
+                       HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
+                       int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
+                       FILE* hf_out = _fdopen(hCrt, "w");
+                       setvbuf(hf_out, NULL, _IONBF, 1);
+                       *stdout = *hf_out;
+                       
+                       HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
+                       hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
+                       FILE* hf_in = _fdopen(hCrt, "r");
+                       setvbuf(hf_in, NULL, _IONBF, 128);
+                       *stdin = *hf_in;
+
+                       cout << "DCP-o-matic is starting." << "\n";
+               }
 #endif
 
                wxMenuBar* bar = new wxMenuBar;
                setup_menu (bar);
                SetMenuBar (bar);
 
+               _config_changed_connection = Config::instance()->Changed.connect (boost::bind (&Frame::config_changed, this));
+               config_changed ();
+
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_new, this),                ID_file_new);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_open, this),               ID_file_open);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_save, this),               ID_file_save);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_properties, this),         ID_file_properties);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_history, this, _1),        ID_file_history, ID_file_history + HISTORY_SIZE);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_exit, this),               wxID_EXIT);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::edit_preferences, this),        wxID_PREFERENCES);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::content_scale_to_fit_width, this), ID_content_scale_to_fit_width);
@@ -200,6 +216,12 @@ public:
                JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&Frame::set_menu_sensitivity, this));
 
                overall_panel->SetSizer (main_sizer);
+
+               wxAcceleratorEntry accel[1];
+               accel[0].Set (wxACCEL_CTRL, static_cast<int>('A'), ID_add_file);
+               Bind (wxEVT_MENU, boost::bind (&ContentPanel::add_file_clicked, _film_editor->content_panel()), ID_add_file);
+               wxAcceleratorTable accel_table (1, accel);
+               SetAcceleratorTable (accel_table);
        }
 
        void new_film (boost::filesystem::path path)
@@ -211,7 +233,10 @@ public:
        }
 
        void load_film (boost::filesystem::path file)
+       try
        {
+               maybe_save_then_delete_film ();
+               
                shared_ptr<Film> film (new Film (file));
                list<string> const notes = film->read_metadata ();
 
@@ -229,6 +254,11 @@ public:
                
                set_film (film);
        }
+       catch (std::exception& e) {
+               wxString p = std_to_wx (file.string ());
+               wxCharBuffer b = p.ToUTF8 ();
+               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
+       }
 
        void set_film (shared_ptr<Film> film)
        {
@@ -236,6 +266,7 @@ public:
                _film_viewer->set_film (_film);
                _film_editor->set_film (_film);
                set_menu_sensitivity ();
+               Config::instance()->add_to_history (_film->directory ());
        }
 
        shared_ptr<Film> film () const {
@@ -307,14 +338,7 @@ private:
                }
                        
                if (r == wxID_OK) {
-                       maybe_save_then_delete_film ();
-                       try {
-                               load_film (wx_to_std (c->GetPath ()));
-                       } catch (std::exception& e) {
-                               wxString p = c->GetPath ();
-                               wxCharBuffer b = p.ToUTF8 ();
-                               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
-                       }
+                       load_film (wx_to_std (c->GetPath ()));
                }
 
                c->Destroy ();
@@ -331,6 +355,15 @@ private:
                d->ShowModal ();
                d->Destroy ();
        }
+
+       void file_history (wxCommandEvent& event)
+       {
+               vector<boost::filesystem::path> history = Config::instance()->history ();
+               int n = event.GetId() - ID_file_history;
+               if (n >= 0 && n < static_cast<int> (history.size ())) {
+                       load_film (history[n]);
+               }
+       }
        
        void file_exit ()
        {
@@ -380,7 +413,7 @@ private:
                                        shared_ptr<Job> (new SendKDMEmailJob (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
                                        );
                        }
-               } catch (libdcp::NotEncryptedError& e) {
+               } catch (dcp::NotEncryptedError& e) {
                        error_dialog (this, _("CPL's content is not encrypted."));
                } catch (exception& e) {
                        error_dialog (this, e.what ());
@@ -393,7 +426,7 @@ private:
 
        void content_scale_to_fit_width ()
        {
-               VideoContentList vc = _film_editor->selected_video_content ();
+               VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_width ();
                }
@@ -401,7 +434,7 @@ private:
 
        void content_scale_to_fit_height ()
        {
-               VideoContentList vc = _film_editor->selected_video_content ();
+               VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_height ();
                }
@@ -493,8 +526,13 @@ private:
                        return;
                }
 
+               /* We don't want to hear about any more configuration changes, since they
+                  cause the File menu to be altered, which itself will be deleted around
+                  now (without, as far as I can see, any way for us to find out).
+               */
+               _config_changed_connection.disconnect ();
+               
                maybe_save_then_delete_film ();
-
                ev.Skip ();
        }
 
@@ -507,7 +545,7 @@ private:
                }
                bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
                bool const have_cpl = _film && !_film->cpls().empty ();
-               bool const have_selected_video_content = !_film_editor->selected_video_content().empty();
+               bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
                
                for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
                        
@@ -561,28 +599,31 @@ private:
        
        void setup_menu (wxMenuBar* m)
        {
-               wxMenu* file = new wxMenu;
-               add_item (file, _("New..."), ID_file_new, ALWAYS);
-               add_item (file, _("&Open..."), ID_file_open, ALWAYS);
-               file->AppendSeparator ();
-               add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
-               file->AppendSeparator ();
-               add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
+               _file_menu = new wxMenu;
+               add_item (_file_menu, _("New...\tCtrl-N"), ID_file_new, ALWAYS);
+               add_item (_file_menu, _("&Open...\tCtrl-O"), ID_file_open, ALWAYS);
+               _file_menu->AppendSeparator ();
+               add_item (_file_menu, _("&Save\tCtrl-S"), ID_file_save, NEEDS_FILM);
+               _file_menu->AppendSeparator ();
+               add_item (_file_menu, _("&Properties..."), ID_file_properties, NEEDS_FILM);
+
+               _history_position = _file_menu->GetMenuItems().GetCount();
+
 #ifndef __WXOSX__      
-               file->AppendSeparator ();
+               _file_menu->AppendSeparator ();
 #endif
        
 #ifdef __WXOSX__       
-               add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
+               add_item (_file_menu, _("&Exit"), wxID_EXIT, ALWAYS);
 #else
-               add_item (file, _("&Quit"), wxID_EXIT, ALWAYS);
+               add_item (_file_menu, _("&Quit"), wxID_EXIT, ALWAYS);
 #endif 
        
 #ifdef __WXOSX__       
-               add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+               add_item (_file_menu, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
 #else
                wxMenu* edit = new wxMenu;
-               add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+               add_item (edit, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
 #endif
 
                wxMenu* content = new wxMenu;
@@ -590,13 +631,13 @@ private:
                add_item (content, _("Scale to fit &height"), ID_content_scale_to_fit_height, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
                
                wxMenu* jobs_menu = new wxMenu;
-               add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
-               add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
+               add_item (jobs_menu, _("&Make DCP\tCtrl-M"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+               add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
                add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
                add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
 
                wxMenu* tools = new wxMenu;
-               add_item (tools, _("Hints..."), ID_tools_hints, 0);
+               add_item (tools, _("Hints...\tCtrl-H"), ID_tools_hints, 0);
                add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
                add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
                
@@ -607,7 +648,7 @@ private:
                add_item (help, _("About"), wxID_ABOUT, ALWAYS);
 #endif 
                
-               m->Append (file, _("&File"));
+               m->Append (_file_menu, _("&File"));
 #ifndef __WXOSX__      
                m->Append (edit, _("&Edit"));
 #endif
@@ -616,13 +657,50 @@ private:
                m->Append (tools, _("&Tools"));
                m->Append (help, _("&Help"));
        }
+
+       void config_changed ()
+       {
+               for (int i = 0; i < _history_items; ++i) {
+                       delete _file_menu->Remove (ID_file_history + i);
+               }
+
+               if (_history_separator) {
+                       _file_menu->Remove (_history_separator);
+               }
+               delete _history_separator;
+               _history_separator = 0;
+               
+               int pos = _history_position;
+               
+               vector<boost::filesystem::path> history = Config::instance()->history ();
+               
+               if (!history.empty ()) {
+                       _history_separator = _file_menu->InsertSeparator (pos++);
+               }
+               
+               for (size_t i = 0; i < history.size(); ++i) {
+                       SafeStringStream s;
+                       if (i < 9) {
+                               s << "&" << (i + 1) << " ";
+                       }
+                       s << history[i].string();
+                       _file_menu->Insert (pos++, ID_file_history + i, std_to_wx (s.str ()));
+               }
+
+               _history_items = history.size ();
+       }
        
        FilmEditor* _film_editor;
        FilmViewer* _film_viewer;
        HintsDialog* _hints_dialog;
        ServersListDialog* _servers_list_dialog;
        wxPreferencesEditor* _config_dialog;
+       wxMenu* _file_menu;
        shared_ptr<Film> _film;
+       int _history_items;
+       int _history_position;
+       wxMenuItem* _history_separator;
+       boost::signals2::scoped_connection _config_changed_connection;
 };
 
 static const wxCmdLineEntryDesc command_line_description[] = {
@@ -632,6 +710,9 @@ static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 
+/** @class App
+ *  @brief The magic App class for wxWidgets.
+ */
 class App : public wxApp
 {
        bool OnInit ()
@@ -737,12 +818,21 @@ class App : public wxApp
                return true;
        }
 
+       /* An unhandled exception has occurred inside the main event loop */
        bool OnExceptionInMainLoop ()
        {
-               error_dialog (0, _("An unknown exception occurred.  Please report this problem to the DCP-o-matic author (carl@dcpomatic.com)."));
+               try {
+                       throw;
+               } catch (exception& e) {
+                       error_dialog (0, wxString::Format (_("An exception occurred (%s).  Please report this problem to the DCP-o-matic author (carl@dcpomatic.com)."), e.what ()));
+               } catch (...) {
+                       error_dialog (0, _("An unknown exception occurred.  Please report this problem to the DCP-o-matic author (carl@dcpomatic.com)."));
+               }
+
+               /* This will terminate the program */
                return false;
        }
-               
+       
        void OnUnhandledException ()
        {
                error_dialog (0, _("An unknown exception occurred.  Please report this problem to the DCP-o-matic author (carl@dcpomatic.com)."));