diff -ur a/data/epdfview-ui.xml b/data/epdfview-ui.xml --- a/data/epdfview-ui.xml 2009-10-29 23:45:04.000000000 +0300 +++ b/data/epdfview-ui.xml 2009-10-30 01:19:38.000000000 +0300 @@ -27,6 +27,7 @@ + diff -ur a/po/ru.po b/po/ru.po --- a/po/ru.po 2009-10-29 23:45:04.000000000 +0300 +++ b/po/ru.po 2009-10-30 01:23:47.000000000 +0300 @@ -319,6 +319,10 @@ msgstr "Переключить полноэкранный режим" #: src/gtk/MainView.cxx:179 +msgid "_Dual pages" +msgstr "По две страницы" + +#: src/gtk/MainView.cxx:179 msgid "Show _Toolbar" msgstr "Показать панель инструментов" diff -ur a/src/Config.cxx b/src/Config.cxx --- a/src/Config.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/Config.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -33,6 +33,8 @@ static const gint DEFAULT_WINDOW_Y = 0; static const gboolean DEFAULT_ZOOM_TO_FIT = FALSE; static const gboolean DEFAULT_ZOOM_TO_WIDTH = FALSE; +static const gboolean DEFAULT_DUAL_PAGES = FALSE; +static const guint PAGES_COUNT = 2; // Static member attributes. Config *Config::m_Config = NULL; @@ -445,6 +447,18 @@ } /// +/// @brief Sets the dual pages option. +/// +/// @param activate Set to TRUE to activate the dual pages option. FALSE +/// otherwise. +/// +void +Config::setDualPages (gboolean activate) +{ + g_key_file_set_boolean (m_Values, "main window", "dualPages", activate); +} + +/// /// @brief Gets if show the status bar. /// /// @return TRUE if the status bar should be shown, FALSE otherwise. @@ -489,6 +503,17 @@ } /// +/// @brief Gets the dual pages mode option. +/// +/// @return TRUE if the dual pages mode is activated. FALSE otherwise. +/// +gboolean +Config::dualPages () +{ + return getBoolean ("main window", "dualPages", DEFAULT_DUAL_PAGES); +} + +/// /// @brief Gets the configuration file name. /// /// This function creates the directory where the configuration file should @@ -507,3 +532,15 @@ return configFile; } + +/// +/// @brief Gets count of pages being displayed to user in multi-pages mode. +/// +/// @return The saved value of pages for multi-pages mode. Currently only dual +/// pages mode is supported, so the result is always 2. +/// +guint +Config::getPagesCount () +{ + return PAGES_COUNT; +} diff -ur a/src/Config.h b/src/Config.h --- a/src/Config.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/Config.h 2009-10-30 01:19:38.000000000 +0300 @@ -35,6 +35,7 @@ static void destroy (void); static Config &getConfig (void); static void loadFile (gboolean load); + static guint getPagesCount (void); ~Config (void); @@ -49,6 +50,7 @@ gboolean showToolbar (void); gboolean zoomToFit (void); gboolean zoomToWidth (void); + gboolean dualPages (void); void save(void); void setExternalBrowserCommandLine (const gchar *commandLine); void setOpenFileFolder (const gchar *folder); @@ -59,6 +61,7 @@ void setWindowPos (gint x, gint y); void setZoomToFit (gboolean active); void setZoomToWidth (gboolean active); + void setDualPages (gboolean active); protected: /// The configuration values. diff -ur a/src/FindPter.cxx b/src/FindPter.cxx --- a/src/FindPter.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/FindPter.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -89,7 +89,10 @@ { DocumentRectangle *rect = ((DocumentRectangle *)m_CurrentMatch->data); m_Document->notifyFindChanged (m_FindPage, rect); - m_Document->goToPage (m_FindPage); + if ( !m_Document->isPageVisible (m_FindPage) ) + { + m_Document->goToPage (m_FindPage); + } } else { @@ -118,7 +121,10 @@ { DocumentRectangle *rect = ((DocumentRectangle *)m_CurrentMatch->data); m_Document->notifyFindChanged (m_FindPage, rect); - m_Document->goToPage (m_FindPage); + if ( !m_Document->isPageVisible (m_FindPage) ) + { + m_Document->goToPage (m_FindPage); + } } else { @@ -229,7 +235,10 @@ } DocumentRectangle *rect = (DocumentRectangle *)m_CurrentMatch->data; m_Document->notifyFindChanged (m_FindPage, rect); - m_Document->goToPage (m_FindPage); + if ( !m_Document->isPageVisible (m_FindPage) ) + { + m_Document->goToPage (m_FindPage); + } IFindView &view = getView (); view.setInformationText (""); diff -ur a/src/gtk/MainView.cxx b/src/gtk/MainView.cxx --- a/src/gtk/MainView.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/gtk/MainView.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -53,6 +53,7 @@ gpointer); static void main_window_find_cb (GtkWidget *, gpointer); static void main_window_fullscreen_cb (GtkToggleAction *, gpointer); +static void main_window_dualpages_cb (GtkToggleAction *, gpointer); static gboolean main_window_moved_or_resized_cb (GtkWidget *, GdkEventConfigure *, gpointer); static void main_window_go_to_first_page_cb (GtkWidget *, gpointer); @@ -176,6 +177,10 @@ N_("Toggle full screen window"), G_CALLBACK (main_window_fullscreen_cb), FALSE }, + { "DualPages", NULL, N_("_Dual pages"), NULL, + N_("Toggle single/dual view mode"), + G_CALLBACK (main_window_dualpages_cb), FALSE }, + { "ShowToolBar", NULL, N_("Show _Toolbar"), NULL, N_("Show or hide the toolbar"), G_CALLBACK (main_window_show_toolbar_cb), TRUE }, @@ -298,6 +303,14 @@ } void +MainView::activeDualPages (gboolean active) +{ + GtkAction *dualPages = + gtk_ui_manager_get_action (m_UIManager, "/MenuBar/ViewMenu/DualPages"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (dualPages), active); +} + +void MainView::activePageModeScroll (gboolean active) { GtkAction *action = @@ -624,6 +637,14 @@ } void +MainView::sensitiveDualPages (gboolean sensitive) +{ + GtkAction *dualPages = + gtk_ui_manager_get_action (m_UIManager, "/MenuBar/ViewMenu/DualPages"); + gtk_action_set_sensitive (dualPages, sensitive); +} + +void MainView::show (void) { Config &config = Config::getConfig (); @@ -1183,6 +1204,18 @@ } /// +/// @brief The user tried to switch to dual pages mode. +/// +void +main_window_dualpages_cb (GtkToggleAction *action, gpointer data) +{ + g_assert ( NULL != data && "The data parameter is NULL."); + + MainPter *pter = (MainPter *)data; + pter->dualPagesActivated (gtk_toggle_action_get_active (action)); +} + +/// /// @brief Called when the window is moved or resized. /// /// This is used to save the current window's position and size. diff -ur a/src/gtk/MainView.h b/src/gtk/MainView.h --- a/src/gtk/MainView.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/gtk/MainView.h 2009-10-30 01:19:38.000000000 +0300 @@ -35,6 +35,7 @@ void activeZoomFit (gboolean active); void activeZoomWidth (gboolean active); + void activeDualPages (gboolean active); void activePageModeScroll (gboolean active); void activePageModeText (gboolean active); gboolean isIndexVisible () const; @@ -61,6 +62,7 @@ void sensitiveZoomOut (gboolean sensitive); void sensitiveZoomFit (gboolean sensitive); void sensitiveZoomWidth (gboolean sensitive); + void sensitiveDualPages (gboolean sensitive); void show (void); void showErrorMessage (const gchar *title, const gchar *body); void showIndex (gboolean show); diff -ur a/src/gtk/PageView.cxx b/src/gtk/PageView.cxx --- a/src/gtk/PageView.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/gtk/PageView.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -54,10 +54,15 @@ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - // The actual page image. - m_PageImage = gtk_image_new (); - gtk_misc_set_padding (GTK_MISC (m_PageImage), - PAGE_VIEW_PADDING, PAGE_VIEW_PADDING); + guint pagesCount = Config::getPagesCount (); + // The actual page images. + m_PageImage = new GtkWidget *[pagesCount]; + for ( guint pageNum = 0; pageNum < pagesCount ; ++pageNum ) + { + m_PageImage[pageNum] = gtk_image_new (); + gtk_misc_set_padding (GTK_MISC (m_PageImage[pageNum]), + PAGE_VIEW_PADDING, PAGE_VIEW_PADDING); + } // I want to be able to drag the page with the left mouse // button, because that will make possible to move the page @@ -66,7 +71,12 @@ // GdkWindow, so I have to add the event box that will receive // the mouse events. m_EventBox = gtk_event_box_new (); - gtk_container_add (GTK_CONTAINER (m_EventBox), m_PageImage); + m_ImageBox = gtk_hbox_new (TRUE, 0); + for ( guint pageNum = 0; pageNum < pagesCount ; ++pageNum ) + { + gtk_container_add (GTK_CONTAINER (m_ImageBox), m_PageImage[pageNum]); + } + gtk_container_add (GTK_CONTAINER (m_EventBox), m_ImageBox); gtk_scrolled_window_add_with_viewport ( GTK_SCROLLED_WINDOW (m_PageScroll), m_EventBox); @@ -75,6 +85,7 @@ PageView::~PageView () { + delete[] m_PageImage; } gdouble @@ -163,7 +174,7 @@ void PageView::resizePage (gint width, gint height) { - GdkPixbuf *originalPage = gtk_image_get_pixbuf (GTK_IMAGE (m_PageImage)); + GdkPixbuf *originalPage = gtk_image_get_pixbuf (GTK_IMAGE (m_PageImage[0])); if ( NULL != originalPage ) { GdkPixbuf *scaledPage = gdk_pixbuf_scale_simple (originalPage, @@ -171,7 +182,7 @@ GDK_INTERP_NEAREST); if ( NULL != scaledPage ) { - gtk_image_set_from_pixbuf (GTK_IMAGE (m_PageImage), scaledPage); + gtk_image_set_from_pixbuf (GTK_IMAGE (m_PageImage[0]), scaledPage); g_object_unref (scaledPage); } } @@ -245,7 +256,7 @@ GtkAdjustment *hAdjustment = gtk_scrolled_window_get_hadjustment ( GTK_SCROLLED_WINDOW (m_PageScroll)); gdouble hAdjValue = hAdjustment->page_size * - (gdouble)dx / m_PageImage->allocation.width; + (gdouble)dx / m_PageImage[0]->allocation.width; gtk_adjustment_set_value (hAdjustment, CLAMP (scrollX - hAdjValue, hAdjustment->lower, @@ -254,7 +265,7 @@ GtkAdjustment *vAdjustment = gtk_scrolled_window_get_vadjustment ( GTK_SCROLLED_WINDOW (m_PageScroll)); gdouble vAdjValue = vAdjustment->page_size * - (gdouble)dy / m_PageImage->allocation.height; + (gdouble)dy / m_PageImage[0]->allocation.height; gtk_adjustment_set_value (vAdjustment, CLAMP (scrollY - vAdjValue, vAdjustment->lower, @@ -279,12 +290,14 @@ } void -PageView::showPage (DocumentPage *page, PageScroll scroll) +PageView::showPage (DocumentPage *page, PageScroll scroll, guint index) { - gtk_image_set_from_pixbuf (GTK_IMAGE (m_PageImage), NULL); + GtkWidget *image = m_PageImage[index]; + gtk_image_set_from_pixbuf (GTK_IMAGE (image), NULL); GdkPixbuf *pixbuf = getPixbufFromPage (page); - gtk_image_set_from_pixbuf (GTK_IMAGE (m_PageImage), pixbuf); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); g_object_unref (pixbuf); + // Set the vertical scroll to the specified. if ( PAGE_SCROLL_NONE != scroll ) { @@ -302,7 +315,20 @@ } void -PageView::showText (const gchar *text) +PageView::showSecondPage (gboolean show) +{ + if ( show ) + { + gtk_widget_show (m_PageImage[1]); + } + else + { + gtk_widget_hide (m_PageImage[1]); + } +} + +void +PageView::showText (const gchar *text, guint index) { // I need to make the changes to the original Pixbuf that the GtkImage // controls shows. @@ -312,14 +338,16 @@ // server-side and therefore drawable, make the modifications // and set the modified pixbuf again to the GtkImage control. // - GdkPixbuf *originalPage = gtk_image_get_pixbuf (GTK_IMAGE (m_PageImage)); + + GtkWidget* image = m_PageImage[index]; + GdkPixbuf *originalPage = gtk_image_get_pixbuf (GTK_IMAGE (image)); if ( NULL != originalPage ) { gint width = gdk_pixbuf_get_width (originalPage); gint height = gdk_pixbuf_get_height (originalPage); PangoLayout *layout = - gtk_widget_create_pango_layout (m_PageImage, text); + gtk_widget_create_pango_layout (image, text); // I want the text to be half the page's width. // Since I don't know the font's size I just set the font @@ -349,7 +377,7 @@ gdk_pixbuf_render_pixmap_and_mask (originalPage, &pixmap, &mask, 0); gdk_draw_layout (pixmap, - m_PageImage->style->black_gc, + image->style->black_gc, (width - logicalRectangle.width) / 2, height / 4 - logicalRectangle.height / 2, layout); @@ -369,7 +397,7 @@ g_object_unref (mask); } - gtk_image_set_from_pixbuf (GTK_IMAGE (m_PageImage), modifiedPage); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), modifiedPage); g_object_unref (modifiedPage); } } @@ -385,7 +413,7 @@ } void -PageView::getPagePosition (gint widgetX, gint widgetY, gint *pageX, gint *pageY) +PageView::getPagePosition (gint widgetX, gint widgetY, gint *pageX, gint *pageY, guint index) { g_assert ( NULL != pageX && "Tried to save the page's X to NULL."); g_assert ( NULL != pageY && "Tried to save the page's Y to NULL."); @@ -395,17 +423,18 @@ // how many widget space is being used for padding. gint horizontalPadding = PAGE_VIEW_PADDING; gint verticalPadding = PAGE_VIEW_PADDING; - GdkPixbuf *page = gtk_image_get_pixbuf (GTK_IMAGE (m_PageImage)); + GdkPixbuf *page = gtk_image_get_pixbuf (GTK_IMAGE (m_PageImage[index])); if ( NULL != page ) { horizontalPadding = - (m_PageImage->allocation.width - gdk_pixbuf_get_width (page)) / 2; + (m_PageImage[index]->allocation.width - gdk_pixbuf_get_width (page)) / 2; verticalPadding = - (m_PageImage->allocation.height - gdk_pixbuf_get_height (page)) / 2; + (m_PageImage[index]->allocation.height - gdk_pixbuf_get_height (page)) / 2; } - - *pageX = widgetX - horizontalPadding + (gint)getHorizontalScroll (); - *pageY = widgetY - verticalPadding + (gint)getVerticalScroll (); + *pageX = widgetX - horizontalPadding + (gint)getHorizontalScroll () - + m_PageImage[index]->allocation.x; + *pageY = widgetY - verticalPadding + (gint)getVerticalScroll () - + m_PageImage[index]->allocation.y; } /// @@ -443,10 +472,7 @@ gint event_x; gint event_y; gtk_widget_get_pointer (view->getTopWidget (), &event_x, &event_y); - gint x; - gint y; - view->getPagePosition (event_x, event_y, &x, &y); - view->getPresenter ()->mouseButtonPressed (event->button, x, y); + view->getPresenter ()->mouseButtonPressed (event->button, event_x, event_y); gtk_widget_grab_focus(view->getTopWidget()); @@ -481,10 +507,7 @@ gint event_x; gint event_y; gtk_widget_get_pointer (view->getTopWidget (), &event_x, &event_y); - gint x; - gint y; - view->getPagePosition (event_x, event_y, &x, &y); - view->getPresenter ()->mouseMoved (x, y); + view->getPresenter ()->mouseMoved (event_x, event_y); return TRUE; } diff -ur a/src/gtk/PageView.h b/src/gtk/PageView.h --- a/src/gtk/PageView.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/gtk/PageView.h 2009-10-30 01:19:38.000000000 +0300 @@ -29,7 +29,7 @@ gdouble getHorizontalScroll (void); GtkWidget *getTopWidget (void); void getPagePosition (gint widgetX, gint widgetY, - gint *pageX, gint *pageY); + gint *pageX, gint *pageY, guint index); void getSize (gint *width, gint *height); gdouble getVerticalScroll (void); void makeRectangleVisible (DocumentRectangle &rect, gdouble scale); @@ -38,13 +38,15 @@ gint dx, gint dy); void setCursor (PageCursor cursorType); void setPresenter (PagePter *pter); - void showPage (DocumentPage *page, PageScroll scroll); - void showText (const gchar *text); + void showPage (DocumentPage *page, PageScroll scroll, guint index = 0); + void showText (const gchar *text, guint index); + void showSecondPage (gboolean show); protected: PageCursor m_CurrentCursor; GtkWidget *m_EventBox; - GtkWidget *m_PageImage; + GtkWidget *m_ImageBox; + GtkWidget **m_PageImage; GtkWidget *m_PageScroll; GdkPixbuf *getPixbufFromPage (DocumentPage *page); diff -ur a/src/IDocument.cxx b/src/IDocument.cxx --- a/src/IDocument.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/IDocument.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -31,7 +31,7 @@ static const gdouble ZOOM_IN_MAX = 4.0; static const gdouble ZOOM_OUT_FACTOR = (1.0 / ZOOM_IN_FACTOR); static const gdouble ZOOM_OUT_MAX = 0.05409; -static const guint CACHE_SIZE = 3; +static const guint CACHE_SIZE = 3 * Config::getPagesCount (); /// This is the error domain that will be used to report Document's errors. GQuark IDocument::errorQuark = 0; @@ -278,11 +278,14 @@ { // Empty the cache to avoid displaying pages from previous file. clearCache (); - // Add the two first pages, if they exists, to the cache. - addPageToCache (1); - if ( 1 < getNumPages () ) + + // Add the first pages to the cache. + guint pageMax = getCountOfVisiblePages () * 2; + for ( guint pageNum = 1; + pageNum <= pageMax && pageNum <= (guint)getNumPages (); + ++pageNum) { - addPageToCache (2); + addPageToCache (pageNum); } for ( GList *item = g_list_first (m_Observers) ; NULL != item ; @@ -954,16 +957,19 @@ } /// -/// @brief Gets the document's current page image. +/// @brief Gets one of the document's current pages. +/// +/// @param index The index of the desired page in the list of current pages. +/// Should always be 0 for single page mode. /// -/// @return The rendered image of the current page or NULL if the image +/// @return The rendered image of the selected page or NULL if the image /// is not yet available. /// DocumentPage * -IDocument::getCurrentPage () +IDocument::getCurrentPage (guint index) { - PageCache *cachedPage = getCachedPage (m_CurrentPage); + PageCache *cachedPage = getCachedPage (m_CurrentPage + index); if ( NULL == cachedPage ) { return NULL; @@ -976,7 +982,7 @@ if ( NULL != pageImage ) { /// XXX: Only non rotated documents for now. - if ( m_FindPage == m_CurrentPage && NULL != m_FindRect && + if ( m_FindPage == m_CurrentPage + (gint)index && NULL != m_FindRect && 0 == getRotation () ) { pageImage->setSelection (*m_FindRect, getZoom ()); @@ -1088,7 +1094,7 @@ void IDocument::goToNextPage () { - goToPage (getCurrentPageNum() + 1); + goToPage (getCurrentPageNum() + getCountOfVisiblePages ()); } /// @@ -1104,18 +1110,49 @@ void IDocument::goToPage (gint pageNum) { - if ( pageNum != m_CurrentPage && 1 <= pageNum && pageNum <= getNumPages ()) + guint pagesCount = getCountOfVisiblePages (); + if ( pageNum <= getNumPages () && + pageNum + pagesCount > 1 ) { - m_CurrentPage = pageNum; + // Check boundaries first + if ( pageNum < 1 ) + { + m_CurrentPage = 1; + } + else if ( pageNum + pagesCount - 1 > (guint)getNumPages () ) + { + m_CurrentPage = getNumPages () - pagesCount + 1; + } + else if ( m_CurrentPage == pageNum ) + { + return; + } + else { + m_CurrentPage = pageNum; + } + + // Process the currently visible pages. + for ( gint num = m_CurrentPage ; + num < m_CurrentPage + (gint)pagesCount && num <= getNumPages (); + ++num ) + { + addPageToCache (num); + } - addPageToCache (m_CurrentPage); - if ( 1 < m_CurrentPage ) + // Process a bunch of previous pages. + for ( gint num = m_CurrentPage - 1; + num >= m_CurrentPage - (gint)pagesCount && num > 0; + --num ) { - addPageToCache (m_CurrentPage - 1); + addPageToCache (num); } - if ( m_CurrentPage < getNumPages () ) + + // Process a bunch of next pages. + for ( gint num = m_CurrentPage + pagesCount; + num < m_CurrentPage + (gint)pagesCount * 2 && num <= getNumPages (); + ++num ) { - addPageToCache (m_CurrentPage + 1); + addPageToCache (num); } notifyPageChanged (); @@ -1131,7 +1168,7 @@ void IDocument::goToPreviousPage () { - goToPage (getCurrentPageNum () - 1); + goToPage (getCurrentPageNum() - getCountOfVisiblePages ()); } /// @@ -1479,24 +1516,25 @@ } /// -/// @brief Checks if the current page has a link on a given position. +/// @brief Checks if one of the current pages has a link on a given position. /// -/// Checks if under a given position the document's current page have a -/// link. +/// Checks if under a given position one of the document's current pages have +/// a link. /// /// @param x The X coordinate of the position to check. /// @param y The Y coordinate of the position to check. +/// @param index The index of the page to look for a link on. /// /// @return TRUE if the document's current page has a link under the given /// position. FALSE otherwise. /// gboolean -IDocument::hasLinkAtPosition (gint x, gint y) +IDocument::hasLinkAtPosition (gint x, gint y, guint index) { // XXX For now only non rotated pages. if ( 0 == getRotation () ) { - DocumentPage *page = getCurrentPage (); + DocumentPage *page = getCurrentPage (index); if ( NULL != page ) { return (NULL != page->getLinkAtPosition (x, y)); @@ -1513,11 +1551,12 @@ /// /// @param x The X coordinate of the current page's link to activate. /// @param y The Y coordinate of the current page's link to activate. +/// @param index The index of the page containing the link. /// void -IDocument::activateLinkAtPosition (gint x, gint y) +IDocument::activateLinkAtPosition (gint x, gint y, guint index) { - DocumentPage *page = getCurrentPage (); + DocumentPage *page = getCurrentPage (index); // XXX For now only non rotated pages. if ( NULL != page && 0 == getRotation () ) { @@ -1528,3 +1567,29 @@ } } } + +/// +/// @brief Gets visibility of a particular page. +/// +/// @return TRUE if the selected page is currently on the screen. +/// FALSE otherwise. +/// +gboolean +IDocument::isPageVisible (guint pageNum) +{ + return ( pageNum >= (guint)m_CurrentPage ) && + ( pageNum < (guint)m_CurrentPage + getCountOfVisiblePages () ); +} + +/// +/// @brief Gets count of pages being currently displayed to user. +/// +/// @return The result depends on current view mode. The value is always 1 for +/// single-page mode and 2 for dual-pages mode. +/// +guint +IDocument::getCountOfVisiblePages () +{ + Config &config = Config::getConfig (); + return ( config.dualPages () ) ? Config::getPagesCount () : 1; +} diff -ur a/src/IDocument.h b/src/IDocument.h --- a/src/IDocument.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/IDocument.h 2009-10-30 01:19:38.000000000 +0300 @@ -265,8 +265,11 @@ virtual gboolean saveFile (const gchar *filename, GError **error) = 0; - virtual GdkRegion* getTextRegion (DocumentRectangle *rect) = 0; - virtual void setTextSelection (DocumentRectangle *rect) = 0; + virtual GdkRegion* getTextRegion (DocumentRectangle *rect, + guint index ) = 0; + virtual gchar *getTextSelection (DocumentRectangle *rect, + guint index) = 0; + virtual void setTextSelection (gchar *text) = 0; void attach (const IDocumentObserver *observer); void detach (const IDocumentObserver *observer); @@ -318,7 +321,8 @@ void getPageSize (gdouble *width, gdouble *height); gint getNumPages (void); void setNumPages (gint numPages); - DocumentPage *getCurrentPage (void); + DocumentPage *getCurrentPage (guint index); + DocumentPage *getPairedPage (void); DocumentPage *getEmptyPage (void); gint getCurrentPageNum (void); DocumentOutline *getOutline (void); @@ -349,8 +353,11 @@ void zoomToFit (gint width, gint height); void zoomToWidth (gint width); - gboolean hasLinkAtPosition (gint x, gint y); - void activateLinkAtPosition (gint x, gint y); + gboolean hasLinkAtPosition (gint x, gint y, guint index); + void activateLinkAtPosition (gint x, gint y, guint index); + + gboolean isPageVisible (guint pageNum); + guint getCountOfVisiblePages (void); static GQuark getErrorQuark (void); static gchar *getErrorMessage (DocumentError errorCode); diff -ur a/src/IMainView.h b/src/IMainView.h --- a/src/IMainView.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/IMainView.h 2009-10-30 01:19:38.000000000 +0300 @@ -78,6 +78,17 @@ virtual void activeZoomWidth (gboolean active) = 0; /// + /// @brief Actives or deactivates the dual pages option. + /// + /// The view must change the state of the dual pages option + /// to @a active. + /// + /// @param active TRUE if the option must be activated, FALSE + /// otherwise. + /// + virtual void activeDualPages (gboolean active) = 0; + + /// /// @brief Tells if the index is shown. /// /// @return @c TRUE if the index is currently shown, @c FALSE @@ -355,6 +366,17 @@ /// virtual void sensitiveZoomWidth (gboolean sensitive) = 0; + /// @brief Changes the sensitivity of the "Dual Pages" action. + /// + /// The view must change the sensitivity (it's called enabled or + /// disabled on some toolkits) of the "Dual Pages" action. + /// + /// @param sensitive Set to TRUE if need to make sensitive (enable) + /// the action (enable) or FALSE to + /// insensitive (disable) it. + /// + virtual void sensitiveDualPages (gboolean sensitive) = 0; + /// /// @brief Sets the page view's cursor. /// diff -ur a/src/IPageView.h b/src/IPageView.h --- a/src/IPageView.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/IPageView.h 2009-10-30 01:19:38.000000000 +0300 @@ -168,7 +168,7 @@ /// /// @brief Shows a document's page. /// - /// The view must show the @a page the the presenter gives to it. + /// The view must show the @a page the presenter gives to it. /// Of course the view can use scroll bars if the page doesn't fit /// the current view's size, but MUST NOT change the page's size. /// @@ -178,23 +178,60 @@ /// @param page The document's page to show. /// @param scroll Tells the main view how to scroll the /// new page. + /// @param index The index on the page to be displayed in the list + /// of current pages. This is only used in dual pages + /// and is 0 by default (for single page mode). /// - virtual void showPage (DocumentPage *page, PageScroll scroll) = 0; + virtual void showPage (DocumentPage *page, PageScroll scroll, + guint index = 0) = 0; /// /// @brief Shows text on the page. /// /// This is only used when the page is still loading but the /// presenter wants to show something on the user. The presenter - /// passes a text string to show during the rendering of the - /// current page. The view should put this string at the middle - /// of the currently shown page. + /// passes a text string to show during the rendering of a page. + /// The view should put this string at the middle of one of the + /// currently shown pages. /// /// @param text The text to show on the middle of the page. + /// @param index The index of the page to show the text on. Should + /// always be 0 for single page mode. /// - virtual void showText (const gchar *text) = 0; - - + virtual void showText (const gchar *text, guint index) = 0; + + /// + /// @brief Shows the second page widget. + /// + /// The view must show the paired page or hide it depending + /// on the @a show parameter. + /// + /// The presenter calls this when user toggles the "Dual Pages" + /// option. + /// + /// @param show Set to TRUE if the second page should be shown. + /// FALSE otherwise. + /// + virtual void showSecondPage (gboolean show) = 0; + + /// + /// @brief Gets a dot coordinate relative to a page. + /// + /// The view must provide to the presenter the position of a dot + /// relative to one of the current pages. + /// + /// @param widgetX The X coordinate relative to a widget. + /// @param widgetY The Y coordinate relative to a widget. + /// @param pageX The location to save the X coordinate relative to + /// a page. + /// @param pageY The location to save the Y coordinate relative to + /// a page. + /// @param index The index of the page in the list of current pages. + /// + virtual void getPagePosition (gint widgetX, gint widgetY, + gint *pageX, gint *pageY, + guint index) = 0; + protected: /// /// @brief Creates a new IPageView object. diff -ur a/src/MainPter.cxx b/src/MainPter.cxx --- a/src/MainPter.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/MainPter.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -136,12 +136,15 @@ view.sensitiveZoomOut (TRUE); view.sensitiveZoomFit (TRUE); view.sensitiveZoomWidth (TRUE); + view.sensitiveDualPages (TRUE); view.activeZoomFit (config.zoomToFit ()); view.activeZoomWidth (config.zoomToWidth ()); + view.activeDualPages (config.dualPages ()); #if defined (HAVE_CUPS) view.sensitivePrint (TRUE); #endif // HAVE_CUPS + dualPagesActivated (config.dualPages ()); checkZoomSettings (); // Check if we should see the outlines. @@ -170,6 +173,7 @@ view.sensitiveZoomOut (FALSE); view.sensitiveZoomFit (FALSE); view.sensitiveZoomWidth (FALSE); + view.sensitiveDualPages (FALSE); showSidebar = FALSE; #if defined (HAVE_CUPS) view.sensitivePrint (FALSE); @@ -332,7 +336,7 @@ /// /// @brief The "Full Screen" action was activated. /// -/// @param show TRUE if Full Screen is active, FALSE otherwise. +/// @param active TRUE if Full Screen is active, FALSE otherwise. /// void MainPter::fullScreenActivated (gboolean active) @@ -341,6 +345,24 @@ } /// +/// @brief The "Dual Pages" action was activated. +/// +/// @param active TRUE if Dual Pages is active, FALSE otherwise. +/// +void +MainPter::dualPagesActivated (gboolean active) +{ + Config &config = Config::getConfig (); + config.setDualPages (active); + if (active) + { + // Need to recheck boundaries + m_Document->goToPage (m_Document->getCurrentPageNum ()); + } + m_PagePter->setDualPages (active); +} + +/// /// @brief The "Go To First Page" action was activated. /// void diff -ur a/src/MainPter.h b/src/MainPter.h --- a/src/MainPter.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/MainPter.h 2009-10-30 01:19:38.000000000 +0300 @@ -50,6 +50,7 @@ void aboutBoxLinkActivated (const gchar *link); void findActivated (void); void fullScreenActivated (gboolean active); + void dualPagesActivated (gboolean active); void goToFirstPageActivated (void); void goToLastPageActivated (void); void goToNextPageActivated (void); diff -ur a/src/PagePter.cxx b/src/PagePter.cxx --- a/src/PagePter.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/PagePter.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -31,6 +31,8 @@ PagePter *pter; /// The scroll to be used when the page is available. PageScroll scroll; + /// Location of the page to be updated (left, right or both). + PageLocation location; } PageNotAvailableData; @@ -54,14 +56,19 @@ }; // Global variables. -static gboolean g_WaitingForPage = FALSE; +static const guint g_pagesCount = Config::getPagesCount (); +static gboolean* g_WaitingForPage = new gboolean[g_pagesCount]; /// /// @brief Constructs a new PagePter object. /// PagePter::PagePter (IDocument *document) { - m_LastSelection = NULL; + m_LastSelection = new GdkRegion *[g_pagesCount]; + for ( guint pageNum = 0; pageNum < g_pagesCount ; ++pageNum ) + { + m_LastSelection[pageNum] = NULL; + } m_Document = document; m_Document->attach (this); m_DragInfo = NULL; @@ -76,6 +83,7 @@ PagePter::~PagePter () { delete m_DragInfo; + delete[] m_LastSelection; m_Document->detach (this); } @@ -121,24 +129,33 @@ { if ( 1 == button ) { - if ( m_Document->hasLinkAtPosition (x, y) ) + IPageView &view = getView (); + + // Check for a link first + gint maxNum = m_Document->getCountOfVisiblePages ();; + for ( gint pageNum = 0; pageNum < maxNum ; ++pageNum ) { - m_Document->activateLinkAtPosition (x, y); + gint pageX = 0; + gint pageY = 0; + view.getPagePosition (x, y, &pageX, &pageY, pageNum); + if ( m_Document->hasLinkAtPosition (pageX, pageY, pageNum) ) + { + m_Document->activateLinkAtPosition (pageX, pageY, pageNum); + return; + } } - else - { - m_DragInfo = new DragInfo; - m_DragInfo->x = x; - m_DragInfo->y = y; - IPageView &view = getView (); - m_DragInfo->startX = x; - m_DragInfo->startY = y; - if(m_ScrollMode == PagePterModeScroll) - view.setCursor (PAGE_VIEW_CURSOR_DRAG); - else - view.setCursor (PAGE_VIEW_CURSOR_SELECT_TEXT); - } + // Link is not found + m_DragInfo = new DragInfo; + m_DragInfo->x = x; + m_DragInfo->y = y; + + m_DragInfo->startX = x; + m_DragInfo->startY = y; + if(m_ScrollMode == PagePterModeScroll) + view.setCursor (PAGE_VIEW_CURSOR_DRAG); + else + view.setCursor (PAGE_VIEW_CURSOR_SELECT_TEXT); } } @@ -155,17 +172,46 @@ { if ( 1 == button ) { - if(m_LastSelection) - gdk_region_destroy(m_LastSelection); - m_LastSelection = NULL; + for ( guint pageNum = 0; pageNum < g_pagesCount ; ++pageNum ) + { + if (m_LastSelection[pageNum]) + gdk_region_destroy(m_LastSelection[pageNum]); + m_LastSelection[pageNum] = NULL; + } if ( m_Document->isLoaded() && m_ScrollMode == PagePterModeSelectText && NULL != m_DragInfo ) { - DocumentRectangle rect(m_DragInfo->startX, m_DragInfo->startY, - m_DragInfo->x, m_DragInfo->y); - m_Document->setTextSelection(&rect); + + gint maxNum = m_Document->getCountOfVisiblePages (); + gchar **selections = new gchar *[maxNum]; + IPageView &view = getView (); + for ( gint pageNum = 0; pageNum < maxNum ; ++pageNum ) + { + gint pageStartX = 0; + gint pageStartY = 0; + view.getPagePosition (m_DragInfo->startX, m_DragInfo->startY, + &pageStartX, &pageStartY, pageNum); + gint pageX = 0; + gint pageY = 0; + view.getPagePosition (m_DragInfo->x, m_DragInfo->y, + &pageX, &pageY, pageNum); + + DocumentRectangle rect(pageStartX, pageStartY, pageX, pageY); + selections[pageNum] = m_Document->getTextSelection(&rect, + pageNum); + } + GString *selection = g_string_new (NULL); + for ( gint pageNum = 0; pageNum < maxNum ; ++pageNum ) + { + if ( selections[pageNum] ) + { + g_string_append (selection, selections[pageNum]); + g_free (selections[pageNum]); + } + } + m_Document->setTextSelection (g_string_free(selection, FALSE)); } delete m_DragInfo; @@ -192,9 +238,23 @@ PagePter::mouseMoved (gint x, gint y) { IPageView &view = getView (); + gint maxNum = m_Document->getCountOfVisiblePages (); + if ( NULL == m_DragInfo ) { - if ( m_Document->hasLinkAtPosition (x, y) ) + gboolean isFound = FALSE; + for ( gint pageNum = 0; pageNum < maxNum ; ++pageNum ) + { + gint pageX = 0; + gint pageY = 0; + view.getPagePosition (x, y, &pageX, &pageY, pageNum); + if ( m_Document->hasLinkAtPosition (pageX, pageY, pageNum) ) + { + isFound = TRUE; + } + } + + if ( isFound ) { view.setCursor (PAGE_VIEW_CURSOR_LINK); } @@ -207,26 +267,39 @@ m_DragInfo->x = x; m_DragInfo->y = y; - if(m_ScrollMode == PagePterModeScroll){ - view.scrollPage (view.getHorizontalScroll (), view.getVerticalScroll (), + if (m_ScrollMode == PagePterModeScroll){ + view.scrollPage (view.getHorizontalScroll (), + view.getVerticalScroll (), x - m_DragInfo->startX, y - m_DragInfo->startY); } else{ - if(!m_Document->isLoaded()) + if (!m_Document->isLoaded()) return ; - - DocumentRectangle rect(m_DragInfo->startX, m_DragInfo->startY, - m_DragInfo->x, m_DragInfo->y); - - GdkRegion *region = m_Document->getTextRegion (&rect); - - if( !m_LastSelection || !gdk_region_equal(m_LastSelection, region)){ - if(m_LastSelection) - gdk_region_destroy(m_LastSelection); - m_LastSelection = gdk_region_copy(region); - DocumentPage *page = m_Document->getCurrentPage(); - if ( NULL != page ) - refreshPage (PAGE_SCROLL_NONE, FALSE); + + for ( gint pageNum = 0; pageNum < maxNum ; ++pageNum ) + { + gint pageStartX = 0; + gint pageStartY = 0; + view.getPagePosition (m_DragInfo->startX, m_DragInfo->startY, + &pageStartX, &pageStartY, pageNum); + gint pageX = 0; + gint pageY = 0; + view.getPagePosition (m_DragInfo->x, m_DragInfo->y, + &pageX, &pageY, pageNum); + + DocumentRectangle rect(pageStartX, pageStartY, pageX, pageY); + GdkRegion *region = m_Document->getTextRegion (&rect, pageNum); + + if ( !m_LastSelection[pageNum] || + !gdk_region_equal(m_LastSelection[pageNum], region)) + { + if(m_LastSelection[pageNum]) + gdk_region_destroy(m_LastSelection[pageNum]); + m_LastSelection[pageNum] = gdk_region_copy(region); + DocumentPage *page = m_Document->getCurrentPage (pageNum); + if ( NULL != page ) + refreshPage (PAGE_SCROLL_NONE, FALSE); + } } } } @@ -245,35 +318,50 @@ void PagePter::notifyLoad () { - g_WaitingForPage = FALSE; + for ( guint pageNum = 0 ; pageNum < g_pagesCount ; ++pageNum ) + { + g_WaitingForPage[pageNum] = FALSE; + } refreshPage (PAGE_SCROLL_START, FALSE); } void PagePter::notifyPageChanged (gint pageNum) { - g_WaitingForPage = FALSE; + for ( guint pageNum = 0 ; pageNum < g_pagesCount ; ++pageNum ) + { + g_WaitingForPage[pageNum] = FALSE; + } refreshPage (m_NextPageScroll, FALSE); } void PagePter::notifyPageRotated (gint rotation) { - g_WaitingForPage = FALSE; + for ( guint pageNum = 0 ; pageNum < g_pagesCount ; ++pageNum ) + { + g_WaitingForPage[pageNum] = FALSE; + } refreshPage (PAGE_SCROLL_START, FALSE); } void PagePter::notifyPageZoomed (gdouble zoom) { - g_WaitingForPage = FALSE; + for ( guint pageNum = 0 ; pageNum < g_pagesCount ; ++pageNum ) + { + g_WaitingForPage[pageNum] = FALSE; + } refreshPage (PAGE_SCROLL_NONE, TRUE); } void PagePter::notifyReload () { - g_WaitingForPage = FALSE; + for ( guint pageNum = 0 ; pageNum < g_pagesCount ; ++pageNum ) + { + g_WaitingForPage[pageNum] = FALSE; + } refreshPage (PAGE_SCROLL_NONE, FALSE); } @@ -283,9 +371,14 @@ g_assert (NULL != user && "The data parameter in NULL."); PageNotAvailableData *data = (PageNotAvailableData *)user; - if ( g_WaitingForPage ) + gboolean isWaiting = FALSE; + for ( guint num = 0 ; num < g_pagesCount ; ++num ) + { + isWaiting |= g_WaitingForPage[num]; + } + if ( isWaiting ) { - data->pter->refreshPage (data->scroll, FALSE); + data->pter->refreshPage (data->scroll, FALSE, data->location); return TRUE; } delete data; @@ -303,46 +396,63 @@ /// to show the scroll this new page. /// @param wasZoomed If this is set to TRUE then it means that the page /// has been refreshed because its scaled has changed. +/// @param pageLocation This determines location (left, right or both) of the +/// page to be refreshed. /// void -PagePter::refreshPage (PageScroll pageScroll, gboolean wasZoomed) +PagePter::refreshPage (PageScroll pageScroll, gboolean wasZoomed, + PageLocation pageLocation) { g_assert (NULL != m_Document && "Tried to show a page from a NULL document."); - if ( m_Document->isLoaded () ) { IPageView &view = getView (); - DocumentPage *documentPage = m_Document->getCurrentPage (); - if ( NULL != documentPage ) + + Config &config = Config::getConfig (); + if ( !config.dualPages () ) { - g_WaitingForPage = FALSE; - if ( NULL != m_LastSelection ) - documentPage->setSelection(m_LastSelection); - view.showPage (documentPage, pageScroll); + pageLocation = PageLocationLeft; } - else + + int pageMin = (pageLocation == PageLocationRight) ? 1 : 0; + int pageMax = (pageLocation == PageLocationLeft) ? 0 : 1; + + for (int pageIndex = pageMin ; pageIndex <= pageMax ; ++pageIndex ) { - if ( !g_WaitingForPage ) + DocumentPage *documentPage = m_Document->getCurrentPage (pageIndex); + if ( NULL != documentPage ) { - documentPage = m_Document->getEmptyPage (); - if ( wasZoomed ) - { - view.resizePage (documentPage->getWidth (), - documentPage->getHeight ()); - } - else + g_WaitingForPage[pageIndex] = FALSE; + if ( NULL != m_LastSelection[pageIndex] ) + documentPage->setSelection(m_LastSelection[pageIndex]); + view.showPage (documentPage, pageScroll, pageIndex); + } + else + { + if ( !g_WaitingForPage[pageIndex] ) { - view.showPage (documentPage, pageScroll); - delete documentPage; - view.showText (_("Loading...")); + documentPage = m_Document->getEmptyPage (); + if ( wasZoomed ) + { + view.resizePage (documentPage->getWidth (), + documentPage->getHeight ()); + } + else + { + view.showPage (documentPage, pageScroll, pageIndex); + delete documentPage; + view.showText (_("Loading..."), pageIndex); + } + + g_WaitingForPage[pageIndex] = TRUE; + PageNotAvailableData *data = new PageNotAvailableData; + data->pter = this; + data->scroll = pageScroll; + data->location = (pageIndex == 0) ? + PageLocationLeft : PageLocationRight; + g_idle_add (PagePter::pageNotAvailable, data); } - - g_WaitingForPage = TRUE; - PageNotAvailableData *data = new PageNotAvailableData; - data->pter = this; - data->scroll = pageScroll; - g_idle_add (PagePter::pageNotAvailable, data); } } } @@ -452,3 +562,16 @@ m_Document->zoomToWidth (width); } } + +/// +/// @brief Sets the view mode: single/dual page. +/// +/// @param active TRUE if Dual Pages mode should be activated, +/// FALSE otherwise. +/// +void +PagePter::setDualPages (gboolean activate) +{ + m_PageView->showSecondPage (activate); + refreshPage (PAGE_SCROLL_NONE, FALSE); +} diff -ur a/src/PagePter.h b/src/PagePter.h --- a/src/PagePter.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/PagePter.h 2009-10-30 01:19:38.000000000 +0300 @@ -28,6 +28,12 @@ PagePterModeSelectText, }; + enum PageLocation{ + PageLocationLeft, + PageLocationRight, + PageLocationBoth, + }; + /// /// @class PagePter. /// @brief The page presenter. @@ -58,7 +64,8 @@ void setNextPageScroll (PageScroll next); void setView (IMainView &view); void viewResized (gint width, gint height); - void setMode(PagePterMode mode); + void setMode (PagePterMode mode); + void setDualPages (gboolean activate); protected: /// The document whose page is shown. @@ -69,12 +76,13 @@ PageScroll m_NextPageScroll; /// The page view. IPageView *m_PageView; - /// Last text selection - GdkRegion *m_LastSelection; + /// Last text selections + GdkRegion **m_LastSelection; /// What page presenter must do when user move mouse with button pressed. PagePterMode m_ScrollMode; - void refreshPage (PageScroll pageScroll, gboolean wasZoomed); + void refreshPage (PageScroll pageScroll, gboolean wasZoomed, + PageLocation pageLocation = PageLocationBoth); }; } diff -ur a/src/PDFDocument.cxx b/src/PDFDocument.cxx --- a/src/PDFDocument.cxx 2009-10-29 23:45:04.000000000 +0300 +++ b/src/PDFDocument.cxx 2009-10-30 01:19:38.000000000 +0300 @@ -669,13 +669,31 @@ } void -PDFDocument::setTextSelection (DocumentRectangle *rect) +PDFDocument::setTextSelection (gchar *text) +{ + g_assert(text); + + for ( GList *obs = g_list_first (m_Observers) ; + NULL != obs ; + obs = g_list_next (obs) ) + { + IDocumentObserver *observer = (IDocumentObserver*)obs->data; + observer->notifyTextSelected(text); + } + + g_free(text); +} + + +gchar * +PDFDocument::getTextSelection (DocumentRectangle *rect, guint index) { g_assert(rect); - PopplerPage *page = poppler_document_get_page (m_Document, getCurrentPageNum()-1); + PopplerPage *page = poppler_document_get_page (m_Document, + getCurrentPageNum() + index - 1); if(!page) - return; + return NULL; gdouble pageWidth, pageHeight; poppler_page_get_size(page, &pageWidth, &pageHeight); @@ -693,29 +711,19 @@ #else // !HAVE_POPPLER_0_6_0 gchar *text = poppler_page_get_text(page, &textRect); #endif // HAVE_POPPLER_0_6_0 - if(!text) - goto cleanup; - - for ( GList *obs = g_list_first (m_Observers) ; - NULL != obs ; - obs = g_list_next (obs) ) - { - IDocumentObserver *observer = (IDocumentObserver*)obs->data; - observer->notifyTextSelected(text); - } - cleanup: if(page) g_object_unref(page); - if(text) - g_free(text); + + return text; } GdkRegion* -PDFDocument::getTextRegion (DocumentRectangle *r) +PDFDocument::getTextRegion (DocumentRectangle *r, guint index) { GdkRegion *res = NULL; - PopplerPage *page = poppler_document_get_page (m_Document, getCurrentPageNum()-1); + PopplerPage *page = poppler_document_get_page (m_Document, + getCurrentPageNum() + index - 1); if(!page) return NULL; diff -ur a/src/PDFDocument.h b/src/PDFDocument.h --- a/src/PDFDocument.h 2009-10-29 23:45:04.000000000 +0300 +++ b/src/PDFDocument.h 2009-10-30 01:19:38.000000000 +0300 @@ -54,8 +54,9 @@ DocumentPage *renderPage (gint pageNum); gboolean saveFile (const gchar *fileName, GError **error); - GdkRegion* getTextRegion (DocumentRectangle* rect); - void setTextSelection (DocumentRectangle *rect); + GdkRegion* getTextRegion (DocumentRectangle* rect, guint index); + void setTextSelection (gchar *text); + gchar *getTextSelection (DocumentRectangle *rect, guint index); protected: /// The PDF document.