Merge tree and list views
It adds also Drag & Drop on tree and a new UI layout. The menu is now on a left side and the toolbar is inside tab.
This commit is contained in:
1 parent
2457573f7b
commit
5d3d16cbdf
38 files changed
+1360
-1950
No files matched your search
@@ -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
|
||||
|
||||
+253
-322
@@ -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
|
||||
********
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
+162
-109
@@ -87,6 +87,13 @@ class Main(object):
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/File/Quit', gtk.keysyms.Q,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/User/Menu Reload', gtk.keysyms.T,
|
||||
gtk.gdk.MOD1_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/User/Menu Toggle', gtk.keysyms.T,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/User/Home', gtk.keysyms.H,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
|
||||
gtk.accel_map_add_entry('<tryton>/Form/New', gtk.keysyms.N,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Save', gtk.keysyms.S,
|
||||
@@ -103,10 +110,6 @@ class Main(object):
|
||||
0)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Switch View', gtk.keysyms.L,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Menu', gtk.keysyms.T,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Home', gtk.keysyms.H,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/Form/Close', gtk.keysyms.W,
|
||||
gtk.gdk.CONTROL_MASK)
|
||||
gtk.accel_map_add_entry('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/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('<tryton>/Form/Import Data')
|
||||
menu_form.add(menuitem_import)
|
||||
|
||||
menu_form.set_accel_group(self.accel_group)
|
||||
menu_form.set_accel_path('<tryton>/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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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('<b>' + self.name + '</b>')
|
||||
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())
|
||||
|
||||
@@ -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 ?'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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 '<Record %s@%s>' % (self.id, self.model_name)
|
||||
return '<Record %s@%s at %s>' % (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
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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']
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 *
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user