diff --git a/CHANGELOG b/CHANGELOG index 68b2ff0186..689bce4199 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +* New UI layout +* Add DnD on tree view +* Merge tree and list views * Remove generic default value on right-click * Made numpad locale aware * Selection widget used for many2one dynamically change their content following diff --git a/doc/usage.rst b/doc/usage.rst index 877f5e8879..e25c1597a7 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -80,35 +80,35 @@ all important visual parts. Figure: Tryton client application:: - Client Window _______________________________________________________ - | Tryton _ o x| - |-------------------------------------------------------| - Menu bar | File User Form Options Plugins Shortcuts Help | - |_______________________________________________________| - | | - Tool bar | New Save | Delete | Find Previous Next Switch v | - |-------------------------------------------------------| - | ______ | - Tab bar | [Menu] |[Tab1]| [Tab2] ... | - |---------| | -------------------------------------| - | .------- ------------------------------------. | - | | | | - | | | | - | | | | - | | | | - | | | | - View | | | | - | | | | - | | | | - | | | | - | | | | - | | | | - | | | | - | | | | - | |___________________________________________________| | - |_______________________________________________________| - Status bar |username company Waiting requests... login@...| - |_______________________________________________________| + Client Window ________________________________________________________________ + | Tryton _ o x| + |----------------------------------------------------------------| + Menu bar | File User Options Plugins Shortcuts Help | + |________________________________________________________________| + | | ______ | + Tabs | Menu | [Tab1] |[Tab2]| [Tab3]... | + |-------------| +-------+ +--------------------------------+| + | + | | Tab2 || + | |-+ | |-----------------------------------------------|| + Tool bar | | |- | | New Save|Delete|Find Previous Next Switch v || + | | |- | |-----------------------------------------------|| + | + | | || + | |-+ | | || + | | |- | | || + | | |- | | || + | + | | || + View | |-+ | | || + | |- | | || + | |- | | || + | | | || + | | | || + | | | || + | | | || + | | | || + |_____________| |_______________________________________________|| + |________________________________________________________________| + Status bar |username company Waiting requests... login@...| + |________________________________________________________________| Menu Bar @@ -149,32 +149,6 @@ contextual menu for some views and fields. In this context menu the user finds :term:`actions` to copy and paste contents or selections for several fields. -Tool Bar -^^^^^^^^ -The tool bar contains some often used menu functions mapped to icons. -In addition to the menu bar the tool bar contains a button called -*Attachment*. - -Attachment -++++++++++ -The attachment item handles the document management system of -Tryton which is able to attach files to any arbitrary :term:`model`. On click -it opens the attachments :term:`dialog`. The default dialog shows a list view -of the attached files and links. - -A single Attachment has the following Options: - -* Attachment Name: The name of the attachment. -* Datas: The attached File. The file size is displayed as text. -* Link: Link to an external site as URL. -* Preview tab: Shows a preview picture of the selected attachment. -* Description tab: Shows and and provides editing of a free text description - for the selected attachment. -* Actions: - - - OK: Closes the Dialog and saves the attachment(s). - - Tabbed Main Frame ^^^^^^^^^^^^^^^^^ This part of the client contains all the related contents and @@ -195,74 +169,22 @@ __ TDI_ .. Note:: Inside :term:`views` there can be tabs, too. -Main Menu -+++++++++ -The first left tab contains the *main menu* (... not to mix up with -the menu bar!). The *main menu* does not contain fixed menu items. + +Menu +++++ +The *menu* does not contain fixed menu items. All of them are dynamically provided by the actual set of the installed :term:`modules` depending on the access rules of the current user. If a menu -item is clicked, the appropriate action will open in a new tab. As the figure -below illustrates, the *main menu* is split up in three frames. - -The upper left frame contains the first or top level entries of the *main -menu*. Standard top level entries are 'Administration' and 'Tryton'. - -.. note:: The *administration* menu will be explained in the modules - documentation for the 'IR' module, the *information repository*. - -The top level entry 'Tryton' connects to the website of the Tryton project. - -The right frame is showing a :term:`tree view` substructure of menu *items* -and *headings*. With the arrow keys it is possible to navigate inside the menu. -By pressing *Enter* or double-clicking onto the menu item the appropriate -:term:`views` open. Use left and right arrow to expand or contract sub -items of a heading. - -Figure: Main Menu Tab:: - - ________________________________________________________ - | Tryton _ o x| - |--------------------------------------------------------| - | File User Form Options Plugins Shortcuts Help | - |________________________________________________________| - | | - | New Save | Delete | Find Previous Next Switch v | - |________________________________________________________| - | ______ | - | |[Menu]| [Tab1] [Tab2] ... | - |--| | ---------------------------------------------| - | | -------------.------------------------------. | - | | Top Level Entry 1 | Menu | | | - | | Top Level Entry 2 |---------------------+--------| | - | | ... | Item 1 | | | - | | Administration | Item 2 | | | - | | Tryton | > Heading | | | - | |____________________| Sub Item 1 | | | - | | Shortcuts [+][-]| Sub Item 2 | | | - | |--------------------| > Sub Heading | | | - | | Sub Item 2 | Sub Sub ... | | | - | | | | | | - | | | | | | - | |____________________|_____________________|________| | - |________________________________________________________| - |username company Waiting requests... login@... | - |________________________________________________________| +item is clicked, the appropriate action will open in a new tab. -The lower left menu frame shows a user adjustable *shortcuts* menu. This menu -is for collecting often used menu items. Using a *shortcut* item will open -the appropriate view in a new tab, just with a double mouse click. -A menu item is added to the *shortcut* menu by pushing the plus button [+] -in the *shortcut* menu. The minus button [-] removes a selected *shortcut* -item from the list. - -Home Action -+++++++++++ -Another tab opens during the startup of the Tryton client: the home action. -It is usually an item of the `Main Menu`_ opening when the user calls his +Home +++++ +A tab opens during the startup of the Tryton client: the home. It is +usually an item of the `Menu`_ opening when the user calls his `Home`__ action defined in the `preferences`__. -__ Menu-Form-Home_ +__ Menu-User-Home_ __ Menu-User-Preferences_ @@ -489,21 +411,34 @@ Preferences... * Email: Email address of the Tryton user. * Signature: Signature block for the Tryton user. * Menu Action: Defines the action which is called as the - `main menu`_. - * Home Action: Defines the action which is called as `home action`__. + `Menu`_. + * Home Action: Defines the action which is called as `Home`__. * Language: Language of the client interface. * Timezone: The local timezone where the user/client resides. * Groups: Displays the users membership to access groups. -__ Menu-Form-Home_ +.. _Menu-User-Menu-Reload: -.. _Menu-user-send-a-request: +Menu Reload: + Reload the menu. + +.. _Menu-User-Menu-Toggle: + +Menu Toggle: + Toggle the menu visibility + +.. _Menu-User-Home: + +Home: + Opens a new `Home`__ tab. + +.. _Menu-User-send-a-request: Send a Request Opens a tab in :term:`form view` which enables the user to send requests to other users of the same database. -.. _Menu-user-read-my-request: +.. _Menu-User-read-my-request: Read my Requests Opens a tab in :term:`tree view` showing all requests related to the @@ -556,206 +491,6 @@ Read my Requests Tryton, which is very similar to email. -Form -^^^^ -The form menu contains functions for the *actual form* in the tab which -is open. Some operations are working with one record or with a selection of -:term:`records`. In :term:`form view` the actual record is selected for -operations. In :term:`tree view` all selected records are used for operations. - -.. _Menu-Form-New: - -New: - Creates a new record. - -.. _Menu-Form-Save: - -Save: - Saves the actual record. - -.. _Menu-Form-Duplicate: - -Duplicate: - Duplicates the content of the actual record in a newly created record. - -.. _Menu-Form-Delete: - -Delete: - Deletes the selected or actual record. - -.. _Menu-Form-Find: - -.. _search_widget: - -Find...: - Opens a :term:`dialog` for finding :term:`fields` with search criteria and - operators. - - * Search criteria: Defines the aspects to seek for. - * General search operators: - - - Equals: Search for results which are exactly the same as the following - term. - - Does Not Equal: Search for results which are different from the following - term. - - * Additional search operators on numbers, amounts and strings: - - - Contains: Search for results which contain the following term. - - Does Not Contain: Search for results which do not include the - following term. - - Starts With: Search for results beginning with the following term. - - Ends With: Search for results ending with the following term. - - * Additional search operators for numbers and amounts: - - - Is Between: Search for results inside a range (from - to). - - Is Not Between: Search for results outside a range (from - to). - - Is Different: Same as 'Does Not Equal', see above. - - * Advanced Search expander opens additional search criteria. - - - Limit: Limits the count of results. - - Offset: Skips a number of results and show only the following. - - * Actions: - - - Find: Search for results of the given criteria. - - New: Create a new record (used when search was fruitless, to create - quickly a new record). - - Ok: Open the selected results. - - Cancel - -.. note:: To search for deactivated records the *Active* search criteria must be - set to *No*. - -.. _Menu-Form-Next: - -Next: - Goes to the next record in a list (sequence). - -.. _Menu-Form-Previous: - -Previous: - Goes to the last record in a list (sequence). - -.. _Menu-Form-Switch_View: - -Switch View: - Switches the actual view aspect to: - - * :term:`Form view` - * :term:`Tree view` - * :term:`Graph view` - - Not all views provide all aspects. - -.. _Menu-Form-Menu: - -Menu: - Activates or reopens the menu tab. - -.. _Menu-Form-Home: - -Home: - Opens a new `home`__ tab. - -__ Menu-User-Preferences_ - -.. _Menu-Form-Close: - -Close Tab: - Closes the current tab. A Request :term:`Dialog` opens in case of unsaved - changes. - -.. _Menu-Form-Previous_Tab: - -Previous Tab: - Shows the previous (left) tab of the actual tab. - -.. _Menu-Form-Next_Tab: - -Next Tab: - Shows the next (right) tab of the actual tab. - -.. _Menu-Form-View_Logs: - -View Logs...: - Shows generic information of the current record. - -.. _Menu-Form-Go_to_Record_ID: - -Go to Record ID...: - Opens specific record id in the current view. - -.. _Menu-Form-Reload_Undo: - -Reload/Undo: - Reloads the content of the actual tab. Undoes changes, if save request for - the current record is denied. - -.. _Menu-Form-Actions: - -Actions...: - Shows all actions for the actual view, model and record. - -.. _Menu-Form-Print: - -Print...: - Shows all print actions for the actual view, model and record. - -.. _Menu-Form-Export_Data: - -Export Data...: - Export of current/selected records into :term:`CSV`-file or open it in Excel. - - * Predefined exports - - - Choose preferences of already saved exports. - - * All Fields: Fields available from the model. - * Fields to export: Defines the specific fields to export. - * Options: - - - Save: Save export as a CSV file. - - Open: Open export in spread sheet application. - - * Add field names: Add a header row with field names to the export data. - * Actions: - - - Add: Adds selected fields to *Fields to export*. - - Remove: Removes selected fields from *Fields to export*. - - Clear: Removes all fields from *Fields to export*. - - Save Export: Saves field mapping to a *Predefined export* with a name. - - Delete Export: Deletes a selected *Predefined export*. - - Ok: Exports the data (action depending on *Options*). - - Cancel - -.. _Menu-Form-Import_Data: - -Import Data...: - Import records from :term:`CSV`-file. - - * All Fields: Fields available in the model (required fields are marked up). - * Fields to Import: Exact sequence of all columns in the CSV file. - * File to Import: File :term:`dialog` for choosing a CSV file to import. - * CSV Parameters: Setup specific parameters for chosen CSV file. - - - Field Separator: Character which separates CSV fields. - - Text Delimiter: Character which encloses text in CSV. - - Encoding: :term:`Character encoding` of CSV file. - - Lines to Skip: Count of lines to skip a headline or another offset. - - * Actions: - - - Add: Adds fields to *Fields to Import*. - - Remove: Deletes fields from *Fields to Import*. - - Clear: Removes all fields from *Fields to Import*. - - Auto-Detect: Tries to auto detect fields in the CSV *File to Import*. - - Ok: Proceeds the data import. - - Cancel - - Options ^^^^^^^ The Options menu sets up several visual and context depending preferences. @@ -938,6 +673,202 @@ Keyboard Shortcuts...: About...: License, Contributors, Authors of Tryton +Tool Bar +******** +The tool bar contains the functionalities linked to the current tab. +The tool bar contains functions for the current tab. +Some operations are working with one record or with a selection of +:term:`records`. In :term:`form view` the actual record is selected for +operations. In :term:`tree view` all selected records are used for operations. + +.. _Toolbar-New: + +New: + Creates a new record. + +.. _Toolbar-Save: + +Save: + Saves the actual record. + +.. _Toolbar-Duplicate: + +Duplicate: + Duplicates the content of the actual record in a newly created record. + +.. _Toolbar-Delete: + +Delete: + Deletes the selected or actual record. + +.. _Toolbar-Find: + +.. _search_widget: + +Find...: + Opens a :term:`dialog` for finding :term:`fields` with search criteria and + operators. + + * Search criteria: Defines the aspects to seek for. + * General search operators: + + - Equals: Search for results which are exactly the same as the following + term. + - Does Not Equal: Search for results which are different from the following + term. + + * Additional search operators on numbers, amounts and strings: + + - Contains: Search for results which contain the following term. + - Does Not Contain: Search for results which do not include the + following term. + - Starts With: Search for results beginning with the following term. + - Ends With: Search for results ending with the following term. + + * Additional search operators for numbers and amounts: + + - Is Between: Search for results inside a range (from - to). + - Is Not Between: Search for results outside a range (from - to). + - Is Different: Same as 'Does Not Equal', see above. + + * Advanced Search expander opens additional search criteria. + + - Limit: Limits the count of results. + - Offset: Skips a number of results and show only the following. + + * Actions: + + - Find: Search for results of the given criteria. + - New: Create a new record (used when search was fruitless, to create + quickly a new record). + - Ok: Open the selected results. + - Cancel + +.. note:: To search for deactivated records the *Active* search criteria must be + set to *No*. + +.. _Toolbar-Next: + +Next: + Goes to the next record in a list (sequence). + +.. _Toolbar-Previous: + +Previous: + Goes to the last record in a list (sequence). + +.. _Toolbar-Switch_View: + +Switch View: + Switches the actual view aspect to: + + * :term:`Form view` + * :term:`Tree view` + * :term:`Graph view` + + Not all views provide all aspects. + +.. _Toolbar-Close: + +Close Tab: + Closes the current tab. A Request :term:`Dialog` opens in case of unsaved + changes. + +.. _Toolbar-Previous_Tab: + +Previous Tab: + Shows the previous (left) tab of the actual tab. + +.. _Toolbar-Next_Tab: + +Next Tab: + Shows the next (right) tab of the actual tab. + +.. _Toolbar-View_Logs: + +View Logs...: + Shows generic information of the current record. + +.. _Toolbar-Go_to_Record_ID: + +Go to Record ID...: + Opens specific record id in the current view. + +.. _Toolbar-Reload_Undo: + +Reload/Undo: + Reloads the content of the actual tab. Undoes changes, if save request for + the current record is denied. + +.. _Toolbar-Actions: + +Actions...: + Shows all actions for the actual view, model and record. + +.. _Toolbar-Print: + +Print...: + Shows all print actions for the actual view, model and record. + +.. _Toolbar-Export_Data: + +Export Data...: + Export of current/selected records into :term:`CSV`-file or open it in Excel. + + * Predefined exports + + - Choose preferences of already saved exports. + + * All Fields: Fields available from the model. + * Fields to export: Defines the specific fields to export. + * Options: + + - Save: Save export as a CSV file. + - Open: Open export in spread sheet application. + + * Add field names: Add a header row with field names to the export data. + * Actions: + + - Add: Adds selected fields to *Fields to export*. + - Remove: Removes selected fields from *Fields to export*. + - Clear: Removes all fields from *Fields to export*. + - Save Export: Saves field mapping to a *Predefined export* with a name. + - Delete Export: Deletes a selected *Predefined export*. + - Ok: Exports the data (action depending on *Options*). + - Cancel + +.. _Toolbar-Import_Data: + +Import Data...: + Import records from :term:`CSV`-file. + + * All Fields: Fields available in the model (required fields are marked up). + * Fields to Import: Exact sequence of all columns in the CSV file. + * File to Import: File :term:`dialog` for choosing a CSV file to import. + * CSV Parameters: Setup specific parameters for chosen CSV file. + + - Field Separator: Character which separates CSV fields. + - Text Delimiter: Character which encloses text in CSV. + - Encoding: :term:`Character encoding` of CSV file. + - Lines to Skip: Count of lines to skip a headline or another offset. + + * Actions: + + - Add: Adds fields to *Fields to Import*. + - Remove: Deletes fields from *Fields to Import*. + - Clear: Removes all fields from *Fields to Import*. + - Auto-Detect: Tries to auto detect fields in the CSV *File to Import*. + - Ok: Proceeds the data import. + - Cancel + +.. _Toolbar-Attachment: + +Attachment: + The attachment item handles the document management system of + Tryton which is able to attach files to any arbitrary :term:`model`. + On click it opens the attachments :term:`dialog`. The default dialog + shows a list view of the attached files and links. + Appendix ******** diff --git a/tryton/action/main.py b/tryton/action/main.py index be69b039e3..84b9262cfc 100644 --- a/tryton/action/main.py +++ b/tryton/action/main.py @@ -142,12 +142,12 @@ class Action(object): res_model = action.get('res_model', data.get('res_model')) res_id = action.get('res_id', data.get('res_id')) - Window.create(view_ids, res_model, res_id, domain, - action['view_type'], window, action_ctx, view_mode, - name=name, limit=action.get('limit'), - auto_refresh=action.get('auto_refresh'), - search_value=search_value, - icon=(action.get('icon.rec_name') or '')) + Window.create(view_ids, res_model, res_id, domain, window, + action_ctx, view_mode, name=name, + limit=action.get('limit'), + auto_refresh=action.get('auto_refresh'), + search_value=search_value, + icon=(action.get('icon.rec_name') or '')) elif action['type'] == 'ir.action.wizard': if action.get('window', False): Window.create_wizard(action['wiz_name'], data, window, @@ -160,6 +160,9 @@ class Action(object): direct_print=action.get('direct_print', False), email_print=action.get('email_print', False), email=action.get('email'), context=context) + if action['wiz_name'] == 'ir.ui.view_sc.add': + from tryton.gui.main import Main + Main.get_main().shortcut_set() elif action['type'] == 'ir.action.report': Action.exec_report(action['report_name'], data, window, diff --git a/tryton/config.py b/tryton/config.py index d41373afed..c341762d7b 100644 --- a/tryton/config.py +++ b/tryton/config.py @@ -86,6 +86,7 @@ class ConfigManager(object): 'client.limit': 1000, 'roundup.url': 'http://bugs.tryton.org/roundup/', 'roundup.xmlrpc': 'roundup-xmlrpc.tryton.org', + 'menu.pane': 200, } self.config = {} self.options = { diff --git a/tryton/gui/main.py b/tryton/gui/main.py index 5999c8a83d..863b746c67 100644 --- a/tryton/gui/main.py +++ b/tryton/gui/main.py @@ -87,6 +87,13 @@ class Main(object): gtk.gdk.CONTROL_MASK) gtk.accel_map_add_entry('/File/Quit', gtk.keysyms.Q, gtk.gdk.CONTROL_MASK) + gtk.accel_map_add_entry('/User/Menu Reload', gtk.keysyms.T, + gtk.gdk.MOD1_MASK) + gtk.accel_map_add_entry('/User/Menu Toggle', gtk.keysyms.T, + gtk.gdk.CONTROL_MASK) + gtk.accel_map_add_entry('/User/Home', gtk.keysyms.H, + gtk.gdk.CONTROL_MASK) + gtk.accel_map_add_entry('/Form/New', gtk.keysyms.N, gtk.gdk.CONTROL_MASK) gtk.accel_map_add_entry('/Form/Save', gtk.keysyms.S, @@ -103,10 +110,6 @@ class Main(object): 0) gtk.accel_map_add_entry('/Form/Switch View', gtk.keysyms.L, gtk.gdk.CONTROL_MASK) - gtk.accel_map_add_entry('/Form/Menu', gtk.keysyms.T, - gtk.gdk.CONTROL_MASK) - gtk.accel_map_add_entry('/Form/Home', gtk.keysyms.H, - gtk.gdk.CONTROL_MASK) gtk.accel_map_add_entry('/Form/Close', gtk.keysyms.W, gtk.gdk.CONTROL_MASK) gtk.accel_map_add_entry('/Form/Previous Tab', gtk.keysyms.Page_Up, @@ -131,6 +134,7 @@ class Main(object): self.toolbar = toolbar toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) toolbar.set_style(gtk.TOOLBAR_BOTH) + self.toolbar_menu = None self.vbox = gtk.VBox() self.window.add(self.vbox) @@ -138,7 +142,6 @@ class Main(object): self.status_hbox = None self.menubar = None self.menuitem_user = None - self.menuitem_form = None self.menuitem_plugins = None self.set_statusbar() @@ -147,18 +150,20 @@ class Main(object): if self.macapp is not None: self.macapp.ready() - - self.vbox.pack_start(toolbar, False, True) - self.buttons = {} self._set_toolbar() self.set_toolbar_label() + self.pane = gtk.HPaned() + self.menu_screen = None + self.vbox.pack_start(self.pane, True, True) + self.notebook = gtk.Notebook() self.notebook.popup_enable() self.notebook.set_scrollable(True) self.notebook.connect_after('switch-page', self._sig_page_changt) - self.vbox.pack_start(self.notebook, True, True) + + self.pane.add2(self.notebook) self.window.show_all() @@ -225,20 +230,6 @@ class Main(object): menu_user.set_accel_group(self.accel_group) menu_user.set_accel_path('/User') - menuitem_form = gtk.MenuItem(_('For_m')) - if self.menuitem_form: - menuitem_form.set_sensitive( - self.menuitem_form.get_property('sensitive')) - else: - menuitem_form.set_sensitive(False) - self.menuitem_form = menuitem_form - menubar.add(menuitem_form) - - menu_form = self._set_menu_form() - menuitem_form.set_submenu(menu_form) - menu_form.set_accel_group(self.accel_group) - menu_form.set_accel_path('/Form') - menuitem_options = gtk.MenuItem(_('_Options')) menubar.add(menuitem_options) @@ -281,7 +272,6 @@ class Main(object): self.macapp.insert_app_menu_item(self.aboutitem,0) menuitem_file.show_all() menuitem_user.show_all() - menuitem_form.show_all() menuitem_options.show_all() menuitem_plugins.show_all() menuitem_shortcut.show_all() @@ -407,6 +397,31 @@ class Main(object): menu_user.add(gtk.SeparatorMenuItem()) + imagemenuitem_menu = gtk.ImageMenuItem(_('_Menu Reload'), self.accel_group) + image = gtk.Image() + image.set_from_stock('tryton-start-here', gtk.ICON_SIZE_MENU) + imagemenuitem_menu.set_image(image) + imagemenuitem_menu.connect('activate', self.sig_win_menu) + imagemenuitem_menu.set_accel_path('/User/Menu Reload') + menu_user.add(imagemenuitem_menu) + + imagemenuitem_menu_toggle = gtk.ImageMenuItem(_('_Menu Toggle'), + self.accel_group) + imagemenuitem_menu_toggle.connect('activate', lambda *a: self.menu_toggle()) + imagemenuitem_menu_toggle.set_accel_path('/User/Menu Toggle') + menu_user.add(imagemenuitem_menu_toggle) + + + imagemenuitem_home = gtk.ImageMenuItem(_('_Home'), self.accel_group) + image = gtk.Image() + image.set_from_stock('tryton-go-home', gtk.ICON_SIZE_MENU) + imagemenuitem_home.set_image(image) + imagemenuitem_home.connect('activate', self.sig_home_new) + imagemenuitem_home.set_accel_path('/User/Home') + menu_user.add(imagemenuitem_home) + + menu_user.add(gtk.SeparatorMenuItem()) + imagemenuitem_send_request = gtk.ImageMenuItem(_('_Send a Request')) image = gtk.Image() image.set_from_stock('tryton-mail-message-new', gtk.ICON_SIZE_MENU) @@ -493,24 +508,8 @@ class Main(object): imagemenuitem_switch.set_accel_path('/Form/Switch View') menu_form.add(imagemenuitem_switch) - imagemenuitem_menu = gtk.ImageMenuItem(_('_Menu'), self.accel_group) - image = gtk.Image() - image.set_from_stock('tryton-start-here', gtk.ICON_SIZE_MENU) - imagemenuitem_menu.set_image(image) - imagemenuitem_menu.connect('activate', self.sig_win_menu) - imagemenuitem_menu.set_accel_path('/Form/Menu') - menu_form.add(imagemenuitem_menu) - menu_form.add(gtk.SeparatorMenuItem()) - imagemenuitem_home = gtk.ImageMenuItem(_('_Home'), self.accel_group) - image = gtk.Image() - image.set_from_stock('tryton-go-home', gtk.ICON_SIZE_MENU) - imagemenuitem_home.set_image(image) - imagemenuitem_home.connect('activate', self.sig_home_new) - imagemenuitem_home.set_accel_path('/Form/Home') - menu_form.add(imagemenuitem_home) - imagemenuitem_close = gtk.ImageMenuItem(_('_Close Tab'), self.accel_group) image = gtk.Image() image.set_from_stock('tryton-close', gtk.ICON_SIZE_MENU) @@ -589,6 +588,10 @@ class Main(object): 'but_import') menuitem_import.set_accel_path('/Form/Import Data') menu_form.add(menuitem_import) + + menu_form.set_accel_group(self.accel_group) + menu_form.set_accel_path('/Form') + menu_form.show_all() return menu_form def _set_menu_options(self): @@ -891,12 +894,6 @@ class Main(object): toolbutton_reload.connect('clicked', self._sig_child_call, 'but_reload') self.buttons['but_reload'] = toolbutton_reload - toolbutton_menu = gtk.ToolButton('tryton-start-here') - self.toolbutton_menu = toolbutton_menu - self.toolbar.insert(toolbutton_menu, -1) - toolbutton_menu.connect('clicked', self.sig_win_menu) - self.buttons['but_menu'] = toolbutton_menu - self.toolbar.insert(gtk.SeparatorToolItem(), -1) toolbutton_action = gtk.ToolButton('tryton-executable') @@ -916,13 +913,18 @@ class Main(object): toolbutton_attach.connect('clicked', self._sig_child_call, 'but_attach') self.buttons['but_attach'] = toolbutton_attach - self.toolbar.insert(gtk.SeparatorToolItem(), -1) + self.menutoolbutton = None + if hasattr(gtk, 'MenuToolButton'): + self.menutoolbutton = gtk.MenuToolButton(None, None) + # Remove the button to keep only the toggle + hbox, = self.menutoolbutton.get_children() + button, toggle = hbox.get_children() + hbox.remove(button) + self.toolbar.insert(self.menutoolbutton, -1) + menu = self._set_menu_form() + self.menutoolbutton.set_menu(menu) + self.toolbar_menu = menu - toolbutton_request = gtk.ToolButton('tryton-mail-message') - self.toolbutton_request = toolbutton_request - self.toolbar.insert(toolbutton_request, -1) - toolbutton_request.connect('clicked', self.sig_request_open) - self.buttons['but_request'] = toolbutton_request def set_toolbar_label(self): labels = { @@ -934,11 +936,9 @@ class Main(object): 'but_next': _('Next'), 'but_switch': _('Switch'), 'but_reload': _('_Reload'), - 'but_menu': _('Menu'), 'but_action': _('Action'), 'but_print': _('Print'), 'but_attach': _('Attachment(0)'), - 'but_request': _('Request'), } tooltips = { 'but_new': _('Create a new record'), @@ -949,15 +949,17 @@ class Main(object): 'but_next': _('Next Record'), 'but_switch': _('Switch view'), 'but_reload': _('Reload'), - 'but_menu': _('Menu'), 'but_action': _('Action'), 'but_print': _('Print'), 'but_attach': _('Add an attachment to the record'), - 'but_request': _('Request'), } for i in self.buttons: self.buttons[i].set_label(labels[i]) self.tooltips.set_tip(self.buttons[i], tooltips[i]) + if self.toolbar_menu and self.menutoolbutton: + self.toolbar_menu.destroy() + menu = self._set_menu_form() + self.menutoolbutton.set_menu(menu) @staticmethod def get_main(): @@ -970,6 +972,34 @@ class Main(object): 'id': action, 'ids': [action], }) + + def _add_shortcut(widget): + ids = self.menu_screen.sel_ids_get() + if not ids: + return + try: + values = rpc.execute('model', self.menu_screen.model_name, + 'read', ids, ['rec_name'], rpc.CONTEXT) + except Exception, exception: + common.process_exception(exception, self.window) + return + try: + for value in values: + rpc.execute('model', 'ir.ui.view_sc', 'create', { + 'name': value['rec_name'], + 'res_id': value['id'], + 'user_id': rpc._USER, + 'resource': self.menu_screen.model_name, + }, rpc.CONTEXT) + except Exception, exception: + common.process_exception(exception, self.window) + self.shortcut_set() + + def _manage_shortcut(widget): + Window.create(False, 'ir.ui.view_sc', False, + domain=[('user_id', '=', rpc._USER)], + window=self.window, mode=['tree', 'form']) + if shortcuts is None: user = rpc._USER try: @@ -982,6 +1012,13 @@ class Main(object): menuitem = gtk.MenuItem(shortcut['name']) menuitem.connect('activate', _action_shortcut, shortcut['res_id']) menu.add(menuitem) + menu.add(gtk.MenuItem()) + add_shortcut = gtk.MenuItem(_('Add Shortcut')) + add_shortcut.connect('activate', _add_shortcut) + menu.add(add_shortcut) + manage_shortcut = gtk.MenuItem(_('Manage Shortcut')) + manage_shortcut.connect('activate', _manage_shortcut) + menu.add(manage_shortcut) menu.show_all() self.menuitem_shortcut.set_submenu(menu) self.menuitem_shortcut.set_sensitive(True) @@ -1082,22 +1119,22 @@ class Main(object): self.shortcut_set() self.set_statusbar() self.request_set() - self.sig_reload_menu() + self.sig_win_menu() CONFIG['client.lang'] = prefs['language'] CONFIG.save() self.window.present() return True def sig_win_close(self, widget): - self._sig_child_call(widget, 'but_close') + self._sig_remove_book(widget, + self.notebook.get_nth_page(self.notebook.get_current_page())) def sig_request_new(self, widget): ctx = {} ctx.update(rpc.CONTEXT) ctx['active_test'] = False - return Window.create(None, 'res.request', False, [ - ], 'form', mode=['form', 'tree'], window=self.window, - context=ctx) + return Window.create(None, 'res.request', False, [ ], + mode=['form', 'tree'], window=self.window, context=ctx) def sig_request_open(self, widget): ctx = {} @@ -1111,9 +1148,8 @@ class Main(object): else: raise ids = ids1 + ids2 - return Window.create(False, 'res.request', ids, [ - ], 'form', mode=['tree', 'form'], window=self.window, - context=ctx) + return Window.create(False, 'res.request', ids, [ ], + mode=['tree', 'form'], window=self.window, context=ctx) def request_set(self, exception=False): try: @@ -1129,13 +1165,6 @@ class Main(object): ids, ids2 = rpc.execute('model', 'res.request', 'request_get', rpc.CONTEXT) label = _('Requests (%s/%s)') % (len(ids), len(ids2)) - self.buttons['but_request'].set_label(label) - self.tooltips.set_tip(self.buttons['but_request'], label) - if not ids: - self.buttons['but_request'].set_stock_id('tryton-mail-message') - else: - self.buttons['but_request'].set_stock_id( - 'tryton-mail-message-new') message = _('Waiting requests: %s received - %s sent') % (len(ids), len(ids2)) self.sb_requests.set_text(message) @@ -1197,12 +1226,9 @@ class Main(object): return self.sig_login() if not self.menuitem_shortcut.get_property('sensitive'): self.shortcut_set() - self.toolbutton_menu.set_sensitive(True) - self.toolbutton_request.set_sensitive(True) self.menuitem_user.set_sensitive(True) - self.menuitem_form.set_sensitive(True) + #self.menuitem_form.set_sensitive(True) self.menuitem_plugins.set_sensitive(True) - self.notebook.grab_focus() if CONFIG.arguments: url = CONFIG.arguments.pop() self.open_url(url) @@ -1211,7 +1237,7 @@ class Main(object): def sig_logout(self, widget, disconnect=True): res = True while res: - wid = self._wid_get() + wid = self.get_page() if wid: if 'but_close' in wid.handlers: res = wid.handlers['but_close']() @@ -1220,15 +1246,19 @@ class Main(object): res = self._win_del() else: res = False + if self.pane.get_child1(): + self.pane.remove(self.pane.get_child1()) + if self.pane.get_position(): + CONFIG['menu.pane'] = self.pane.get_position() + if self.menu_screen: + self.menu_screen.destroy() + self.menu_screen = None self.sb_username.set_text('') self.sb_servername.set_text('') self.sb_requests.set_text('') self.shortcut_unset() - self.toolbutton_menu.set_sensitive(False) - self.toolbutton_request.set_sensitive(False) - self.toolbutton_request.set_label(_('Request')) self.menuitem_user.set_sensitive(False) - self.menuitem_form.set_sensitive(False) + #self.menuitem_form.set_sensitive(False) self.menuitem_plugins.set_sensitive(False) if disconnect: rpc.logout() @@ -1256,25 +1286,32 @@ class Main(object): def sig_shortcuts(self, widget): Shortcuts(self.window).run() - def sig_reload_menu(self): - res = False - for page in range(len(self.pages)): - if self.pages[page].model == 'ir.ui.menu': - self.pages[page].sig_reload() - hbox = self.notebook.get_tab_label(self.pages[page].widget) - for child in hbox.get_children(): - if isinstance(child, gtk.Label): - child.set_text(_('Menu')) - break - res = True - return res + def menu_toggle(self, nohide=False): + has_focus = True + if (self.menu_screen + and self.menu_screen.current_view.view_type == 'tree'): + try: + has_focus = self.menu_screen.current_view.widget_tree.has_focus() + except AttributeError: + has_focus = (self.menu_screen.current_view.widget_tree.flags() + & gtk.HAS_FOCUS) + if self.pane.get_position() and has_focus: + CONFIG['menu.pane'] = self.pane.get_position() + if not nohide: + self.pane.set_position(0) + self.notebook.grab_focus() + else: + self.pane.set_position(int(CONFIG['menu.pane'])) + if self.menu_screen: + self.menu_screen.current_view.set_cursor() def sig_win_menu(self, widget=None, quiet=True, prefs=None): - for page in range(len(self.pages)): - if self.pages[page].model == 'ir.ui.menu': - page_num = self.notebook.page_num(self.pages[page].widget) - self.notebook.set_current_page(page_num) - return True + if self.pane.get_child1(): + self.pane.remove(self.pane.get_child1()) + if self.pane.get_position(): + CONFIG['menu.pane'] = self.pane.get_position() + self.menu_screen = None + self.menu_toggle(nohide=True) res = self.sig_win_new(widget, menu_type='menu', quiet=quiet, prefs=prefs) return res @@ -1360,6 +1397,16 @@ class Main(object): return False def win_add(self, page): + if page.model == 'ir.ui.menu' and not self.pane.get_child1(): + screen = page.screen + screen.screen_container.alternate_view = True + screen.switch_view(view_type=screen.current_view.view_type) + self.pane.pack1(screen.screen_container.alternate_viewport) + self.menu_screen = screen + screen.display(set_cursor=True) + return + elif page.model == 'ir.ui.menu': + self.sig_win_menu() previous_page_id = self.notebook.get_current_page() previous_widget = self.notebook.get_nth_page(previous_page_id) self.previous_pages[page] = previous_widget @@ -1375,8 +1422,6 @@ class Main(object): else: noise_size = icon_w + 3 name = page.name - if page.model == 'ir.ui.menu': - name = _('Menu') label = gtk.Label(name) self.tooltips.set_tip(label, page.name) self.tooltips.enable() @@ -1418,10 +1463,8 @@ class Main(object): def sb_set(self, view=None): if not view: - view = self._wid_get() + view = self.get_page() for i in self.buttons: - if i in ('but_menu', 'but_request'): - continue if self.buttons[i]: self.buttons[i].set_sensitive( bool(view and (i in view.handlers))) @@ -1484,10 +1527,13 @@ class Main(object): del page self.notebook.set_current_page(next_page_id) + if not self.pages and self.menu_screen: + self.menu_screen.current_view.set_cursor() return self.notebook.get_current_page() != -1 - def _wid_get(self): - page_id = self.notebook.get_current_page() + def get_page(self, page_id=None): + if page_id is None: + page_id = self.notebook.get_current_page() if page_id == -1: return None page_widget = self.notebook.get_nth_page(page_id) @@ -1497,7 +1543,7 @@ class Main(object): return None def _sig_child_call(self, widget, button_name): - wid = self._wid_get() + wid = self.get_page() if wid: res = True if button_name in wid.handlers: @@ -1508,6 +1554,14 @@ class Main(object): def _sig_page_changt(self, notebook, page, page_num): self.last_page = self.current_page self.current_page = self.notebook.get_current_page() + + current_form = self.get_page(self.current_page) + parent = self.toolbar.get_parent() + if parent: + parent.remove(self.toolbar) + current_form.toolbar_box.add(self.toolbar) + current_form.toolbar_box.show_all() + self.sb_set() def sig_db_new(self, widget): @@ -1681,7 +1735,6 @@ class Main(object): return res_id = False mode = None - view_type = params.get('view_type', 'form') try: view_ids = json.loads(params.get('views', 'false')) limit = json.loads(params.get('limit', 'null')) @@ -1700,9 +1753,9 @@ class Main(object): mode = ['form', 'tree'] try: Window.create(view_ids, model, res_id=res_id, domain=domain, - view_type=view_type, window=self.window, - context=context, mode=mode, name=name, limit=limit, - auto_refresh=auto_refresh, search_value=search_value) + window=self.window, context=context, mode=mode, + name=name, limit=limit, auto_refresh=auto_refresh, + search_value=search_value) except Exception: return diff --git a/tryton/gui/window/attachment.py b/tryton/gui/window/attachment.py index 86cdc3566d..1724a2df8a 100644 --- a/tryton/gui/window/attachment.py +++ b/tryton/gui/window/attachment.py @@ -15,7 +15,7 @@ class Attachment(object): def run(self): screen = Screen('ir.attachment', self.parent, domain=[ ('resource', '=', self.resource), - ], view_type=['tree', 'form'], context={ + ], mode=['tree', 'form'], context={ 'resource': self.resource, }, exclude_field='resource') screen.search_filter() diff --git a/tryton/gui/window/board.py b/tryton/gui/window/board.py index 2344b01d5b..73a2593f67 100644 --- a/tryton/gui/window/board.py +++ b/tryton/gui/window/board.py @@ -2,6 +2,7 @@ #this repository contains the full copyright notices and license terms. "Board" import gtk +import pango from tryton.signal_event import SignalEvent import tryton.rpc as rpc from tryton.gui.window.view_board import ViewBoard @@ -11,7 +12,7 @@ import tryton.common as common class Board(SignalEvent): 'Board' - def __init__(self, window, view_id, context=None, name=False, + def __init__(self, model, window, view_id, context=None, name=False, auto_refresh=False): super(Board, self).__init__() @@ -28,10 +29,38 @@ class Board(SignalEvent): self.name = self.board.name else: self.name = name - self.model = '' + self.model = model self.widget = gtk.VBox() + title = gtk.Label() + title.set_use_markup(True) + title.modify_font(pango.FontDescription("14")) + title.set_label('' + self.name + '') + title.set_padding(20, 4) + title.set_alignment(0.0, 0.5) + title.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) + title.show() + + hbox = gtk.HBox() + hbox.pack_start(title, expand=True, fill=True) + hbox.show() + + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) + frame.add(hbox) + frame.show() + + eb = gtk.EventBox() + eb.add(frame) + eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff")) + eb.show() + + self.widget.pack_start(eb, expand=False, fill=True, padding=3) + + self.toolbar_box = gtk.HBox() + self.widget.pack_start(self.toolbar_box, False, True) + viewport = gtk.Viewport() viewport.set_shadow_type(gtk.SHADOW_NONE) viewport.add(self.board.widget_get()) diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py index 0ed7ffee24..233eec985a 100644 --- a/tryton/gui/window/form.py +++ b/tryton/gui/window/form.py @@ -27,13 +27,13 @@ _ = gettext.gettext class Form(SignalEvent): "Form" - def __init__(self, model, window, res_id=False, domain=None, view_type=None, + def __init__(self, model, window, res_id=False, domain=None, mode=None, view_ids=None, context=None, name=False, limit=None, auto_refresh=False, search_value=None): super(Form, self).__init__() - if not view_type: - view_type = ['tree', 'form'] + if not mode: + mode = ['tree', 'form'] if domain is None: domain = [] if view_ids is None: @@ -49,7 +49,7 @@ class Form(SignalEvent): self.widget = gtk.VBox(spacing=3) self.widget.show() - self.screen = Screen(self.model, self.window, view_type=view_type, + self.screen = Screen(self.model, self.window, mode=mode, context=self.context, view_ids=view_ids, domain=domain, limit=limit, readonly=bool(auto_refresh), search_value=search_value) @@ -110,6 +110,9 @@ class Form(SignalEvent): self.widget.pack_start(eb, expand=False, fill=True, padding=3) + self.toolbar_box = gtk.HBox() + self.widget.pack_start(self.toolbar_box, False, True) + viewport = gtk.Viewport() viewport.set_shadow_type(gtk.SHADOW_NONE) viewport.add(self.screen.widget) @@ -198,12 +201,15 @@ class Form(SignalEvent): self.window.present() def destroy(self): + if self.toolbar_box.get_children(): + toolbar = self.toolbar_box.get_children()[0] + self.toolbar_box.remove(toolbar) self.screen.signal_unconnect(self) self.screen.destroy() self.screen = None self.widget = None - self.scrolledwindow.destroy() - self.scrolledwindow = None + #self.scrolledwindow.destroy() + #self.scrolledwindow = None def sel_ids_get(self): return self.screen.sel_ids_get() @@ -361,7 +367,7 @@ class Form(SignalEvent): def sig_reload(self, test_modified=True): if not hasattr(self, 'screen'): return False - if test_modified and self.screen.is_modified(): + if test_modified and self.screen.modified(): res = sur_3b(_('This record has been modified\n' \ 'do you want to save it ?'), self.window) if res == 'ok': @@ -441,7 +447,7 @@ class Form(SignalEvent): def _record_message(self, screen, signal_data): name = '_' if signal_data[0] >= 0: - name = str(signal_data[0] + 1) + name = str(signal_data[0]) msg = name + ' / ' + str(signal_data[1]) if signal_data[1] < signal_data[2]: msg += _(' of ') + str(signal_data[2]) @@ -455,7 +461,7 @@ class Form(SignalEvent): self.signal('attachment-count', signal_data) def modified_save(self, reload=True): - if self.screen.is_modified(): + if self.screen.modified(): value = sur_3b( _('This record has been modified\n' \ 'do you want to save it ?'), diff --git a/tryton/gui/window/preference.py b/tryton/gui/window/preference.py index 5b55f4fb62..7d1dd42be2 100644 --- a/tryton/gui/window/preference.py +++ b/tryton/gui/window/preference.py @@ -37,10 +37,10 @@ class Preference(object): args = ('model', 'res.user', 'get_preferences_fields_view', rpc.CONTEXT) try: - res = rpc.execute(*args) + view = rpc.execute(*args) except Exception, exception: - res = common.process_exception(exception, parent, *args) - if not res: + view = common.process_exception(exception, parent, *args) + if not view: self.win.destroy() self.win = None return @@ -48,11 +48,9 @@ class Preference(object): title = gtk.Label(_('Edit User Preferences')) title.show() self.win.vbox.pack_start(title, expand=False, fill=True) - arch = res['arch'] - fields = res['fields'] - self.screen = Screen('res.user', self.win, view_type=[]) + self.screen = Screen('res.user', self.win, mode=[]) self.screen.new(default=False) - self.screen.add_view(arch, fields) + self.screen.add_view(view) args = ('model', 'res.user', 'get_preferences', False, rpc.CONTEXT) try: diff --git a/tryton/gui/window/tree.py b/tryton/gui/window/tree.py deleted file mode 100644 index ad31e02313..0000000000 --- a/tryton/gui/window/tree.py +++ /dev/null @@ -1,397 +0,0 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -"Tree" -import gtk -import gettext -import tryton.common as common -from tryton.gui.window.view_tree import ViewTree, ViewTreeSC -import tryton.rpc as rpc -from tryton.config import CONFIG -from tryton.gui.window.win_export import WinExport -from window import Window -from tryton.action import Action -from tryton.signal_event import SignalEvent - -_ = gettext.gettext - -class Tree(SignalEvent): - "Tree page" - - def __init__(self, model, window, res_id=False, view_id=False, domain=None, - context=None, name=False): - super(Tree, self).__init__() - if domain is None: - domain = {} - if context is None: - context = {} - ctx = {} - ctx.update(context) - ctx.update(rpc.CONTEXT) - if view_id: - try: - view_base = rpc.execute('model', 'ir.ui.view', 'read', view_id, - ['model', 'type'], ctx) - except Exception, exception: - common.process_exception(exception, window) - raise - try: - view = rpc.execute('model', view_base['model'], - 'fields_view_get', view_id, view_base['type'], ctx) - except Exception, exception: - common.process_exception(exception, window) - raise - else: - try: - view = rpc.execute('model', model, 'fields_view_get', False, - 'tree', ctx) - except Exception, exception: - common.process_exception(exception, window) - raise - - self.widget = gtk.VBox() - - hpaned = gtk.HPaned() - hpaned.set_position(220) - - self.toolbar_vpaned = gtk.VPaned() - self.toolbar_vpaned.set_position(400) - - self.toolbar = gtk.Toolbar() - self.toolbar.set_orientation(gtk.ORIENTATION_VERTICAL) - self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ) - self.toolbar_vpaned.add1(self.toolbar) - - self.toolbar_vpaned.add1(self.toolbar_vpaned) - - scrolledwindow = gtk.ScrolledWindow() - scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_AUTOMATIC) - viewport = gtk.Viewport() - viewport.set_shadow_type(gtk.SHADOW_ETCHED_IN) - - vbox = gtk.VBox() - - toolbar = gtk.Toolbar() - toolbar.set_style(gtk.TOOLBAR_ICONS) - toolitem = gtk.ToolItem() - self.toolitem_label = gtk.Label() - toolitem.add(self.toolitem_label) - toolbar.insert(toolitem, -1) - addbutton = gtk.ToolButton('tryton-list-add') - addbutton.connect('clicked', self.sc_add) - toolbar.insert(addbutton, -1) - removebutton = gtk.ToolButton('tryton-list-remove') - removebutton.connect('clicked', self.sc_del) - toolbar.insert(removebutton, -1) - vbox.pack_start(toolbar, expand=False) - - self.treeview_sc = gtk.TreeView() - self.treeview_sc.set_reorderable(False) - self.treeview_sc.set_headers_visible(False) - vbox.pack_start(self.treeview_sc) - - viewport.add(vbox) - - scrolledwindow.add(viewport) - - self.toolbar_vpaned.add2(scrolledwindow) - - hpaned.add1(self.toolbar_vpaned) - - viewport = gtk.Viewport() - viewport.set_shadow_type(gtk.SHADOW_ETCHED_IN) - - self.scrolledwindow = gtk.ScrolledWindow() - self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_AUTOMATIC) - viewport.add(self.scrolledwindow) - - hpaned.add2(viewport) - - self.widget.pack_start(hpaned) - self.widget.show_all() - - self.model = view['model'] or model - self.domain2 = domain - if view.get('field_childs', False): - self.domain = [] - else: - self.domain = domain - self.view = view - self.window = window - - self.context = context - - self.tree_res = ViewTree(view, [], self.window, True, - context=context) - self.tree_res.view.connect('row-activated', self.sig_activate) - self.tree_res.view.connect('key_press_event', self.sig_key_press) - self.tree_res.view.connect_after('test-expand-row', - self.sig_test_expand_row) - - if not name: - self.name = self.tree_res.name - else: - self.name = name - - if CONFIG['client.modepda']: - self.toolbar_vpaned.hide() - - self.treeview_sc.connect('row-activated', self.sc_go) - self.tree_sc = ViewTreeSC(self.treeview_sc, self.model, self.window) - self.handlers = { - 'but_new': self.sig_new, - 'but_reload': self.sig_reload, - 'but_switch': self.sig_edit, - 'but_action': self.sig_action, - 'but_print': self.sig_print, - 'but_save_as': self.sig_save_as, - 'but_close': self.sig_close, - } - - self.scrolledwindow.add(self.tree_res.widget_get()) - self.sig_reload() - - self.tree_res.view.grab_focus() - if self.tree_res.view.get_model().get_iter_root(): - self.tree_res.view.grab_focus() - selection = self.tree_res.view.get_selection() - selection.select_path((0)) - - def sig_reload(self, widget=None): - self.toolitem_label.set_text(_('Shortcuts')) - try: - ctx = {} - ctx.update(self.context) - ctx.update(rpc.CONTEXT) - args = ('model', self.model, 'search', self.domain2, 0, None, - None, ctx) - ids = rpc.execute(*args) - except Exception, exception: - ids = common.process_exception(exception, self.window, *args) - if not ids: - return - if self.tree_res.toolbar and not CONFIG['client.modepda']: - for child in self.toolbar.get_children(): - self.toolbar.remove(child) - ctx = {} - ctx.update(rpc.CONTEXT) - try: - args = ('model', self.view['model'], 'read', ids, - ['name', 'icon'], ctx) - results = rpc.execute(*args) - except Exception, exception: - results = common.process_exception(exception, self.window, *args) - if not results: - return - results.sort(lambda x, y: cmp(ids.index(x['id']), - ids.index(y['id']))) - radiotb = None - for res in results: - common.ICONFACTORY.register_icon(res['icon']) - radiotb = gtk.RadioToolButton(radiotb, res['icon']) - radiotb.set_label(res['name']) - radiotb.show_all() - radiotb.set_data('id', res['id']) - radiotb.connect('clicked', self.menu_main_clicked) - self.menu_main_clicked(radiotb, focus=False) - self.toolbar.insert(radiotb, -1) - radiotb.child.connect('key_press_event', self.menu_main_key_press) - else: - self.tree_res.ids = ids - self.tree_res.reload() - self.toolbar.hide() - self.toolbar_vpaned.set_position(-1) - self.tree_res.view.grab_focus() - if self.tree_res.view.get_model().get_iter_root(): - self.tree_res.view.grab_focus() - selection = self.tree_res.view.get_selection() - selection.select_path((0)) - self.tree_sc.update() - - def menu_main_clicked(self, widget, focus=True): - if widget.get_active(): - obj_id = widget.get_data('id') - args = ('model', self.model, 'read', obj_id, - [self.view['field_childs']], rpc.CONTEXT) - try: - ids = rpc.execute(*args)[self.view['field_childs']] - except Exception, exception: - res = common.process_exception(exception, self.window, *args) - if not res: - return False - ids = res[self.view['field_childs']] - if not ids: - return False - - self.tree_res.ids = ids - self.tree_res.reload() - - self.sig_action('tree_open', obj_id=obj_id, warning=False) - if focus: - if self.tree_res.view.get_model().get_iter_root(): - self.tree_res.view.grab_focus() - selection = self.tree_res.view.get_selection() - selection.unselect_all() - selection.select_path((0)) - self.tree_res.view.set_cursor((0)) - return False - - def menu_main_key_press(self, widget, event): - if event.keyval == gtk.keysyms.Right: - if self.tree_res.view.get_model().get_iter_root(): - self.tree_res.view.grab_focus() - selection = self.tree_res.view.get_selection() - selection.unselect_all() - selection.select_path((0)) - self.tree_res.view.set_cursor((0)) - return True - - def sig_print(self): - self.sig_action('form_print') - - def sig_action(self, keyword='tree_action', obj_id=None, warning=True): - ids = self.ids_get() - if not obj_id and ids and len(ids): - obj_id = ids[0] - if obj_id: - ctx = self.context.copy() - if 'active_ids' in ctx: - del ctx['active_ids'] - if 'active_id' in ctx: - del ctx['active_id'] - return Action.exec_keyword(keyword, self.window, { - 'model': self.model, - 'id': obj_id, - 'ids':ids, - }, context=ctx, warning=warning) - else: - common.message(_('No record selected!'), self.window) - return False - - def sig_activate(self, widget, iter, path): - if not self.sig_action('tree_open', warning=False): - if self.tree_res.view.row_expanded(iter): - self.tree_res.view.collapse_row(iter) - else: - self.tree_res.view.expand_row(iter, False) - if self.model != 'ir.ui.menu': - self.sig_edit() - - def sig_key_press(self, widget, event): - if event.keyval == gtk.keysyms.Left: - selection = self.tree_res.view.get_selection() - model, paths = selection.get_selected_rows() - if len(paths) == 1: - if not self.tree_res.view.row_expanded(paths[0]): - if len(paths[0]) > 1: - new_path = paths[0][:-1] - selection.select_path(new_path) - self.tree_res.view.collapse_row(new_path) - elif self.tree_res.toolbar: - for child in self.toolbar.get_children(): - if child.get_active(): - child.child.grab_focus() - break - for path in paths: - self.tree_res.view.collapse_row(path) - return True - elif event.keyval == gtk.keysyms.Right: - model, paths = self.tree_res.view.get_selection()\ - .get_selected_rows() - for path in paths: - self.tree_res.view.expand_row(path, False) - return True - - def sig_test_expand_row(self, widget, iter, path): - model = self.tree_res.view.get_model() - iter_children = model.iter_children(iter) - if iter_children and model.get(iter_children, 0)[0] in model.to_reload: - hostname = rpc._SOCK.hostname - port = rpc._SOCK.port - while True: - password = common.ask(_('Password:'), self.window, - visibility=False) - if password is None: - return True - res = rpc.login(rpc._USERNAME, password, hostname, port, - rpc._DATABASE) - if res == -1: - common.message(_('Connection error!\n' \ - 'Unable to connect to the server!'), self.window) - return True - if res < 0: - continue - return False - return False - - def sig_new(self): - Window.create(None, self.model, domain=self.domain, window=self.window, - context=self.context, mode=['form', 'tree']) - - def sig_edit(self): - obj_ids = self.ids_get() - if not obj_ids: - obj_ids = [] - if self.tree_res.toolbar: - for child in self.toolbar.get_children(): - if child.get_active(): - obj_ids.append(child.get_data('id')) - mode = ['form', 'tree'] - if len(obj_ids) > 1: - mode = ['tree', 'form'] - Window.create(None, self.model, obj_ids, self.domain, - window=self.window, context=self.context, - mode=mode) - - def sc_del(self, widget): - obj_id = self.tree_sc.sel_id_get() - if obj_id is not None: - sc_id = int(self.tree_sc.value_get(2)) - try: - rpc.execute('model', 'ir.ui.view_sc', 'delete', sc_id, - rpc.CONTEXT) - except Exception, exception: - common.process_exception(exception, self.window) - self.tree_sc.update() - - def sc_add(self, widget): - ids = self.ids_get() - if len(ids): - try: - res = rpc.execute('model', self.model, 'read', ids, - ['rec_name'], rpc.CONTEXT) - for obj in res: - obj_id = obj['id'] - name = obj['rec_name'] - user = rpc._USER - rpc.execute('model', 'ir.ui.view_sc', 'create', { - 'resource': self.model, - 'user_id': user, - 'res_id': obj_id, - 'name': name, - }, rpc.CONTEXT) - except Exception, exception: - common.process_exception(exception, self.window) - self.tree_sc.update() - - def sc_go(self, widget=None, *args): - obj_id = self.tree_sc.sel_id_get() - if obj_id is not None: - self.sig_action(keyword='tree_open', obj_id=obj_id) - - def ids_get(self): - res = self.tree_res.sel_ids_get() - return res - - def id_get(self): - res = self.tree_res.sel_id_get() - return res - - def sig_save_as(self, widget=None): - win = WinExport(self.model, self.ids_get(), self.window, - context=self.context) - win.run() - - def sig_close(self): - return True diff --git a/tryton/gui/window/view_board/action.py b/tryton/gui/window/view_board/action.py index d41d408252..65c16b3993 100644 --- a/tryton/gui/window/view_board/action.py +++ b/tryton/gui/window/view_board/action.py @@ -8,7 +8,6 @@ import time import datetime from tryton.gui.window.win_search import WinSearch from tryton.action import Action as Action2 -from tryton.gui.window.view_tree.view_tree import ViewTree import tryton.common as common from tryton.pyson import PYSONDecoder import gettext @@ -46,6 +45,7 @@ class Action(SignalEvent): self.action.setdefault('pyson_domain', '[]') self.context = {'active_id': False, 'active_ids': []} + self.context.update(rpc.CONTEXT) self.context.update(PYSONDecoder(self.context).decode( self.action.get('pyson_context', '{}'))) @@ -73,7 +73,7 @@ class Action(SignalEvent): tooltips = common.Tooltips() - if self.action['view_type'] == 'form': + if self.action['res_model']: but_search = gtk.Button() tooltips.set_tip(but_search, _('Search')) but_search.connect('clicked', self._sig_search) @@ -96,7 +96,7 @@ class Action(SignalEvent): hbox.pack_start(but_open, expand=False, fill=False) - if self.action['view_type'] == 'form': + if self.action['res_model']: hbox.pack_start(gtk.VSeparator(), expand=False, fill=True) but_previous = gtk.Button() tooltips.set_tip(but_previous, _('Previous')) @@ -140,11 +140,13 @@ class Action(SignalEvent): alignment.set_padding(0, 2, 2, 2) vbox.pack_start(alignment, expand=True, fill=True) + hbox.set_focus_chain([]) + self.widget.show_all() - if self.action['view_type'] == 'form': + if self.action['res_model']: self.screen = Screen(self.action['res_model'], - self._window, view_type=self.action['view_mode'], + self._window, mode=self.action['view_mode'], context=self.context, view_ids=view_ids, domain=self.domain, readonly=True, alternate_view=True) alignment.add(self.screen.screen_container.alternate_viewport) @@ -152,29 +154,6 @@ class Action(SignalEvent): self.screen.signal_connect(self, 'record-message', self._sig_label) self.screen.signal_connect(self, 'record-message', self._active_changed) - elif self.action['view_type'] == 'tree': - ctx = {} - ctx.update(rpc.CONTEXT) - ctx.update(self.context) - try: - view_base = rpc.execute('model', 'ir.ui.view', 'read', - view_ids[0], ['model', 'type'], ctx) - except Exception, exception: - common.process_exception(exception, self._window) - raise - try: - view = rpc.execute('model', view_base['model'], - 'fields_view_get', view_ids[0], view_base['type'], ctx) - except Exception, exception: - common.process_exception(exception, self._window) - raise - self.tree = ViewTree(view, [], self._window, True, - context=ctx) - alignment.add(self.tree.widget_get()) - name = self.tree.name - self.tree.view.connect('key_press_event', self.sig_key_press) - self.tree.signal_connect(self, 'select-changed', - self._active_changed) if attrs.get('string'): self.title.set_text(attrs['string']) @@ -219,7 +198,7 @@ class Action(SignalEvent): def _sig_label(self, screen, signal_data): name = '_' if signal_data[0] >= 0: - name = str(signal_data[0] + 1) + name = str(signal_data[0]) line = '(%s/%s)' % (name, signal_data[1]) self.label.set_text(line) @@ -268,6 +247,7 @@ class Action(SignalEvent): def update_domain(self, actions): domain_ctx = self.context.copy() + domain_ctx['context'] = domain_ctx for action in actions: if action.active: domain_ctx['_active_%s' % action.act_id] = action.active diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py index 44941ad073..185304fea9 100644 --- a/tryton/gui/window/view_form/model/field.py +++ b/tryton/gui/window/view_form/model/field.py @@ -27,8 +27,7 @@ class Field(object): class CharField(object): - def __init__(self, parent, attrs): - self.parent = parent + def __init__(self, attrs): self.attrs = attrs self.name = attrs['name'] self.internal = False @@ -65,8 +64,9 @@ class CharField(object): return screen_domain, screen_domain def context_get(self, record, check_load=True, eval_context=True): - context = {} - context.update(self.parent.context) + context = record.context_get().copy() + if record.parent: + context.update(record.parent.context_get()) if eval_context: context.update(record.expr_eval(self.attrs.get('context', {}), check_load=check_load)) @@ -114,8 +114,9 @@ class CharField(object): def set(self, record, value, modified=False): record.value[self.name] = value if modified: - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') + record.signal('record-changed') return True def get(self, record, check_load=True, readonly=True, modified=False): @@ -130,16 +131,16 @@ class CharField(object): def set_client(self, record, value, force_change=False): internal = record.value.get(self.name, False) - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) if (internal or False) != (record.value.get(self.name, False) or False): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.modified_fields = prev_modified_fields return record.validate(softvalidation=True) record.signal('record-changed') @@ -185,9 +186,6 @@ class CharField(object): def get_timestamp(self, record): return {} - def destroy(self): - self.parent = None - class SelectionField(CharField): @@ -232,19 +230,20 @@ class FloatField(CharField): def set_client(self, record, value, force_change=False): internal = record.value.get(self.name) - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) digits = record.expr_eval(self.attrs.get('digits', (16, 2))) if abs(float(internal or 0.0) - float(record.value[self.name] or 0.0)) \ >= (10.0**(-int(digits[1]))): if not self.get_state_attrs(record).get('readonly', False): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.modified_fields = prev_modified_fields + return record.validate(softvalidation=True) record.signal('record-changed') @@ -254,20 +253,20 @@ class NumericField(CharField): def set_client(self, record, value, force_change=False): value = Decimal(str(value)) internal = record.value.get(self.name) - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) digits = record.expr_eval(self.attrs.get('digits', (16, 2))) if abs((internal or Decimal('0.0')) - \ (record.value[self.name] or Decimal('0.0'))) \ >= Decimal(str(10.0**(-int(digits[1])))): if not self.get_state_attrs(record).get('readonly', False): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.prev_modified_fields = prev_modified_fields return record.validate(softvalidation=True) record.signal('record-changed') @@ -286,16 +285,16 @@ class BooleanField(CharField): def set_client(self, record, value, force_change=False): value = bool(value) internal = bool(record.value.get(self.name, False)) - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) if internal != bool(record.value.get(self.name, False)): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.modified_fields = prev_modified_fields return record.validate(softvalidation=True) record.signal('record-changed') @@ -313,25 +312,41 @@ class M2OField(CharField): ''' def get(self, record, check_load=True, readonly=True, modified=False): - if record.value.get(self.name): - if isinstance(record.value[self.name], (int, basestring, long)): - self.set(record, record.value[self.name]) - if isinstance(record.value[self.name], (int, basestring, long)): - return record.value[self.name] - return record.value[self.name][0] or False + value = record.value.get(self.name) + if record.parent_name == self.name: + value = record.parent.id if record.parent else False + if value: + if isinstance(value, (int, basestring, long)): + self.set(record, value) + value = record.value.get(self.name) + if isinstance(value, (int, basestring, long)): + return value + return value[0] or False return False def get_client(self, record): - if record.value.get(self.name): - if isinstance(record.value[self.name], (int, basestring, long)): - self.set(record, record.value[self.name]) - if isinstance(record.value[self.name], (int, basestring, long)): - return record.value[self.name] - return record.value[self.name][1] + value = record.value.get(self.name) + if record.parent_name == self.name: + value = record.parent.id if record.parent else False + if value: + if isinstance(value, (int, basestring, long)): + self.set(record, value) + value = record.value.get(self.name) + if isinstance(value, (int, basestring, long)): + return value + return value[1] return False def set(self, record, value, modified=False): - if value and isinstance(value, (int, long)): + if record.parent_name == self.name: + if record.parent: + if 'rec_name' in record.parent.value: + value = (record.parent.id, record.parent.value['rec_name']) + else: + value = record.parent.id + else: + value = False + if value and isinstance(value, (int, long)) and value > 0: args = ('model', self.attrs['relation'], 'read', value, ['rec_name'], rpc.CONTEXT) try: @@ -342,7 +357,8 @@ class M2OField(CharField): if not result: return value = value, result['rec_name'] - if value and len(value) != 2: + if value and (isinstance(value, (int, long)) + or len(value) != 2): value = (False, '') record.value[self.name + '.rec_name'] = '' else: @@ -351,22 +367,28 @@ class M2OField(CharField): else: record.value[self.name + '.rec_name'] = '' record.value[self.name] = value or (False, '') + if record.parent_name == self.name: + if record.parent: + if 'rec_name' not in record.parent.value: + record.parent.value['rec_name'] = \ + record.value[self.name + '.rec_name'] if modified: - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') + record.signal('record-changed') def set_client(self, record, value, force_change=False): internal = record.value.get(self.name) or (False, '') - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) if (internal[0] or False) != (record.value[self.name][0] or False): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.modified_fields = prev_modified_fields return record.validate(softvalidation=True) record.signal('record-changed') @@ -395,6 +417,12 @@ class M2OField(CharField): screen_domain, attr_domain = self.domains_get(record) return localize_domain(inverse_leaf(screen_domain), self.name) + attr_domain + def get_state_attrs(self, record): + result = super(M2OField, self).get_state_attrs(record) + if record.parent_name == self.name: + result = result.copy() + result['readonly'] = True + return result class O2OField(M2OField): pass @@ -405,27 +433,42 @@ class O2MField(CharField): internal = Group of the related objects ''' - def __init__(self, parent, attrs): - super(O2MField, self).__init__(parent, attrs) + def __init__(self, attrs): + super(O2MField, self).__init__(attrs) self.context = {} def _group_changed(self, group, record): - record.parent.modified = True + if not record.parent: + return record.parent.modified_fields.setdefault(self.name) + record.parent.signal('record-modified') self.sig_changed(record.parent) record.parent.validate(softvalidation=True) record.parent.signal('record-changed') + record.parent.group.signal('group-changed', record.parent) + + def _group_list_changed(self, group, signal): + group.parent.group.signal('group-list-changed', signal) + + def _group_cleared(self, group, signal): + group.parent.signal('group-cleared') def _set_default_value(self, record): if record.value.get(self.name) is not None: return from group import Group + parent_name = self.attrs.get('relation_field', '') group = Group(self.attrs['relation'], {}, record.window, parent=record, - parent_name=self.attrs.get('relation_field', ''), + parent_name=parent_name, + child_name=self.name, context=self.context, parent_datetime_field=self.attrs.get('datetime_field')) + if record.model_name == self.attrs['relation']: + group.fields = record.group.fields group.signal_connect(group, 'group-changed', self._group_changed) + group.signal_connect(group, 'group-list-changed', self._group_list_changed) + group.signal_connect(group, 'group-cleared', self._group_cleared) record.value[self.name] = group def get_client(self, record): @@ -440,7 +483,7 @@ class O2MField(CharField): if record2.id > 0: values = record2.get(check_load=check_load, get_readonly=readonly, get_modifiedonly=modified) - if record2.is_modified() and values: + if record2.modified and values: result.append(('write', record2.id, values)) result[0][1].append(record2.id) else: @@ -479,18 +522,28 @@ class O2MField(CharField): def set(self, record, value, modified=False): from group import Group group = record.value.get(self.name) + fields = {} if group is not None: group.signal_unconnect(group) + fields = group.fields + elif record.model_name == self.attrs['relation']: + fields = record.group.fields + parent_name = self.attrs.get('relation_field', '') group = Group(self.attrs['relation'], {}, record.window, - parent=record, parent_name=self.attrs.get('relation_field', ''), + parent=record, parent_name=parent_name, + child_name=self.name, context=self.context, parent_datetime_field=self.attrs.get('datetime_field')) + group.fields = fields record.value[self.name] = group - record.value[self.name].load(value, display=False) + group.load(value, display=False) group.signal_connect(group, 'group-changed', self._group_changed) + group.signal_connect(group, 'group-list-changed', self._group_list_changed) + group.signal_connect(group, 'group-cleared', self._group_cleared) if modified: - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') + record.signal('record-changed') def set_client(self, record, value, force_change=False): pass @@ -502,31 +555,38 @@ class O2MField(CharField): if value and len(value) and isinstance(value[0], (int, long)): return self.set(record, value, modified=modified) - # value is a list of dict + group = record.value.get(self.name) fields = {} + if group is not None: + group.signal_unconnect(group) + fields = group.fields + elif record.model_name == self.attrs['relation']: + fields = record.group.fields + + # value is a list of dict + fields_dict = {} if value and len(value): context = self.context_get(record) field_names = [] for val in value: for fieldname in val.keys(): - if fieldname not in field_names: + if (fieldname not in field_names + and fieldname not in fields): field_names.append(fieldname) - args = ('model', self.attrs['relation'], 'fields_get', - field_names, context) - try: - fields = rpc.execute(*args) - except Exception, exception: - fields = common.process_exception(exception, record.window, - *args) - if not fields: - return False + if field_names: + args = ('model', self.attrs['relation'], 'fields_get', + field_names, context) + try: + fields_to_load = rpc.execute(*args) + except Exception, exception: + fields_dict = common.process_exception(exception, + record.window, *args) + if not fields_dict: + return False - group = record.value.get(self.name) - if group is not None: - group.signal_unconnect(group) - group = Group( - self.attrs['relation'], fields, record.window, parent=record, - parent_name=self.attrs.get('relation_field', ''), + parent_name = self.attrs.get('relation_field', '') + group = Group(self.attrs['relation'], fields_dict, record.window, + parent=record, parent_name=parent_name, child_name=self.name, context=self.context, parent_datetime_field=self.attrs.get('datetime_field')) if record.value.get(self.name): @@ -535,12 +595,14 @@ class O2MField(CharField): group.record_deleted.extend(record.value[self.name].record_deleted) group.record_removed.extend(record.value[self.name].record_removed) record.value[self.name] = group + group.fields = fields for vals in (value or []): new_record = record.value[self.name].new(default=False) new_record.set_default(vals, modified=modified) group.add(new_record) - group.signal_connect(group, - 'group-changed', self._group_changed) + group.signal_connect(group, 'group-changed', self._group_changed) + group.signal_connect(group, 'group-list-changed', self._group_list_changed) + group.signal_connect(group, 'group-cleared', self._group_cleared) return True def set_on_change(self, record, value): @@ -607,7 +669,7 @@ class O2MField(CharField): if not record2.loaded: continue if not record2.validate(): - if not record2.is_modified(): + if not record2.modified: record.value[self.name].remove(record2) else: res = False @@ -643,10 +705,16 @@ class M2MField(O2MField): def set(self, record, value, modified=False): from group import Group group = record.value.get(self.name) + fields = {} if group is not None: group.signal_unconnect(group) + fields = group.fields + elif record.model_name == self.attrs['relation']: + fields = record.group.fields + parent_name = self.attrs.get('relation_field', '') group = Group(self.attrs['relation'], {}, record.window, - parent=record, parent_name=self.attrs.get('relation_field', ''), + parent=record, parent_name=parent_name, + child_name=self.name, context=self.context, parent_datetime_field=self.attrs.get('datetime_field')) if record.value.get(self.name): @@ -654,11 +722,15 @@ class M2MField(O2MField): group.record_deleted.extend(record.value[self.name].record_deleted) group.record_removed.extend(record.value[self.name].record_removed) record.value[self.name] = group - record.value[self.name].load(value, display=False) + group.fields = fields + group.load(value, display=False) group.signal_connect(group, 'group-changed', self._group_changed) + group.signal_connect(group, 'group-list-changed', self._group_list_changed) + group.signal_connect(group, 'group-cleared', self._group_cleared) if modified: - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') + record.signal('record-changed') class ReferenceField(CharField): @@ -680,16 +752,16 @@ class ReferenceField(CharField): def set_client(self, record, value, force_change=False): internal = record.value.get(self.name) - prev_modified = record.modified + prev_modified_fields = record.modified_fields.copy() self.set(record, value) if (internal or False) != (record.value[self.name] or False): - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') try: self.sig_changed(record) except Exception: record.value[self.name] = internal - record.modified = prev_modified + record.modified_fields = prev_modified_fields return record.validate(softvalidation=True) record.signal('record-changed') @@ -706,7 +778,7 @@ class ReferenceField(CharField): ref_id = int(ref_id) if not ref_id: ref_str = '' - if not ref_str and ref_id: + if not ref_str and ref_id > 0: args = ('model', ref_model, 'read', ref_id, ['rec_name'], rpc.CONTEXT) try: @@ -731,8 +803,8 @@ class ReferenceField(CharField): if self.name + '.rec_name' in record.value: del record.value[self.name + '.rec_name'] if modified: - record.modified = True record.modified_fields.setdefault(self.name) + record.signal('record-modified') TYPES = { 'char': CharField, diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index b6cee4c6fa..30ea101dfb 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -1,5 +1,6 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +import copy import tryton.rpc as rpc from record import Record from field import Field, O2MField @@ -10,8 +11,8 @@ import tryton.common as common class Group(SignalEvent, list): def __init__(self, model_name, fields, window, ids=None, parent=None, - parent_name='', context=None, domain=None, readonly=False, - parent_datetime_field=None): + parent_name='', child_name='', context=None, domain=None, + readonly=False, parent_datetime_field=None): super(Group, self).__init__() if domain is None: domain = [] @@ -20,6 +21,7 @@ class Group(SignalEvent, list): self.__window = window self.parent = parent self.parent_name = parent_name + self.child_name = child_name self.parent_datetime_field = parent_datetime_field self._context = context or {} self.model_name = model_name @@ -33,6 +35,7 @@ class Group(SignalEvent, list): if self._context.get('_datetime'): self.readonly = True self.__id2record = {} + self.__field_childs = None def __get_window(self): return self.__window @@ -45,6 +48,7 @@ class Group(SignalEvent, list): window = property(__get_window, __set_window) def insert(self, pos, record): + assert record.group is self if pos >= 1: self.__getitem__(pos - 1).next[id(self)] = record if pos < self.__len__(): @@ -54,16 +58,17 @@ class Group(SignalEvent, list): super(Group, self).insert(pos, record) self.__id2record[record.id] = record if not self.lock_signal: - self.signal('group-list-changed', ('record-added', pos)) + self.signal('group-list-changed', ('record-added', record)) def append(self, record): + assert record.group is self if self.__len__() >= 1: self.__getitem__(self.__len__() - 1).next[id(self)] = record record.next[id(self)] = None super(Group, self).append(record) self.__id2record[record.id] = record if not self.lock_signal: - self.signal('group-list-changed', ('record-added', -1)) + self.signal('group-list-changed', ('record-added', record)) def _remove(self, record): idx = self.index(record) @@ -73,24 +78,23 @@ class Group(SignalEvent, list): self.__getitem__(idx + 1) else: self.__getitem__(idx - 1).next[id(self)] = None + if not self.lock_signal: + self.signal('group-list-changed', ('record-removed', record)) super(Group, self).remove(record) del self.__id2record[record.id] - if not self.lock_signal: - self.signal('group-list-changed', ('record-removed', idx)) def clear(self): if not self.lock_signal: - for record in self: - self.signal('group-list-changed', ('record-removed', 0)) - del self[:] + for record in self[:]: + self.signal('group-list-changed', ('record-removed', record)) + self.pop(0) if not self.lock_signal: self.signal('group-list-changed', ('group-cleared',)) self.__id2record = {} self.record_removed, self.record_deleted = [], [] def move(self, record, pos): - self.lock_signal = True - if self.__len__() > pos: + if self.__len__() > pos >= 0: idx = self.index(record) self._remove(record) if pos > idx: @@ -99,7 +103,6 @@ class Group(SignalEvent, list): else: self._remove(record) self.append(record) - self.lock_signal = False def __setitem__(self, i, value): super(Group, self).__setitem__(i, value) @@ -113,7 +116,7 @@ class Group(SignalEvent, list): for name, attr in fields.iteritems(): field = Field(attr['type']) attr['name'] = name - self.fields[name] = field(self, attr) + self.fields[name] = field(attr) if isinstance(self.fields[name], O2MField) \ and '_datetime' in self._context: self.fields[name].context.update({ @@ -121,17 +124,26 @@ class Group(SignalEvent, list): }) def save(self): + saved = [] for record in self: - saved = record.save() - self.writen(saved) + saved.append(record.save()) + return saved - def writen(self, ids): + @property + def root_group(self): + root = self + parent = self.parent + while parent: + root = parent.group + return root + + def written(self, ids): if isinstance(ids, (int, long)): ids = [ids] ids = [x for x in self.on_write_ids(ids) or [] if x not in ids] if not ids: return - self.reload(ids) + self.root_group.reload(ids) return ids def reload(self, ids): @@ -154,7 +166,7 @@ class Group(SignalEvent, list): res += res2 return list({}.fromkeys(res)) - def load(self, ids, display=True, modified=False): + def load(self, ids, display=True, modified=False, id2record=None): if not ids: return True @@ -166,7 +178,7 @@ class Group(SignalEvent, list): if self.get(id): continue new_record = Record(self.model_name, id, self.window, - parent=self.parent, parent_name=self.parent_name, group=self) + group=self) self.append(new_record) new_records.append(new_record) new_record.signal_connect(self, 'record-changed', @@ -206,26 +218,21 @@ class Group(SignalEvent, list): context = property(_get_context) def add(self, record, position=-1, modified=True): - if not record.group is self: - fields = {} - for i in record.group.fields: - fields[record.group.fields[i].attrs['name']] = \ - record.group.fields[i].attrs - self.add_fields(fields) + if record.group is not self: + record.signal_unconnect(record.group) record.group = self - + record.window = self.window + record.signal_connect(self, 'record-changed', self._record_changed) + record.signal_connect(self, 'record-modified', self._record_modified) if position == -1: self.append(record) else: self.insert(position, record) self.current_idx = position - record.parent = self.parent - record.parent_name = self.parent_name - record.window = self.window if modified: - record.modified = True - record.signal_connect(self, 'record-changed', self._record_changed) - record.signal_connect(self, 'record-modified', self._record_modified) + record.modified_fields.setdefault('id') + record.signal('record-modified') + self.signal('group-changed', record) return record def set_sequence(self, field='sequence'): @@ -238,9 +245,9 @@ class Group(SignalEvent, list): else: index = record[field].get(record) - def new(self, default=True, domain=None, context=None, signal=True): - record = Record(self.model_name, None, self.window, group=self, - parent=self.parent, parent_name=self.parent_name) + def new(self, default=True, domain=None, context=None, signal=True, + obj_id=None): + record = Record(self.model_name, obj_id, self.window, group=self) record.signal_connect(self, 'record-changed', self._record_changed) record.signal_connect(self, 'record-modified', self._record_modified) if default: @@ -260,7 +267,8 @@ class Group(SignalEvent, list): if signal: record.signal('record-changed', record.parent) - def remove(self, record, remove=False, modified=True, signal=True): + def remove(self, record, remove=False, modified=True, signal=True, + force_remove=False): idx = self.index(record) if self[idx].id > 0: if remove: @@ -272,10 +280,12 @@ class Group(SignalEvent, list): self.record_removed.remove(self[idx]) self.record_deleted.append(self[idx]) if record.parent: - record.parent.modified = True + record.parent.modified_fields.setdefault('id') + record.parent.signal('record-modified') if modified: - record.modified = True - if not record.parent or self[idx].id <= 0: + record.modified_fields.setdefault('id') + record.signal('record-modified') + if not record.parent or self[idx].id <= 0 or force_remove: self._remove(self[idx]) if len(self): @@ -360,11 +370,25 @@ class Group(SignalEvent, list): super(Group, self).destroy() self.__window = None self.parent = None - for field in self.fields.itervalues(): - field.destroy() self.fields = {} self.record_deleted, self.record_removed = [], [] self.__id2record = None for record in self: record.destroy() self[:] = [] + + def get_by_path(self, path): + 'return record by path' + group = self + record = None + for child_name, id_ in path: + record = group.get(id_) + if not record: + return None + if not child_name: + continue + record[child_name] + group = record.value.get(child_name) + if not isinstance(group, Group): + return None + return record diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py index 23abaf8c0c..a05f629221 100644 --- a/tryton/gui/window/view_form/model/record.py +++ b/tryton/gui/window/view_form/model/record.py @@ -14,22 +14,18 @@ class Record(SignalEvent): id = -1 - def __init__(self, model_name, obj_id, window, group=None, parent=None, - parent_name=''): + def __init__(self, model_name, obj_id, window, group=None): super(Record, self).__init__() - self.window = window + self.__window = window self.model_name = model_name self.id = obj_id or Record.id if self.id < 0: Record.id -= 1 self._loaded = set() - self.parent = parent - self.parent_name = parent_name self.group = group if group is not None: assert model_name == group.model_name self.state_attrs = {} - self.__modified = False self.modified_fields = {} self._timestamp = None self.attachment_count = -1 @@ -37,7 +33,7 @@ class Record(SignalEvent): self.value = {} self.autocompletion = {} - def __getitem__(self, name): + def __getitem__(self, name, raise_exception=False): if name not in self._loaded and self.id > 0: ids = [self.id] if name == '*': @@ -68,6 +64,7 @@ class Record(SignalEvent): if field.attrs.get('loading', 'eager') == 'eager'] else: fields = self.group.fields.keys() + fields = [fname for fname in fields if fname not in self._loaded] fields.extend(('%s.rec_name' % fname for fname in fields[:] if self.group.fields[fname].attrs['type'] in ('many2one', 'one2one', 'reference'))) @@ -78,6 +75,8 @@ class Record(SignalEvent): try: values = rpc.execute(*args) except Exception, exception: + if raise_exception: + raise values = common.process_exception(exception, self.window, *args) if not values: values = [{'id': x} for x in ids] @@ -95,17 +94,67 @@ class Record(SignalEvent): return self.group.fields.get(name, False) def __repr__(self): - return '' % (self.id, self.model_name) + return '' % (self.id, self.model_name, id(self)) - def get_modified(self): - return self.__modified + @property + def modified(self): + return bool(self.modified_fields) + + @property + def parent(self): + return self.group.parent + + @property + def parent_name(self): + return self.group.parent_name def set_modified(self, value): - self.__modified = value if value: self.signal('record-modified') - modified = property(get_modified, set_modified) + def children_group(self, field_name, check_load=True): + if not field_name: + return [] + if check_load: + self._check_load([field_name]) + group = self.value.get(field_name) + if group is False or group is None: + return [] + + if id(group.fields) != id(self.group.fields): + self.group.fields.update(group.fields) + group.fields = self.group.fields + group.on_write = self.group.on_write + group.readonly = self.group.readonly + if group.window != self.window: + group.window = self.window + return group + + def get_path(self, group): + path = [] + i = self + child_name = '' + while i: + path.append((child_name, i.id)) + if i.group is group: + break + child_name = i.group.child_name + i = i.parent + path.reverse() + return tuple(path) + + def _get_window(self): + return self.__window + + def _set_window(self, window): + self.__window = window + for fieldname, value in self.value.iteritems(): + if fieldname not in self.group.fields: + continue + if isinstance(self.group.fields[fieldname], field.O2MField): + value.window = window + + window = property(_get_window, _set_window) def get_removed(self): if self.group is not None: @@ -126,9 +175,6 @@ class Record(SignalEvent): readonly = property(get_readonly) - def is_modified(self): - return self.modified - def fields_get(self): return self.group.fields @@ -187,40 +233,48 @@ class Record(SignalEvent): return result def save(self, force_reload=True): - if self.id < 0: - value = self.get(get_readonly=True) - args = ('model', self.model_name, 'create', value, - self.context_get()) - try: - res = rpc.execute(*args) - except Exception, exception: - res = common.process_exception(exception, self.window, *args) - if not res: - return False - old_id = self.id - self.id = res - self.group.id_changed(old_id) - else: - if not self.is_modified(): - return self.id - self._check_load() - value = self.get(get_readonly=True, get_modifiedonly=True) - context = self.context_get() - context = context.copy() - context['_timestamp'] = self.get_timestamp() - args = ('model', self.model_name, 'write', [self.id], value, - context) - try: - if not rpc.execute(*args): - return False - except Exception, exception: - if not common.process_exception(exception, self.window, *args): - return False - self._loaded.clear() - if force_reload: - self.reload() - if self.group: - self.group.writen(self.id) + if self.id < 0 or self.modified: + if self.id < 0: + value = self.get(get_readonly=True) + args = ('model', self.model_name, 'create', value, + self.context_get()) + try: + res = rpc.execute(*args) + except Exception, exception: + res = common.process_exception(exception, self.window, + *args) + if not res: + return False + old_id = self.id + self.id = res + self.group.id_changed(old_id) + elif self.modified: + self._check_load() + value = self.get(get_readonly=True, get_modifiedonly=True, + check_load=False) + if value: + context = self.context_get() + context = context.copy() + context['_timestamp'] = self.get_timestamp() + args = ('model', self.model_name, 'write', [self.id], + value, context) + try: + if not rpc.execute(*args): + return False + except Exception, exception: + res = common.process_exception(exception, self.window, + *args) + if not res: + return False + self._loaded.clear() + self.modified_fields = {} + if force_reload: + self.reload() + if self.group: + self.group.written(self.id) + if self.parent: + self.parent.modified_fields.pop(self.group.child_name, None) + self.parent.save(force_reload=force_reload) return self.id def default_get(self, domain=None, context=None): @@ -233,6 +287,8 @@ class Record(SignalEvent): vals = common.process_exception(exception, self.window, *args) if not vals: return + if self.parent_name in self.group.fields and self.parent: + vals[self.parent_name] = self.parent.id self.set_default(vals) for fieldname, fieldinfo in self.group.fields.iteritems(): if not fieldinfo.attrs.get('autocomplete'): @@ -331,9 +387,6 @@ class Record(SignalEvent): for fieldname, value in later.iteritems(): self.group.fields[fieldname].set(self, value, modified=modified) self._loaded.add(fieldname) - self.modified = modified - if not self.modified: - self.modified_fields = {} if signal: self.signal('record-changed') @@ -496,7 +549,6 @@ class Record(SignalEvent): def destroy(self): super(Record, self).destroy() self.window = None - self.parent = None self.group = None self.value = None self.next = None diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index d3a0ea98e1..bdc0dbb6ce 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -1,6 +1,9 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. "Screen" +import gobject +import copy +import xml.dom.minidom import tryton.rpc as rpc from tryton.gui.window.view_form.model.group import Group from tryton.gui.window.view_form.view.screen_container import ScreenContainer @@ -9,22 +12,19 @@ from tryton.signal_event import SignalEvent from tryton.common import node_attributes from tryton.config import CONFIG import tryton.common as common -import gobject -import copy -import xml.dom.minidom class Screen(SignalEvent): "Screen" - def __init__(self, model_name, window, view_ids=None, view_type=None, + def __init__(self, model_name, window, view_ids=None, mode=None, context=None, views_preload=None, domain=None, row_activate=None, limit=None, readonly=False, exclude_field=None, sort=None, search_value=None, alternate_view=False): if view_ids is None: view_ids = [] - if view_type is None: - view_type = ['tree', 'form'] + if mode is None: + mode = ['tree', 'form'] if context is None: context = {} if views_preload is None: @@ -34,10 +34,10 @@ class Screen(SignalEvent): super(Screen, self).__init__() + self.readonly = readonly self.search_count = 0 if not row_activate: - # TODO change for a function that switch to form view - self.row_activate = self.switch_view + self.row_activate = self.default_row_activate else: self.row_activate = row_activate self.domain = domain @@ -65,12 +65,12 @@ class Screen(SignalEvent): self.sort = sort self.view_to_load = [] - if view_type: - self.view_to_load = view_type[1:] + if mode: + self.view_to_load = mode[1:] view_id = False if view_ids: view_id = view_ids.pop(0) - view = self.add_view_id(view_id, view_type[0]) + view = self.add_view_id(view_id, mode[0]) self.screen_container.set(view.widget) self.display() @@ -90,10 +90,9 @@ class Screen(SignalEvent): 'tree', ctx) except Exception: return - self.filter_widget = Form(self.fields_view_tree['arch'], - self.fields_view_tree['fields'], self.model_name, - self.window, self.domain, (self, self.search_filter), - self.context) + self.filter_widget = Form(self.fields_view_tree, + self.model_name, self.window, self.domain, + (self, self.search_filter), self.context) self.screen_container.add_filter(self.filter_widget.widget, self.search_filter, self.search_clear, self.search_prev, self.search_next) @@ -194,9 +193,9 @@ class Screen(SignalEvent): group = property(__get_group, __set_group) - def new_group(self, readonly=False): + def new_group(self): self.group = Group(self.model_name, {}, self.window, domain=self.domain, - context=self.context, readonly=readonly) + context=self.context, readonly=self.readonly) def _group_cleared(self, group, signal): for view in self.views: @@ -224,10 +223,16 @@ class Screen(SignalEvent): except Exception: offset = 0 try: - pos = self.group.index(record) - except Exception: - pos = -1 - self.signal('record-message', (pos + offset, len(self.group) + offset, + pos = self.group.index(record) + offset + 1 + except ValueError: + pos = [] + i = record + while i: + pos.append(i.group.index(i) + 1) + i = i.parent + pos.reverse() + pos = tuple(pos) + self.signal('record-message', (pos or 0, len(self.group) + offset, self.search_count, record and record.id)) attachment_count = 0 if record and record.attachment_count > 0: @@ -268,9 +273,25 @@ class Screen(SignalEvent): self.screen_container = None self.widget = None + def default_row_activate(self): + from tryton.action import Action + if (self.current_view.view_type == 'tree' and + self.current_view.widget_tree.keyword_open): + return Action.exec_keyword('tree_open', self.window, { + 'model': self.model_name, + 'id': self.id_get(), + 'ids': self.ids_get(), + }, context=self.context.copy(), warning=False) + else: + self.switch_view(view_type='form') + return True + def switch_view(self, view_type=None, default=True, context=None): + if self.modified(): + return self.current_view.set_value() - if self.current_record and self.current_record not in self.group: + if (self.current_record and + self.current_record not in self.current_record.group): self.current_record = None fields = self.current_view.get_fields() if self.current_record and not self.current_record.validate(fields): @@ -283,7 +304,8 @@ class Screen(SignalEvent): self.load_view_to_load() self.__current_view = len(self.views) - 1 else: - self.__current_view = (self.__current_view + 1) % len(self.views) + self.__current_view = ((self.__current_view + 1) + % len(self.views)) if not view_type: break elif self.current_view.view_type == view_type: @@ -306,10 +328,7 @@ class Screen(SignalEvent): def add_view_id(self, view_id, view_type, display=False, context=None): if view_type in self.views_preload: - return self.add_view(self.views_preload[view_type]['arch'], - self.views_preload[view_type]['fields'], display, - toolbar=self.views_preload[view_type].get('toolbar', False), - context=context) + view = self.views_preload[view_type] else: ctx = {} ctx.update(rpc.CONTEXT) @@ -324,17 +343,19 @@ class Screen(SignalEvent): view = common.process_exception(exception, self.window, *args) if not view: return - return self.add_view(view['arch'], view['fields'], display, - toolbar=view.get('toolbar', False), context=context) + return self.add_view(view, display, toolbar=view.get('toolbar', False), + context=context) - def add_view(self, arch, fields, display=False, toolbar=None, context=None): + def add_view(self, view, display=False, toolbar=None, context=None): if toolbar is None: toolbar = {} + arch = view['arch'] + fields = view['fields'] xml_dom = xml.dom.minidom.parseString(arch) for node in xml_dom.childNodes: if node.localName == 'tree': - self.fields_view_tree = {'arch': arch, 'fields': fields} + self.fields_view_tree = view break if node.localName == 'tree': @@ -345,22 +366,14 @@ class Screen(SignalEvent): if field not in self.group.fields: fields[field]['loading'] = loading + children_field = view.get('field_childs') + from tryton.gui.window.view_form.view.widget_parse import WidgetParse - if self.current_record and (self.current_record not in self.group): - self.group.append(self.current_record) self.group.add_fields(fields, context=context) - if self.exclude_field: - if self.exclude_field in self.group.fields: - field = self.group.fields[self.exclude_field] - field.attrs['states'] = {'invisible': True} - field.attrs['readonly'] = True - field.attrs['invisible'] = True - field.attrs['tree_invisible'] = True - field.attrs['exclude_field'] = True - parser = WidgetParse(parent=self.parent, window=self.window) - view = parser.parse(self, xml_dom, self.group.fields, toolbar=toolbar) + view = parser.parse(self, xml_dom, self.group.fields, toolbar=toolbar, + children_field=children_field) self.views.append(view) @@ -406,8 +419,12 @@ class Screen(SignalEvent): ctx.update(rpc.CONTEXT) ctx.update(self.context) ctx.update(context) - record = self.group.new(default, self.domain, ctx) - self.group.add(record, self.new_model_position()) + if self.current_record: + group = self.current_record.group + else: + group = self.group + record = group.new(default, self.domain, ctx) + group.add(record, self.new_model_position()) self.current_record = record fields = None if self.current_view: @@ -440,31 +457,27 @@ class Screen(SignalEvent): def save_current(self): if not self.current_record: - return False + if self.current_view == 'tree' and len(self.group): + self.current_record = self.group[0] + else: + return True self.current_view.set_value() obj_id = False fields = self.current_view.get_fields() - if self.current_record.validate(fields): + path = self.current_record.get_path(self.group) + if self.current_view.view_type == 'tree': + self.group.save() + obj_id = self.current_record.id + elif self.current_record.validate(fields): obj_id = self.current_record.save(force_reload=True) else: self.current_view.set_cursor() self.current_view.display() return False - if self.current_view.view_type == 'tree': - for record in self.group: - if record.is_modified(): - if record.validate(fields): - obj_id = record.save(force_reload=True) - else: - self.current_view.set_cursor() - self.current_record = record - self.current_view.set_cursor() - self.display() - return False - self.current_view.set_cursor() + if path: + path = path[:-1] + ((path[-1][0], obj_id),) + self.current_record = self.group.get_by_path(path) self.display() - if self.current_record not in self.group: - self.group.add(self.current_record, modified=False) self.request_set() return obj_id @@ -484,22 +497,22 @@ class Screen(SignalEvent): includeid=includeid, check_load=check_load, get_modifiedonly=get_modifiedonly) - def is_modified(self): + def modified(self): self.current_view.set_value() res = False if self.current_view.view_type != 'tree': - res = self.current_record and self.current_record.is_modified() + res = self.current_record and self.current_record.modified else: for record in self.group: - if record.is_modified(): + if record.modified: res = True return res - def reload(self, writen=False): + def reload(self, written=False): ids = self.sel_ids_get() self.group.reload(ids) - if writen: - self.group.writen(ids) + if written: + self.group.written(ids) if self.parent: self.parent.reload() self.display() @@ -532,35 +545,43 @@ class Screen(SignalEvent): if not res: return False self.current_view.set_cursor() - if self.current_record in self.group: - idx = self.group.index(self.current_record) - self.group.remove(self.current_record, remove=remove) - if self.group: - idx = min(idx, len(self.group) - 1) - self.current_record = self.group[idx] - else: - self.current_record = None + record = self.current_record + idx = record.group.index(record) + record.group.remove(record, remove=remove) + + if delete: + if (record.parent and + record.parent.model_name == record.model_name): + record.parent.save() + + if record.group: + idx = min(idx, len(record.group) - 1) + self.current_record = record.group[idx] + elif (record.parent and + record.parent.model_name == record.model_name): + self.current_record = record.parent + else: + self.current_record = None if reload_ids: - self.group.reload(reload_ids) + self.group.root_group.reload(reload_ids) self.display() res = True if self.current_view.view_type == 'tree': - ids = self.current_view.sel_ids_get() - if delete and ids: + records = self.current_view.selected_records() + if delete and records: context = {} context.update(rpc.CONTEXT) context.update(self.context) context['_timestamp'] = {} - for record_id in ids: - record = self.group.get(record_id) + for record in records: context['_timestamp'].update(record.get_timestamp()) - reload_ids = self.group.on_write_ids(ids) + reload_ids = self.group.on_write_ids([x.id for x in records]) if reload_ids: - for record_id in ids: - if record_id in reload_ids: - reload_ids.remove(record_id) - args = ('model', self.model_name, 'delete', ids, - context) + for record in records: + if record.id in reload_ids: + reload_ids.remove(record.id) + args = ('model', self.model_name, 'delete', + [x.id for x in records], context) try: res = rpc.execute(*args) except Exception, exception: @@ -568,24 +589,34 @@ class Screen(SignalEvent): *args) if not res: return False - sel_records = self.current_view.selected_records() - if not sel_records: + if not records: return True - idx = self.group.index(sel_records[0]) - for record in sel_records: + path = self.current_view.store.on_get_path(records[0]) + for record in records: # set current model to None to prevent __select_changed # to save the previous_model as it can be already deleted. self.current_record = None - self.group.remove(record, remove=remove, signal=False) + record.group.remove(record, remove=remove, signal=False) + # send record-changed only once record.signal('record-changed') - if self.group: - idx = min(idx, len(self.group) - 1) - self.current_record = self.group[idx] + + if delete: + for record in records: + if record.parent: + record.parent.save() + + if path[-1] > 0: + path = path[:-1] + (path[-1] - 1,) else: - self.current_record = None + path = path[:-1] + if path: + iter_ = self.current_view.store.get_iter(path) + self.current_record = self.current_view.store.get_value(iter_, 0) + elif len(self.group): + self.current_record = self.group[0] if reload_ids: - self.group.reload(reload_ids) + self.group.root_group.reload(reload_ids) self.current_view.set_cursor() self.display() res = True @@ -624,54 +655,78 @@ class Screen(SignalEvent): self.current_view.set_cursor(reset_view=False) def display_next(self): - self.current_view.set_value() - self.current_view.set_cursor(reset_view=False) - if self.current_record in self.group: - idx = self.group.index(self.current_record) - inc = 1 - if self.current_view.view_type == 'tree': - start, end = self.current_view.widget_tree.get_visible_range() - inc += end[0] - start[0] - if inc >= 4 and (end[0] + 1) < len(self.group): - inc -= 3 - vadjustment = self.current_view.widget_tree.get_vadjustment() - vadjustment.value = vadjustment.value + vadjustment.page_increment - idx = idx + inc - if idx >= len(self.group): - idx = len(self.group) - 1 - self.current_record = self.group[idx] + view = self.current_view + view.set_value() + view.set_cursor(reset_view=False) + if view.view_type == 'tree': + start, end = view.widget_tree.get_visible_range() + vadjustment = view.widget_tree.get_vadjustment() + vadjustment.value = vadjustment.value + vadjustment.page_increment + store = view.store + iter_ = store.get_iter(end) + self.current_record = store.get_value(iter_, 0) + elif self.current_record.group: + group = self.current_record.group + record = self.current_record + while group: + children = record.children_group(view.children_field) + if children: + record = children[0] + break + idx = group.index(record) + 1 + if idx < len(group): + record = group[idx] + break + parent = record.parent + if not parent: + break + next = parent.next.get(id(parent.group)) + while not next: + parent = parent.parent + if not parent: + break + next = parent.next.get(id(parent.group)) + if not next: + break + record = next + break + self.current_record = record else: - self.current_record = len(self.group) \ - and self.group[0] - self.current_view.set_cursor(reset_view=False) - self.current_view.display() + self.current_record = len(self.group) and self.group[0] + view.set_cursor(reset_view=False) + view.display() def display_prev(self): - self.current_view.set_value() - self.current_view.set_cursor(reset_view=False) - if self.current_record in self.group: - inc = 1 - if self.current_view.view_type == 'tree': - range = self.current_view.widget_tree.get_visible_range() - if range: - start, end = range - inc += end[0] - start[0] - if inc >= 4 and start[0] > 0: - inc -= 3 - vadjustment = \ - self.current_view.widget_tree.get_vadjustment() - if vadjustment.value: - vadjustment.value = vadjustment.value - \ - vadjustment.page_increment - idx = self.group.index(self.current_record) - inc - if idx < 0: - idx = 0 - self.current_record = self.group[idx] + view = self.current_view + view.set_value() + view.set_cursor(reset_view=False) + if view.view_type == 'tree': + start, end = view.widget_tree.get_visible_range() + vadjustment = view.widget_tree.get_vadjustment() + vadjustment.value = vadjustment.value - vadjustment.page_increment + store = view.store + iter_ = store.get_iter(start) + self.current_record = store.get_value(iter_, 0) + elif self.current_record.group: + group = self.current_record.group + record = self.current_record + idx = group.index(record) - 1 + if idx >= 0: + record = group[idx] + children = True + while children: + children = record.children_group(view.children_field) + if children: + record = children[-1] + else: + parent = record.parent + if parent: + record = parent + self.current_record = record else: - self.current_record = len(self.group) \ - and self.group[-1] - self.current_view.set_cursor(reset_view=False) - self.current_view.display() + self.current_record = len(self.group) and self.group[-1] + view.set_cursor(reset_view=False) + view.display() def sel_ids_get(self): return self.current_view.sel_ids_get() diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py index 3a4599d9ca..f41a4c02a9 100644 --- a/tryton/gui/window/view_form/view/form.py +++ b/tryton/gui/window/view_form/view/form.py @@ -16,9 +16,10 @@ _ = gettext.gettext class ViewForm(ParserView): def __init__(self, window, screen, widget, children=None, - buttons=None, toolbar=None, notebooks=None, cursor_widget=''): + buttons=None, toolbar=None, notebooks=None, cursor_widget='', + children_field=None): super(ViewForm, self).__init__(window, screen, widget, children, - buttons, toolbar, notebooks, cursor_widget) + buttons, toolbar, notebooks, cursor_widget, children_field) self.view_type = 'form' for button in self.buttons: @@ -157,7 +158,7 @@ class ViewForm(ParserView): value = Action._exec_action(act, self.window, data, {}) if atype in ('print', 'action'): if self.screen: - self.screen.reload(writen=True) + self.screen.reload(written=True) return value def __getitem__(self, name): diff --git a/tryton/gui/window/view_form/view/form_gtk/many2many.py b/tryton/gui/window/view_form/view/form_gtk/many2many.py index f2ef976968..28df2acef0 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2many.py @@ -60,7 +60,7 @@ class Many2Many(WidgetInterface): hbox.set_focus_chain([self.wid_text]) self.screen = Screen(attrs['relation'], self.window, - view_type=['tree'], views_preload=attrs.get('views', {}), + mode=['tree'], views_preload=attrs.get('views', {}), row_activate=self._on_activate) if not isinstance(self.screen.window, gtk.Dialog): diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py index d1e94464c6..9ae657594d 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2one.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py @@ -147,7 +147,7 @@ class Many2One(WidgetInterface): domain = self.field.domain_get(self.record) context = self.field.context_get(self.record) return Screen(self.attrs['relation'], self.window, domain=domain, - context=context, view_type=['form'], + context=context, mode=['form'], views_preload=self.attrs.get('views', {})) def sig_new(self, *args): diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py index c07a376b50..23d9b707f0 100644 --- a/tryton/gui/window/view_form/view/form_gtk/one2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py @@ -154,7 +154,7 @@ class One2Many(WidgetInterface): self.widget.pack_start(frame, expand=False, fill=True) self.screen = Screen(attrs['relation'], self.window, - view_type=attrs.get('mode', 'tree,form').split(','), + mode=attrs.get('mode', 'tree,form').split(','), views_preload=attrs.get('views', {}), row_activate=self._on_activate, exclude_field=attrs.get('relation_field', None)) @@ -324,17 +324,17 @@ class One2Many(WidgetInterface): def _sig_label(self, screen, signal_data): name = '_' - if signal_data[0] >= 0: - name = str(signal_data[0] + 1) + if signal_data[0] >= 1: + name = str(signal_data[0]) self.but_open.set_sensitive(True) self.but_del.set_sensitive(not self._readonly) if self.attrs.get('add_remove'): self.but_remove.set_sensitive(not self._readonly) - if signal_data[0] + 1 < signal_data[1]: + if signal_data[0] < signal_data[1]: self.but_next.set_sensitive(True) else: self.but_next.set_sensitive(False) - if signal_data[0] > 0: + if signal_data[0] > 1: self.but_pre.set_sensitive(True) else: self.but_pre.set_sensitive(False) @@ -381,7 +381,7 @@ class One2Many(WidgetInterface): def set_value(self, record, field): self.screen.current_view.set_value() - if self.screen.is_modified(): - record.modified = True + if self.screen.modified(): # TODO check if required record.modified_fields.setdefault(field.name) + record.signal('record-modified') return True diff --git a/tryton/gui/window/view_form/view/form_gtk/parser.py b/tryton/gui/window/view_form/view/form_gtk/parser.py index a6a132ec4d..c44c6ea5c9 100644 --- a/tryton/gui/window/view_form/view/form_gtk/parser.py +++ b/tryton/gui/window/view_form/view/form_gtk/parser.py @@ -83,7 +83,7 @@ class Button(object): }, self.form.window, context=ctx) else: raise Exception('Unallowed button type') - self.form.screen.reload(writen=True) + self.form.screen.reload(written=True) else: self.form.screen.display() @@ -292,9 +292,10 @@ class _container(object): class ParserForm(ParserInterface): - def __init__(self, window, parent=None, attrs=None, screen=None): + def __init__(self, window, parent=None, attrs=None, screen=None, + children_field=None): super(ParserForm, self).__init__(window, parent=parent, attrs=attrs, - screen=screen) + screen=screen, children_field=children_field) self.widget_id = 0 def parse(self, model_name, root_node, fields, notebook=None, paned=None, @@ -322,8 +323,7 @@ class ParserForm(ParserInterface): attrs = common.node_attributes(node) if not cursor_widget: if attrs.get('name') and fields.get(attrs['name']) \ - and not fields[attrs['name']].attrs.get('exclude_field', - False): + and attrs['name'] != self.screen.exclude_field: cursor_widget = attrs.get('name') if node.localName == 'image': common.ICONFACTORY.register_icon(attrs['name']) @@ -359,6 +359,9 @@ class ParserForm(ParserInterface): elif node.localName == 'label': text = attrs.get('string', '') if 'name' in attrs and attrs['name'] in fields: + if attrs['name'] == self.screen.exclude_field: + container.empty_add(int(attrs.get('colspan', 1))) + continue for attr_name in ('states', 'invisible'): if attr_name not in attrs \ and attr_name in fields[attrs['name']].attrs: @@ -480,6 +483,8 @@ class ParserForm(ParserInterface): gtk.gdk.PIXBUF_ROTATE_NONE) text = attrs.get('string', '') if 'name' in attrs and attrs['name'] in fields: + if attrs['name'] == self.screen.exclude_field: + continue for attr_name in ('states', 'invisible'): if attr_name in fields[attrs['name']].attrs: attrs[attr_name] = \ @@ -532,6 +537,9 @@ class ParserForm(ParserInterface): elif node.localName == 'field': name = str(attrs['name']) + if name == self.screen.exclude_field: + container.empty_add(int(attrs.get('colspan', 1))) + continue if name not in fields: container.empty_add(int(attrs.get('colspan', 1))) log = logging.getLogger('view') @@ -593,6 +601,9 @@ class ParserForm(ParserInterface): button_list += buttons text = '' if 'name' in attrs and attrs['name'] in fields: + if attrs['name'] == self.screen.exclude_field: + container.empty_add(int(attrs.get('colspan', 1))) + continue for attr_name in ('states', 'invisible'): if attr_name in fields[attrs['name']].attrs: attrs[attr_name] = fields[attrs['name'] diff --git a/tryton/gui/window/view_form/view/form_gtk/reference.py b/tryton/gui/window/view_form/view/form_gtk/reference.py index 4942995707..d204d26415 100644 --- a/tryton/gui/window/view_form/view/form_gtk/reference.py +++ b/tryton/gui/window/view_form/view/form_gtk/reference.py @@ -153,7 +153,7 @@ class Reference(WidgetInterface): return False if model and obj_id: if not leave: - screen = Screen(model, self.window, view_type=['form']) + screen = Screen(model, self.window, mode=['form']) screen.load([obj_id]) win = WinForm(screen, self.window) while win.run(): @@ -207,7 +207,7 @@ class Reference(WidgetInterface): model = self.get_model() if not model: return - screen = Screen(model, self.window, view_type=['form']) + screen = Screen(model, self.window, mode=['form']) win = WinForm(screen, self.window, new=True) while win.run(): if screen.save_current(): diff --git a/tryton/gui/window/view_form/view/graph.py b/tryton/gui/window/view_form/view/graph.py index 8e574ef4bb..6d4d6a16ef 100644 --- a/tryton/gui/window/view_form/view/graph.py +++ b/tryton/gui/window/view_form/view/graph.py @@ -6,9 +6,10 @@ from interface import ParserView class ViewGraph(ParserView): def __init__(self, window, screen, widget, children=None, - buttons=None, toolbar=None, notebooks=None, cursor_widget=None): + buttons=None, toolbar=None, notebooks=None, cursor_widget=None, + children_field=None): super(ViewGraph, self).__init__(window, screen, widget, children, - buttons, toolbar, notebooks, cursor_widget) + buttons, toolbar, notebooks, cursor_widget, children_field) self.view_type = 'graph' self.widgets = children diff --git a/tryton/gui/window/view_form/view/interface.py b/tryton/gui/window/view_form/view/interface.py index e5ce31fc33..47ea8b5ae4 100644 --- a/tryton/gui/window/view_form/view/interface.py +++ b/tryton/gui/window/view_form/view/interface.py @@ -5,19 +5,22 @@ class ParserInterface(object): - def __init__(self, window, parent=None, attrs=None, screen=None): + def __init__(self, window, parent=None, attrs=None, screen=None, + children_field=None): self.window = window self.parent = parent self.attrs = attrs self.title = None self.buttons = {} self.screen = screen + self.children_field = children_field class ParserView(object): def __init__(self, window, screen, widget, children=None, buttons=None, - toolbar=None, notebooks=None, cursor_widget=None): + toolbar=None, notebooks=None, cursor_widget=None, + children_field=None): self.window = window self.screen = screen self.widget = widget @@ -30,6 +33,7 @@ class ParserView(object): notebooks = [] self.notebooks = notebooks self.cursor_widget = cursor_widget + self.children_field = children_field def get_fields(self): return self.children.keys() diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index f0e04915b2..81cc6855ab 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -2,6 +2,11 @@ #this repository contains the full copyright notices and license terms. import gobject import gtk +import sys +if sys.version_info < (2, 6): + import simplejson as json +else: + import json import tryton.rpc as rpc import locale from interface import ParserView @@ -20,25 +25,43 @@ _ = gettext.gettext class AdaptModelGroup(gtk.GenericTreeModel): - def __init__(self, group): + def __init__(self, group, children_field=None): super(AdaptModelGroup, self).__init__() self.group = group self.last_sort = None self.sort_asc = True self.set_property('leak_references', False) + self.children_field = children_field + self.__removed = None # XXX dirty hack to allow update of has_child - def added(self, modellist, position): - if modellist is self.group: - model = self.group[position] - self.row_inserted(self.on_get_path(model), - self.get_iter(self.on_get_path(model))) + def added(self, group, record): + if group is self.group: + path = self.on_get_path(record) + iter_ = self.get_iter(path) + self.row_inserted(path, iter_) + if record.children_group(self.children_field): + self.row_has_child_toggled(path, iter_) + if (record.parent and + record.group is not self.group): + path = self.on_get_path(record.parent) + iter_ = self.get_iter(path) + self.row_has_child_toggled(path, iter_) def cancel(self): pass - def removed(self, lst, position): - self.row_deleted(position) - self.invalidate_iters() + def removed(self, group, record): + if group is self.group: + path = self.on_get_path(record) + self.row_deleted(path) + if (record.parent and + record.group != self.group and + len(record.children_group(self.children_field)) <= 1): + path = self.on_get_path(record.parent) + iter_ = self.get_iter(path) + self.__removed = record # XXX check for thread + self.row_has_child_toggled(path, iter_) + self.__removed = None def append(self, model): self.group.add(model) @@ -46,14 +69,45 @@ class AdaptModelGroup(gtk.GenericTreeModel): def prepend(self, model): self.group.add(model, 0) - def remove(self, iter): - idx = self.get_path(iter)[0] - self.group.remove(self.group[idx]) + def remove(self, iter_): + record = self.get_value(iter_, 0) + record.group.remove(record) self.invalidate_iters() - def move(self, path, position): - idx = path[0] - self.group.move(self.group[idx], position) + def __move(self, record, path, offset=0): + iter_ = self.get_iter(path) + record_pos = self.get_value(iter_, 0) + group = record_pos.group + pos = group.index(record_pos) + offset + if group is not record.group: + record.group.remove(record, remove=True, force_remove=True) + group.add(record) + record.modified_fields.setdefault(record.parent_name or 'id') + group.move(record, pos) + + def move_before(self, record, path): + self.__move(record, path) + + def move_after(self, record, path): + self.__move(record, path, 1) + + def move_into(self, record, path): + iter_ = self.get_iter(path) + parent = self.get_value(iter_, 0) + group = parent.children_group(self.children_field, check_load=True) + if group is not record.group: + record.group.remove(record, remove=True, force_remove=True) + group.add(record) + record.modified_fields.setdefault(record.parent_name or 'id') + group.move(record, 0) + + def move_end(self, record): + group = self.group + if group != record.group: + record.group.remove(record, remove=True) + group.add(record) + record.modified_fields.setdefault(record.parent_name or 'id') + group.move(record, -1) def sort(self, ids): ids2pos = {} @@ -87,67 +141,96 @@ class AdaptModelGroup(gtk.GenericTreeModel): return len(self.group) def on_get_flags(self): - return gtk.TREE_MODEL_LIST_ONLY + if not self.children_field: + return gtk.TREE_MODEL_LIST_ONLY + return 0 def on_get_n_columns(self): + # XXX return 1 def on_get_column_type(self, index): + # XXX return gobject.TYPE_PYOBJECT - def on_get_path(self, iter): - if iter in self.group: - return self.group.index(iter) + def on_get_path(self, iter_): + if isinstance(iter_, tuple): + return tuple(x[0] for x in iter_) else: - return 0 + path = [] + i = iter_ + while i: + path.append(i.group.index(i)) + if i.group is self.group: + break + i = i.parent + path.reverse() + return tuple(path) + + def on_get_tree_path(self, iter): + return self.on_get_path(iter) def on_get_iter(self, path): - if isinstance(path, tuple): - path = path[0] - if self.group is not None: - if path < len(self.group): - return self.group[path] - else: + group = self.group + record = None + for i in path: + if group is None or i >= len(group): return None - else: + record = group[i] + if not self.children_field: + break + group = record.children_group(self.children_field, check_load=True) + return record + + def on_get_value(self, record, column): + return record + + def on_iter_next(self, record): + if not record: return None + return record.next.get(id(record.group)) - def on_get_value(self, node, column): - assert column == 0 - return node + def on_iter_has_child(self, record): + if not self.children_field: + return False + children = record.children_group(self.children_field) + length = len(children) + if self.__removed and self.__removed in children: + length -= 1 + return bool(length) - def on_iter_next(self, node): - try: - return node.next[id(self.group)] - except IndexError: - return None - - def on_iter_has_child(self, node): - return False - - def on_iter_children(self, node): + def on_iter_children(self, record): + if self.children_field and record.children_group(self.children_field): + return record.children_group(self.children_field)[0] return None - def on_iter_n_children(self, node): - if node is None: + def on_iter_n_children(self, record): + if record is None or not self.children_field: return len(self.group) - return 0 + return len(record.children_group(self.children_field)) - def on_iter_nth_child(self, node, nth): - if node is None and self.group is not None: - return self.on_get_iter(0) + def on_iter_nth_child(self, record, nth): + if record is None or not self.children_field: + if nth < len(self.group): + return self.group[nth] + return None + if nth < len(record.children_group(self.children_field)): + return record.children_group(self.children_field)[nth] return None - def on_iter_parent(self, node): - return None + def on_iter_parent(self, record): + if record is None: + return None + return record.parent class ViewList(ParserView): def __init__(self, window, screen, widget, children=None, buttons=None, - toolbar=None, notebooks=None, cursor_widget=None): + toolbar=None, notebooks=None, cursor_widget=None, + children_field=None): super(ViewList, self).__init__(window, screen, widget, children, - buttons, toolbar, notebooks, cursor_widget) + buttons, toolbar, notebooks, cursor_widget, children_field) self.store = None self.view_type = 'tree' @@ -250,10 +333,22 @@ class ViewList(ParserView): selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect('changed', self.__select_changed) + dnd = False + if self.children_field: + children_field = self.widget_tree.cells.get(self.children_field) + if children_field: + parent_name = children_field.attrs.get('relation_field') + dnd = parent_name in self.widget_tree.cells + elif self.widget_tree.sequence: + dnd = True # Disable DnD on mac until it is fully supported - if self.widget_tree.sequence \ - and not (os.name == 'mac' \ - or (hasattr(os, 'uname') and os.uname()[0] == 'Darwin')): + if os.name == 'mac': + dnd = False + elif hasattr(os, 'uname') and os.uname()[0] == 'Darwin': + dnd = False + if screen.readonly: + dnd = False + if dnd: self.widget_tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),], gtk.gdk.ACTION_MOVE) @@ -270,6 +365,8 @@ class ViewList(ParserView): self.widget_tree.connect('drag-data-delete', self.drag_data_delete) self.widget_tree.connect('key_press_event', self.on_keypress) + self.widget_tree.connect('test-expand-row', self.test_expand_row) + self.widget_tree.set_expander_column(self.widget_tree.get_column(0)) def get_fields(self): return [col.name for col in self.widget_tree.get_columns() if col.name] @@ -298,7 +395,7 @@ class ViewList(ParserView): } value = Action._exec_action(act, self.window, data, {}) if self.screen: - self.screen.reload(writen=True) + self.screen.reload(written=True) return value @@ -307,16 +404,59 @@ class ViewList(ParserView): self.on_copy() return False if event.keyval in (gtk.keysyms.Down, gtk.keysyms.Up): - path, column = self.widget_tree.get_cursor() + path, column = widget.get_cursor() if not path: return False - store = self.widget_tree.get_model() + model = widget.get_model() if event.keyval == gtk.keysyms.Down: - if path[0] == len(store) - 1: - return True + test = True + for i in xrange(len(path)): + iter_ = model.get_iter(path[0:i+1]) + if model.iter_next(iter_): + test = False + if test: + iter_ = model.get_iter(path) + if (model.iter_has_child(iter_) + and widget.row_expanded(path)): + test = False + return test elif event.keyval == gtk.keysyms.Up: - if path[0] == 0: + if path == (0,): return True + if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right): + selection = widget.get_selection() + model, paths = selection.get_selected_rows() + if event.keyval == gtk.keysyms.Left: + if len(paths) == 1: + path, = paths + if not widget.row_expanded(path): + path = path[:-1] + if path: + selection.select_path(path) + widget.collapse_row(path) + for path in paths: + widget.collapse_row(path) + elif event.keyval == gtk.keysyms.Right: + for path in paths: + widget.expand_row(path, False) + + def test_expand_row(self, widget, iter_, path): + model = widget.get_model() + iter_ = model.iter_children(iter_) + if not iter_: + return False + fields = [col.name for col in self.widget_tree.get_columns() + if col.name] + while iter_: + record = model.get_value(iter_, 0) + if not record.get_loaded(fields): + try: + for field in fields: + record.__getitem__(field, True) + except Exception, exception: + return True + iter_ = model.iter_next(iter_) + return False def on_copy(self): clipboard = self.widget_tree.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD) @@ -371,8 +511,9 @@ class ViewList(ParserView): def drag_data_get(self, treeview, context, selection, target_id, etime): treeview.emit_stop_by_name('drag-data-get') - def _func_sel_get(store, path, iter, data): - data.append(path) + def _func_sel_get(store, path, iter_, data): + value = store.get_value(iter_, 0) + data.append(json.dumps(value.get_path(store.group))) data = [] treeselection = treeview.get_selection() treeselection.selected_foreach(_func_sel_get, data) @@ -392,20 +533,39 @@ class ViewList(ParserView): return if not selection.data: return - model = treeview.get_model() - data = common.safe_eval(selection.data) + store = treeview.get_model() + try: + data = json.loads(selection.data) + except ValueError: + return + record = store.group.get_by_path(data) + record_path = store.on_get_path(record) drop_info = treeview.get_dest_row_at_pos(x, y) + def check_recursion(from_, to): + if not from_ or not to: + return True + length = min(len(from_), len(to)) + if len(from_) < len(to) and from_[:length] == to[:length]: + return False + return True if drop_info: path, position = drop_info - idx = path[0] - if position in (gtk.TREE_VIEW_DROP_BEFORE, - gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): - model.move(data, idx) - else: - model.move(data, idx + 1) + check_path = path + if position in (gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_AFTER): + check_path = path[:-1] + if not check_recursion(record_path, check_path): + return + if position == gtk.TREE_VIEW_DROP_BEFORE: + store.move_before(record, path) + elif position == gtk.TREE_VIEW_DROP_AFTER: + store.move_after(record, path) + elif self.children_field: + store.move_into(record, path) + else: + store.move_end(record) context.drop_finish(False, etime) if treeview.sequence: - self.screen.group.set_sequence(field=treeview.sequence) + record.group.set_sequence(field=treeview.sequence) def drag_data_delete(self, treeview, context): treeview.emit_stop_by_name('drag-data-delete') @@ -543,21 +703,27 @@ class ViewList(ParserView): def __sig_switch(self, treeview, path, column): if column._type == 'button': return - self.screen.row_activate() + if not self.screen.row_activate(): + if treeview.row_expanded(path): + treeview.collapse_row(path) + else: + treeview.expand_row(path, False) def __select_changed(self, tree_sel): previous_record = self.screen.current_record if tree_sel.get_mode() == gtk.SELECTION_SINGLE: - model, iter = tree_sel.get_selected() - if iter: - path = model.get_path(iter)[0] - self.screen.current_record = model.group[path] + model, iter_ = tree_sel.get_selected() + if model and iter_: + record = model.get_value(iter_, 0) + self.screen.current_record = record elif tree_sel.get_mode() == gtk.SELECTION_MULTIPLE: model, paths = tree_sel.get_selected_rows() - if paths: - self.screen.current_record = model.group[paths[0][0]] + if model and paths: + iter_ = model.get_iter(paths[0]) + record = model.get_value(iter_, 0) + self.screen.current_record = record if hasattr(self.widget_tree, 'editable') \ and self.widget_tree.editable \ @@ -587,7 +753,8 @@ class ViewList(ParserView): or (not self.widget_tree.get_model()) \ or self.screen.group != \ self.widget_tree.get_model().group: - self.store = AdaptModelGroup(self.screen.group) + self.store = AdaptModelGroup(self.screen.group, + self.children_field) self.widget_tree.set_model(self.store) self.reload = False if not self.screen.current_record: @@ -647,6 +814,8 @@ class ViewList(ParserView): self.widget_tree.grab_focus() if self.screen.current_record: path = self.store.on_get_path(self.screen.current_record) + if self.store.get_flags() & gtk.TREE_MODEL_LIST_ONLY: + path = (path[0],) focus_column = None for column in self.widget_tree.get_columns(): renderers = column.get_cell_renderers() @@ -656,13 +825,16 @@ class ViewList(ParserView): if isinstance(renderer, CellRendererToggle): editable = renderer.get_property('activatable') elif isinstance(renderer, - (gtk.CellRendererProgress, CellRendererButton)): + (gtk.CellRendererProgress, CellRendererButton, + gtk.CellRendererPixbuf)): editable = False else: editable = renderer.get_property('editable') if column.get_visible() and editable: focus_column = column break + if path[:-1]: + self.widget_tree.expand_to_path(path[:-1]) self.widget_tree.scroll_to_cell(path, focus_column, use_align=False) self.widget_tree.set_cursor(path, focus_column, new) @@ -691,7 +863,8 @@ class ViewList(ParserView): if isinstance(renderer, CellRendererToggle): renderer.set_property('activatable', False) elif isinstance(renderer, - (gtk.CellRendererProgress, CellRendererButton)): + (gtk.CellRendererProgress, CellRendererButton, + gtk.CellRendererPixbuf)): pass else: renderer.set_property('editable', False) diff --git a/tryton/gui/window/view_form/view/list_gtk/editabletree.py b/tryton/gui/window/view_form/view/list_gtk/editabletree.py index 8b6f9f05cd..d17869124d 100644 --- a/tryton/gui/window/view_form/view/list_gtk/editabletree.py +++ b/tryton/gui/window/view_form/view/list_gtk/editabletree.py @@ -72,7 +72,7 @@ class EditableTreeView(gtk.TreeView): idx = (current + i + 1) % len(cols) if not cols[idx].get_cell_renderers(): continue - renderer = cols[idx].get_cell_renderers()[0] + renderer = cols[idx].get_cell_renderers()[-1] if isinstance(renderer, CellRendererToggle): editable = renderer.get_property('activatable') elif isinstance(renderer, @@ -91,7 +91,7 @@ class EditableTreeView(gtk.TreeView): idx = (current - (i + 1)) % len(cols) if not cols[idx].get_cell_renderers(): continue - renderer = cols[idx].get_cell_renderers()[0] + renderer = cols[idx].get_cell_renderers()[-1] if isinstance(renderer, CellRendererToggle): editable = renderer.get_property('activatable') elif isinstance(renderer, @@ -192,7 +192,7 @@ class EditableTreeView(gtk.TreeView): elif event.keyval in (gtk.keysyms.Return,): col = None for column in self.get_columns(): - renderer = column.get_cell_renderers()[0] + renderer = column.get_cell_renderers()[-1] if isinstance(renderer, CellRendererToggle): editable = renderer.get_property('activatable') elif isinstance(renderer, @@ -243,7 +243,6 @@ class EditableTreeView(gtk.TreeView): entry.set_max_length(int(field.attrs.get('size', 0))) # store in the record the entry widget to get the value in set_value field.editabletree_entry = entry - record.modified = True record.modified_fields.setdefault(column.name) return False diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/parser.py index 41f3bac2dd..5d035c8aad 100644 --- a/tryton/gui/window/view_form/view/list_gtk/parser.py +++ b/tryton/gui/window/view_form/view/list_gtk/parser.py @@ -66,8 +66,10 @@ def sort_model(column, treeview, screen): class ParserTree(ParserInterface): - def __init__(self, window, parent=None, attrs=None, screen=None): - super(ParserTree, self).__init__(window, parent, attrs, screen) + def __init__(self, window, parent=None, attrs=None, screen=None, + children_field=None): + super(ParserTree, self).__init__(window, parent, attrs, screen, + children_field=children_field) self.treeview = None def parse(self, model_name, root_node, fields): @@ -83,6 +85,7 @@ class ParserTree(ParserInterface): treeview.cells = {} treeview.sequence = attrs.get('sequence', False) treeview.colors = attrs.get('colors', '"black"') + treeview.keyword_open = attrs.get('keyword_open', False) self.treeview = treeview treeview.set_property('rules-hint', True) if not self.title: @@ -124,7 +127,22 @@ class ParserTree(ParserInterface): renderer.connect_after('editing-started', send_keys, treeview) - col = gtk.TreeViewColumn(fields[fname].attrs['string'], renderer) + col = gtk.TreeViewColumn(fields[fname].attrs['string']) + + if 'icon' in node_attrs: + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand=False) + icon = node_attrs['icon'] + def setter(column, cell, store, iter): + record = store.get_value(iter, 0) + value = record[icon].get_client(record) or '' + common.ICONFACTORY.register_icon(value) + pixbuf = treeview.render_icon(stock_id=value, + size=gtk.ICON_SIZE_BUTTON, detail=None) + cell.set_property('pixbuf', pixbuf) + col.set_cell_data_func(render_pixbuf, setter) + + col.pack_start(renderer, expand=False) col.name = fname hbox = gtk.HBox(False, 2) @@ -169,13 +187,16 @@ class ParserTree(ParserInterface): col.set_fixed_width(width) #XXX doesn't work well when resize columns #col.set_expand(True) - if not treeview.sequence \ - and fields[fname].attrs.get('sortable', True): + if (not treeview.sequence + and not self.children_field + and fields[fname].attrs.get('sortable', True)): col.connect('clicked', sort_model, treeview, self.screen) col.set_resizable(True) col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_visible(not node_attrs.get('tree_invisible', fields[fname].attrs.get('tree_invisible', False))) + if fname == self.screen.exclude_field: + col.set_visible(False) i = treeview.append_column(col) if 'sum' in node_attrs and fields[fname].attrs['type'] \ in ('integer', 'biginteger', 'float', 'numeric', @@ -317,6 +338,8 @@ class Char(object): raise NotImplementedError def get_textual_value(self, record): + if not record: + return '' return record[self.field_name].get_client(record) or '' def value_from_text(self, record, text): @@ -540,7 +563,7 @@ class M2O(Char): return True, searched return False, False screen = Screen(relation, self.window, domain=domain, context=context, - view_type=['form']) + mode=['form']) if obj_id: screen.load([obj_id]) win = WinForm(screen, self.window) @@ -605,7 +628,7 @@ class O2M(Char): relation = field.attrs['relation'] context = field.context_get(record) - screen = Screen(relation, self.window, view_type=['tree', 'form'], + screen = Screen(relation, self.window, mode=['tree', 'form'], exclude_field=field.attrs.get('relation_field')) screen.group = group win = WinForm(screen, self.window, view_type='tree', context=context) @@ -897,7 +920,7 @@ class Button(object): }, self.window, context=ctx) else: raise Exception('Unallowed button type') - self.screen.reload(writen=True) + self.screen.reload(written=True) else: self.screen.display() diff --git a/tryton/gui/window/view_form/view/widget_parse.py b/tryton/gui/window/view_form/view/widget_parse.py index 742c26c3a2..02ab9beb02 100644 --- a/tryton/gui/window/view_form/view/widget_parse.py +++ b/tryton/gui/window/view_form/view/widget_parse.py @@ -27,19 +27,21 @@ PARSERS2 = { class WidgetParse(ParserInterface): - def parse(self, screen, root_node, fields, toolbar=None): + def parse(self, screen, root_node, fields, toolbar=None, + children_field=None): widget = None for node in root_node.childNodes: if not node.nodeType == node.ELEMENT_NODE: continue if node.localName in PARSERS: widget = PARSERS[node.localName](self.window, self.parent, - self.attrs, screen) + self.attrs, screen, children_field) wid, child, buttons, on_write, notebooks, cursor_widget = \ widget.parse(screen.model_name, node, fields) screen.set_on_write(on_write) res = PARSERS2[node.localName](self.window, screen, wid, child, - buttons, toolbar, notebooks, cursor_widget) + buttons, toolbar, notebooks, cursor_widget, + children_field) res.title = widget.title widget = res break diff --git a/tryton/gui/window/view_form/widget_search/form.py b/tryton/gui/window/view_form/widget_search/form.py index 366b14d51b..1e88be6587 100644 --- a/tryton/gui/window/view_form/widget_search/form.py +++ b/tryton/gui/window/view_form/widget_search/form.py @@ -201,16 +201,16 @@ class Parse(object): class Form(object): - def __init__(self, xml, fields, model=None, parent=None, domain=None, + def __init__(self, view, model=None, parent=None, domain=None, call=None, context=None): if domain is None: domain = [] if context is None: context = {} - parser = Parse(parent, fields, model=model, context=context, + parser = Parse(parent, view['fields'], model=model, context=context, on_change=self.on_change) self.parent = parent - self.fields = fields + self.fields = view['fields'] self.model = model self.parser = parser self.call = call @@ -219,7 +219,7 @@ class Form(object): width = 640 if self.parent: width = self.parent.size_request()[0] - (self.widgets, self.widget) = parser.parse(xml, width) + (self.widgets, self.widget) = parser.parse(view['arch'], width) self.widget.show() self.spin_limit = parser.spin_limit self.spin_limit.connect('value-changed', self.limit_changed) diff --git a/tryton/gui/window/view_tree/__init__.py b/tryton/gui/window/view_tree/__init__.py deleted file mode 100644 index 3c3da70c87..0000000000 --- a/tryton/gui/window/view_tree/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -from view_tree import * -from view_tree_sc import * diff --git a/tryton/gui/window/view_tree/parse.py b/tryton/gui/window/view_tree/parse.py deleted file mode 100644 index a70d7a920b..0000000000 --- a/tryton/gui/window/view_tree/parse.py +++ /dev/null @@ -1,98 +0,0 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -"Parse" -import gtk -from xml.parsers import expat -import gettext - -_ = gettext.gettext - -class Parse(object): - "Parse" - - def __init__(self, fields): - self.fields = fields - self.pixbufs = {} - self.fields_order = [] - self.fields_attrs = {} - self.title = _('Tree') - self.toolbar = False - self.tree = None - self.pos = 1 - self.fill = 0 - - def _psr_start(self, name, attrs): - if name == 'tree': - if 'string' in attrs: - self.title = attrs['string'] - if 'toolbar' in attrs: - self.toolbar = bool(attrs.get('toolbar')) - if 'fill' in attrs: - self.fill = bool(int(attrs.get('fill'))) - elif name == 'field': - if not attrs['name'] in self.fields: - return - field_type = self.fields[attrs['name']]['type'] - if field_type != 'boolean': - column = gtk.TreeViewColumn( - self.fields[attrs['name']]['string']) - if 'icon' in attrs: - render_pixbuf = gtk.CellRendererPixbuf() - column.pack_start(render_pixbuf, expand=False) - column.add_attribute(render_pixbuf, 'pixbuf', self.pos) - self.fields_order.append(str(attrs['icon'])) - self.pixbufs[self.pos] = True - self.pos += 1 - - cell = gtk.CellRendererText() - cell.set_fixed_height_from_font(1) - if field_type in ('float', 'numeric', 'integer', - 'biginteger', 'float_time'): - cell.set_property('xalign', 1.0) - column.pack_start(cell, expand=False) - column.add_attribute(cell, 'text', self.pos) - else: - cell = gtk.CellRendererToggle() - column = gtk.TreeViewColumn( - self.fields[attrs['name']]['string']) - column.pack_start(cell, expand=False) - column.add_attribute(cell, 'active', self.pos) - self.pos += 1 - self.fields_order.append(str(attrs['name'])) - self.fields_attrs[attrs['name']] = attrs - column.set_resizable(True) - column.set_visible(not attrs.get('tree_invisible', False)) - self.tree.append_column(column) - elif name == 'button': - pass - else: - import logging - log = logging.getLogger('view') - log.error('unknown tag: '+str(name)) - del log - - def _psr_end(self, name): - pass - - def _psr_char(self, char): - pass - - def parse(self, xml_data, tree): - "Parse" - cell = gtk.CellRendererText() - cell.set_fixed_height_from_font(1) - column = gtk.TreeViewColumn('ID', cell, text=0) - column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - column.set_fixed_width(60) - column.set_visible(False) - tree.append_column(column) - self.tree = tree - psr = expat.ParserCreate() - psr.StartElementHandler = self._psr_start - psr.EndElementHandler = self._psr_end - psr.CharacterDataHandler = self._psr_char - psr.Parse(xml_data) - if not self.fill: - column = gtk.TreeViewColumn() - tree.append_column(column) - return self.pos diff --git a/tryton/gui/window/view_tree/view_tree.py b/tryton/gui/window/view_tree/view_tree.py deleted file mode 100644 index 2f04dc979e..0000000000 --- a/tryton/gui/window/view_tree/view_tree.py +++ /dev/null @@ -1,423 +0,0 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -"View tree" -import gtk -import gobject -from tryton.config import CONFIG -import time -import tryton.rpc as rpc -from parse import Parse -import datetime -import locale -from tryton.common import HM_FORMAT -import tryton.common as common -from tryton.translate import date_format -from tryton.pyson import PYSONDecoder -from tryton.signal_event import SignalEvent - -FIELDS_LIST_TYPE = { - 'boolean': gobject.TYPE_BOOLEAN, - 'integer': gobject.TYPE_INT, - 'biginteger': gobject.TYPE_INT, -} - - -class ViewTreeModel(gtk.GenericTreeModel, gtk.TreeSortable): - - def __init__(self, ids, view, fields, fields_type, fields_attrs, - context=None, pixbufs=None, treeview=None): - gtk.GenericTreeModel.__init__(self) - self.fields = fields - self.fields_type = fields_type - self.fields_attrs = fields_attrs - self.view = view - self.roots = ids - self.context = context or {} - self.to_reload = [] - self.tree = self._node_process(self.roots) - self.pixbufs = pixbufs or {} - self.treeview = treeview - - def _read(self, ids, fields): - ctx = {} - ctx.update(self.context) - ctx.update(rpc.CONTEXT) - res_ids = [] - if ids: - args = ('model', self.view['model'], 'read', ids, fields, ctx) - try: - res_ids = rpc.execute(*args) - for obj_id in ids: - if obj_id in self.to_reload: - self.to_reload.remove(obj_id) - except Exception, exception: - for obj_id in ids: - val = {'id': obj_id} - for field in fields: - if field in self.fields_type \ - and self.fields_type[field]['type'] \ - in ('one2many', 'many2many'): - val[field] = [] - else: - val[field] = '' - res_ids.append(val) - if obj_id not in self.to_reload: - self.to_reload.append(obj_id) - res_ids.sort(lambda x, y: cmp(ids.index(x['id']), ids.index(y['id']))) - for field in self.fields: - field_type = self.fields_type[field]['type'] - if field in self.fields_attrs \ - and 'widget' in self.fields_attrs[field]: - field_type = self.fields_attrs[field]['widget'] - if field_type in ('date',): - display_format = date_format() - for obj in res_ids: - if obj[field]: - obj[field] = common.datetime_strftime(obj[field], - display_format) - elif field_type in ('datetime',): - display_format = date_format() + ' ' + HM_FORMAT - for obj in res_ids: - if obj[field]: - if 'timezone' in rpc.CONTEXT: - try: - import pytz - lzone = pytz.timezone(rpc.CONTEXT['timezone']) - szone = pytz.timezone(rpc.TIMEZONE) - sdt = szone.localize(obj[field], is_dst=True) - ldt = sdt.astimezone(lzone) - obj[field] = ldt - except Exception: - pass - obj[field] = common.datetime_strftime(obj[field], - display_format) - elif field_type in ('many2one', 'one2one'): - for obj in res_ids: - if obj[field]: - obj[field] = obj[field + '.rec_name'] - elif field_type in ('selection'): - selection = self.fields_type[field]['selection'] - if 'relation' in self.fields_type[field]: - try: - result = rpc.execute('model', - self.fields_type[field]['relation'], - 'search_read', - self.fields_type[field].get('domain', []), - 0, None, None, - ['rec_name'], rpc.CONTEXT) - selection = [(x['id'], x['rec_name']) for x in result] - except Exception: - selection = [] - else: - if not isinstance(self.fields_type[field]['selection'], - (list, tuple)): - try: - selection = rpc.execute('model', - self.view['model'], - self.fields_type[field]['selection'], - rpc.CONTEXT) - except Exception: - selection = [] - self.fields_type[field]['selection'] = selection - elif field_type in ('float', 'numeric'): - digits = self.fields_type[field].get('digits', (16, 2)) - for obj in res_ids: - if isinstance(digits, str): - digits = PYSONDecoder(obj).decode(digits) - obj[field] = locale.format('%.' + str(digits[1]) + 'f', - round(obj[field] or 0.0, digits[1]), True) - elif field_type in ('integer',): - for obj in res_ids: - obj[field] = locale.format('%d', obj[field] or 0, True) - elif field_type in ('float_time',): - conv = None - if 'float_time' in self.fields_attrs[field]: - conv = rpc.CONTEXT.get( - self.fields_attrs[field]['float_time']) - for obj in res_ids: - obj[field] = common.float_time_to_text(obj[field], conv) - elif field_type in ('boolean',): - for obj in res_ids: - obj[field] = bool(obj[field]) - return res_ids - - def _node_process(self, ids): - tree = [] - fields = self.fields_type.keys() - for field_name in self.fields_type: - if self.fields_type[field_name]['type'] in ('many2one', 'one2one'): - fields.append(field_name + '.rec_name') - if self.view.get('field_childs', False): - res = self._read(ids, fields + [self.view['field_childs']]) - for obj in res: - tree.append([obj['id'], None, [], - obj[self.view['field_childs']]]) - tree[-1][1] = [obj[y] for y in self.fields] - if obj[self.view['field_childs']]: - tree[-1][2] = None - else: - res = self._read(ids, fields) - for obj in res: - tree.append([obj['id'], [obj[y] for y in self.fields], []]) - return tree - - def _node_expand(self, node): - node[2] = self._node_process(node[3]) - - #Mandatory GenericTreeModel method - def on_get_path(self, node): - '''returns the tree path (a tuple of indices)''' - return tuple([x[0] for x in node]) - - def on_get_flags(self): - return 0 - - def on_get_n_columns(self): - return len(self.fields)+1 - - def on_get_column_type(self, index): - if index in self.pixbufs: - return gtk.gdk.Pixbuf - if index == 0: - return gobject.TYPE_INT - return FIELDS_LIST_TYPE.get( - self.fields_type[self.fields[index-1]]['type'], - gobject.TYPE_STRING) - - def on_get_tree_path(self, node): - '''returns the tree path (a tuple of indices)''' - return tuple([x[0] for x in node]) - - def on_get_iter(self, path): - '''returns the node corresponding to the given path.''' - node = [] - tree = self.tree - for i in path: - if not tree or i >= len(tree): - return None - node.append((i, tree)) - tree = tree[i][2] - return node - - def on_get_value(self, node, column): - (i, values) = node[-1] - if column: - value = values[i][1][column - 1] - else: - return values[i][0] - - res = value or '' - if (column in self.pixbufs) and res: - common.ICONFACTORY.register_icon(res) - return self.treeview.render_icon(stock_id=res, - size=gtk.ICON_SIZE_BUTTON, detail=None) - field = self.fields[column - 1] - field_type = self.fields_type[field]['type'] - if field_type in ('selection'): - res = dict(self.fields_type[field]['selection']).get(res, '') - return res - - def on_iter_next(self, node): - '''returns the next node at this level of the tree''' - node = node[:] - (i, values) = node[-1] - if i < len(values) - 1: - node[-1] = (i + 1, values) - return node - return None - - def on_iter_children(self, node): - '''returns the first child of this node''' - if node is None: - return [(0, self.tree)] - node = node[:] - (i, values) = node[-1] - - to_reload = False - if len(values[i]) >= 4: - for obj_id in values[i][3]: - if obj_id in self.to_reload: - to_reload = True - - if values[i][2] is None or to_reload: - self._node_expand(values[i]) - if values[i][2] == []: - return None - node.append((0, values[i][2])) - return node - - def on_iter_has_child(self, node): - '''returns true if this node has children''' - (i, values) = node[-1] - return values[i][2] != [] - - def on_iter_n_children(self, node): - '''returns the number of children of this node''' - if node is None: - return len(self.tree) - (i, values) = node[-1] - - to_reload = False - if len(values[i]) >= 4: - for obj_id in values[i][3]: - if obj_id in self.to_reload: - to_reload = True - - if values[i][2] is None or to_reload: - self._node_expand(values[i]) - return len(values[i][2]) - - def on_iter_nth_child(self, node, child): - '''returns the nth child of this node''' - if node is None: - if child < len(self.tree): - return [(child, self.tree)] - return None - node = node[:] - (i, values) = node[-1] - - to_reload = False - if len(values[i]) >= 4: - for obj_id in values[i][3]: - if obj_id in self.to_reload: - to_reload = True - - if values[i][2] is None or to_reload: - self._node_expand(values[i]) - if child < len(values[i][2]): - node.append((child, values[i][2])) - return node - return None - - def on_iter_parent(self, node): - '''returns the parent of this node''' - if node is None: - return None - return node[:-1] - - def cus_refresh(self): - tree = self.tree - tree[0][2] = None - - def _cus_row_find(self, ids_res): - tree = self.tree - try: - ids = ids_res[:] - while len(ids)>0: - if ids[-1] in self.roots: - ids.pop() - break - ids.pop() - path = [] - while ids != []: - path.append(0) - val = ids.pop() - i = iter(tree) - while True: - node = i.next() - if node[0] == val: - break - path[-1] += 1 - if (node[2] is None) and (ids != []): - return None - tree = node[2] - return (tuple(path), node) - except Exception: - return None - -class ViewTree(SignalEvent): - "View tree" - - def __init__(self, view_info, ids, window, sel_multi=False, - context=None): - super(ViewTree, self).__init__() - self.window = window - self.view = gtk.TreeView() - self.view.set_headers_visible(not CONFIG['client.modepda']) - self.context = {} - if context: - self.context.update(context) - self.fields = view_info['fields'] - parse = Parse(self.fields) - parse.parse(view_info['arch'], self.view) - self.toolbar = parse.toolbar - self.pixbufs = parse.pixbufs - self.name = parse.title - self.sel_multi = sel_multi - - selection = self.view.get_selection() - if sel_multi: - selection.set_mode(gtk.SELECTION_MULTIPLE) - else: - selection.self.view.get_selection().set_mode(gtk.SELECTION_SINGLE) - selection.connect('changed', self.__select_changed) - self.view.set_expander_column(self.view.get_column(1)) - self.view.set_enable_search(False) - self.view.get_column(0).set_visible(False) - self.view.connect('key_press_event', self.on_keypress) - - self.ids = ids - self.view_info = view_info - self.fields_order = parse.fields_order - self.fields_attrs = parse.fields_attrs - self.model = None - self.reload() - - self.view.show_all() - self.search = [] - self.next = 0 - - def on_keypress(self, widget, event): - if event.keyval in (gtk.keysyms.Down, gtk.keysyms.Up): - path, column = self.view.get_cursor() - if not path: - return False - store = self.view.get_model() - if event.keyval == gtk.keysyms.Down: - iter = store.get_iter(path) - if path[0] == len(store) - 1 \ - and not store.iter_next(iter) \ - and (not store.iter_has_child(iter) \ - or not self.view.row_expanded(path)): - return True - elif event.keyval == gtk.keysyms.Up: - if path[0] == 0 \ - and len(path) == 1: - return True - - def reload(self): - self.model = ViewTreeModel(self.ids, self.view_info, self.fields_order, - self.fields, self.fields_attrs, context=self.context, - pixbufs=self.pixbufs, treeview=self.view) - self.view.set_model(self.model) - - def widget_get(self): - return self.view - - def sel_ids_get(self): - sel = self.view.get_selection() - if not sel: - return None - sel = sel.get_selected_rows() - if not sel: - return [] - (model, iters) = sel - return [int(model.get_value(model.get_iter(x), 0)) for x in iters] - - def sel_id_get(self): - res = self.sel_ids_get() - if res: - res = res[0] - return res - - def value_get(self, col): - sel = self.view.get_selection().get_selected_rows() - if sel is None: - return None - (model, i) = sel - if not i: - return None - return model.get_value(i, col) - - def __select_changed(self, tree_sel): - self.signal('select-changed') diff --git a/tryton/gui/window/view_tree/view_tree_sc.py b/tryton/gui/window/view_tree/view_tree_sc.py deleted file mode 100644 index 51feb07ea4..0000000000 --- a/tryton/gui/window/view_tree/view_tree_sc.py +++ /dev/null @@ -1,75 +0,0 @@ -#This file is part of Tryton. The COPYRIGHT file at the top level of -#this repository contains the full copyright notices and license terms. -import tryton.rpc as rpc -import gobject -import gtk -import gettext -from tryton.gui import Main -import tryton.common as common - -_ = gettext.gettext - - -class ViewTreeSC(object): - - def __init__(self, tree, model, window): - self.model = model - self.tree = tree - self.window = window - self.tree.get_selection().set_mode('single') - column = gtk.TreeViewColumn (_('ID'), gtk.CellRendererText(), text=0) - self.tree.append_column(column) - column.set_visible(False) - cell = gtk.CellRendererText() - - column = gtk.TreeViewColumn (_('Description'), cell, text=1) - self.tree.append_column(column) - self.tree.connect('key_press_event', self.on_keypress) - - def on_keypress(self, widget, event): - if event.keyval in (gtk.keysyms.Down, gtk.keysyms.Up): - path, column = self.tree.get_cursor() - if not path: - return False - store = self.tree.get_model() - if event.keyval == gtk.keysyms.Down: - if path[0] == len(store) - 1: - return True - elif event.keyval == gtk.keysyms.Up: - if path[0] == 0: - return True - - def update(self): - store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, - gobject.TYPE_STRING) - user = rpc._USER - args = ('model', 'ir.ui.view_sc', 'get_sc', user, self.model, - rpc.CONTEXT) - try: - view_sc = rpc.execute(*args) - except Exception, exception: - view_sc = common.process_exception(exception, self.window, *args) - if not view_sc: - return - for shortcut in view_sc: - num = store.append() - store.set(num, 0, shortcut['res_id'], 1, shortcut['name'], - 2, shortcut['id']) - self.tree.set_model(store) - if self.model == 'ir.ui.menu': - Main.get_main().shortcut_set(shortcuts=view_sc) - - def value_get(self, col): - sel = self.tree.get_selection().get_selected() - if sel is None: - return None - (model, i) = sel - if not i: - return None - return model.get_value(i, col) - - def sel_id_get(self): - res = self.value_get(0) - if res is not None: - return int(res) - return None diff --git a/tryton/gui/window/win_form.py b/tryton/gui/window/win_form.py index 593fc24b73..442e1f9ce1 100644 --- a/tryton/gui/window/win_form.py +++ b/tryton/gui/window/win_form.py @@ -268,7 +268,7 @@ class WinForm(object): def _sig_label(self, screen, signal_data): name = '_' if signal_data[0] >= 0: - name = str(signal_data[0] + 1) + name = str(signal_data[0]) line = '(%s/%s)' % (name, signal_data[1]) self.label.set_text(line) @@ -303,7 +303,7 @@ class WinForm(object): if res == gtk.RESPONSE_OK: return True elif res == gtk.RESPONSE_CANCEL: - self.screen.remove(delete=True) + self.screen.remove() return False def new(self): diff --git a/tryton/gui/window/win_search.py b/tryton/gui/window/win_search.py index 5318d08c7f..d6e5a7e787 100644 --- a/tryton/gui/window/win_search.py +++ b/tryton/gui/window/win_search.py @@ -48,7 +48,7 @@ class WinSearch(object): self.win.vbox.pack_start(scrollwindow, expand=True, fill=True) self.screen = Screen(model, self.win, domain=domain, - view_type=['tree'], context=context, + mode=['tree'], context=context, views_preload=views_preload, row_activate=self.sig_activate) self.view = self.screen.current_view self.view.unset_editable() @@ -104,7 +104,7 @@ class WinSearch(object): elif button == gtk.RESPONSE_ACCEPT: res = None screen = Screen(self.model_name, self.win, domain=self.domain, - context=self.context, view_type=['form']) + context=self.context, mode=['form']) win = WinForm(screen, self.win, new=True) while win.run(): if screen.save_current(): diff --git a/tryton/gui/window/window.py b/tryton/gui/window/window.py index bd62eacace..f6a24abf2c 100644 --- a/tryton/gui/window/window.py +++ b/tryton/gui/window/window.py @@ -7,35 +7,22 @@ from preference import * class Window(object): @staticmethod - def create(view_ids, model, res_id=False, domain=None, - view_type='form', window=None, context=None, mode=None, name=False, - limit=None, auto_refresh=False, search_value=None, icon=None): + def create(view_ids, model, res_id=False, domain=None, window=None, + context=None, mode=None, name=False, limit=None, + auto_refresh=False, search_value=None, icon=None): from tryton.gui import Main if context is None: context = {} - if view_type not in ('form', 'tree', 'board'): - import logging - log = logging.getLogger('view') - log.error('unknown view type: '+view_type) - del log - return - if view_type == 'form': + if model: from form import Form - win = Form(model, window, res_id, domain, view_type=mode, - view_ids = (view_ids or []), context=context, name=name, + win = Form(model, window, res_id, domain, mode=mode, + view_ids=(view_ids or []), context=context, name=name, limit=limit, auto_refresh=auto_refresh, search_value=search_value) - elif view_type == 'tree': - if model == 'ir.ui.menu': - if Main.get_main().sig_reload_menu(): - return - from tree import Tree - win = Tree(model, window, res_id, view_ids and view_ids[0] or None, - domain, context, name=name) - elif view_type == 'board': + else: from board import Board - win = Board(window, view_ids and view_ids[0] or None, + win = Board(model, window, view_ids and view_ids[0] or None, context=context, name=name, auto_refresh=auto_refresh) win.icon = icon Main.get_main().win_add(win) diff --git a/tryton/gui/window/wizard.py b/tryton/gui/window/wizard.py index d47e9badfb..84e0f8d274 100644 --- a/tryton/gui/window/wizard.py +++ b/tryton/gui/window/wizard.py @@ -83,8 +83,7 @@ class Wizard(SignalEvent): elif res['type'] == 'form': self.datas['form'] = {} if res['type'] == 'form': - self.update(res['arch'], res['fields'], res['state'], - res['object'], context=ctx) + self.update(res, res['state'], res['object'], context=ctx) self.screen.current_record.set_default(self.datas['form']) break elif res['type'] == 'action': @@ -148,7 +147,7 @@ class Wizard(SignalEvent): self.state = state self.process() - def update(self, arch, fields, state, obj_name, context=None): + def update(self, view, state, obj_name, context=None): self.model = obj_name hbuttonbox = gtk.HButtonBox() @@ -170,13 +169,13 @@ class Wizard(SignalEvent): hbuttonbox.pack_start(but) val = {} + fields = view['fields'] for i in fields: if 'value' in fields[i]: val[i] = fields[i]['value'] - self.screen = Screen(obj_name, self.window, view_type=[], - context=context) - self.screen.add_view(arch, fields, display=True) + self.screen = Screen(obj_name, self.window, mode=[], context=context) + self.screen.add_view(view, display=True) self.screen.widget.show() title = gtk.Label() diff --git a/tryton/signal_event.py b/tryton/signal_event.py index 2c6de8c79b..3b8759b81c 100644 --- a/tryton/signal_event.py +++ b/tryton/signal_event.py @@ -31,7 +31,7 @@ class SignalEvent(object): for sig in signal: i = 0 while i < len(self.__connects[sig]): - if self.__connects[sig][i][2] == key: + if self.__connects[sig][i][2] is key: del self.__connects[sig][i] else: i += 1 diff --git a/tryton/wizard/main.py b/tryton/wizard/main.py index 450ad15cfd..ec9479235b 100644 --- a/tryton/wizard/main.py +++ b/tryton/wizard/main.py @@ -16,7 +16,7 @@ _ = gettext.gettext class Dialog(object): "Dialog for wizard" - def __init__(self, arch, fields, state, obj_name, parent, + def __init__(self, view, state, obj_name, parent, action='', size=(0, 0), context=None): self.parent = parent self.action = action @@ -52,12 +52,13 @@ class Dialog(object): self.states.append(i[0]) val = {} + fields = view['fields'] for i in fields: if 'value' in fields[i]: val[i] = fields[i]['value'] - self.screen = Screen(obj_name, self.dia, view_type=[], context=context) - self.screen.add_view(arch, fields, display=True) + self.screen = Screen(obj_name, self.dia, mode=[], context=context) + self.screen.add_view(view, display=True) title = gtk.Label() title.set_use_markup(True) @@ -216,9 +217,8 @@ class Wizard(object): datas['form'] = {} if res['type'] == 'form': if not dia: - dia = Dialog(res['arch'], res['fields'], res['state'], - res['object'], parent, action=action, - size=res['size'], context=ctx) + dia = Dialog(res, res['state'], res['object'], parent, + action=action, size=res['size'], context=ctx) dia.screen.current_record.set_default(datas['form']) res2 = dia.run(datas['form']) if not res2: